summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_fs_base/tree.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_base/tree.c')
-rw-r--r--subversion/libsvn_fs_base/tree.c5451
1 files changed, 5451 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_base/tree.c b/subversion/libsvn_fs_base/tree.c
new file mode 100644
index 0000000..e603af4
--- /dev/null
+++ b/subversion/libsvn_fs_base/tree.c
@@ -0,0 +1,5451 @@
+/* tree.c : tree-like filesystem, built on DAG filesystem
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+/* The job of this layer is to take a filesystem with lots of node
+ sharing going on --- the real DAG filesystem as it appears in the
+ database --- and make it look and act like an ordinary tree
+ filesystem, with no sharing.
+
+ We do just-in-time cloning: you can walk from some unfinished
+ transaction's root down into directories and files shared with
+ committed revisions; as soon as you try to change something, the
+ appropriate nodes get cloned (and parent directory entries updated)
+ invisibly, behind your back. Any other references you have to
+ nodes that have been cloned by other changes, even made by other
+ processes, are automatically updated to point to the right clones. */
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "svn_private_config.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "svn_mergeinfo.h"
+#include "svn_fs.h"
+#include "svn_sorts.h"
+#include "svn_checksum.h"
+#include "fs.h"
+#include "err.h"
+#include "trail.h"
+#include "node-rev.h"
+#include "key-gen.h"
+#include "dag.h"
+#include "tree.h"
+#include "lock.h"
+#include "revs-txns.h"
+#include "id.h"
+#include "bdb/txn-table.h"
+#include "bdb/rev-table.h"
+#include "bdb/nodes-table.h"
+#include "bdb/changes-table.h"
+#include "bdb/copies-table.h"
+#include "bdb/node-origins-table.h"
+#include "bdb/miscellaneous-table.h"
+#include "../libsvn_fs/fs-loader.h"
+#include "private/svn_fspath.h"
+#include "private/svn_fs_util.h"
+#include "private/svn_mergeinfo_private.h"
+
+
+/* ### I believe this constant will become internal to reps-strings.c.
+ ### see the comment in window_consumer() for more information. */
+
+/* ### the comment also seems to need tweaking: the log file stuff
+ ### is no longer an issue... */
+/* Data written to the filesystem through the svn_fs_apply_textdelta()
+ interface is cached in memory until the end of the data stream, or
+ until a size trigger is hit. Define that trigger here (in bytes).
+ Setting the value to 0 will result in no filesystem buffering at
+ all. The value only really matters when dealing with file contents
+ bigger than the value itself. Above that point, large values here
+ allow the filesystem to buffer more data in memory before flushing
+ to the database, which increases memory usage but greatly decreases
+ the amount of disk access (and log-file generation) in database.
+ Smaller values will limit your overall memory consumption, but can
+ drastically hurt throughput by necessitating more write operations
+ to the database (which also generates more log-files). */
+#define WRITE_BUFFER_SIZE 512000
+
+/* The maximum number of cache items to maintain in the node cache. */
+#define NODE_CACHE_MAX_KEYS 32
+
+
+
+/* The root structure. */
+
+/* Structure for svn_fs_root_t's node_cache hash values. */
+struct dag_node_cache_t
+{
+ dag_node_t *node; /* NODE to be cached. */
+ int idx; /* Index into the keys array for this cache item's key. */
+ apr_pool_t *pool; /* Pool in which NODE is allocated. */
+};
+
+
+typedef struct base_root_data_t
+{
+
+ /* For revision roots, this is a dag node for the revision's root
+ directory. For transaction roots, we open the root directory
+ afresh every time, since the root may have been cloned, or
+ the transaction may have disappeared altogether. */
+ dag_node_t *root_dir;
+
+ /* Cache structures, for mapping const char * PATH to const
+ struct dag_node_cache_t * structures.
+
+ ### Currently this is only used for revision roots. To be safe
+ for transaction roots, you must have the guarantee that there is
+ never more than a single transaction root per Subversion
+ transaction ever open at a given time -- having two roots open to
+ the same Subversion transaction would be a request for pain.
+ Also, you have to ensure that if a 'make_path_mutable()' fails for
+ any reason, you don't leave cached nodes for the portion of that
+ function that succeeded. In other words, this cache must never,
+ ever, lie. */
+ apr_hash_t *node_cache;
+ const char *node_cache_keys[NODE_CACHE_MAX_KEYS];
+ int node_cache_idx;
+} base_root_data_t;
+
+
+static svn_fs_root_t *make_revision_root(svn_fs_t *fs, svn_revnum_t rev,
+ dag_node_t *root_dir,
+ apr_pool_t *pool);
+
+static svn_fs_root_t *make_txn_root(svn_fs_t *fs, const char *txn,
+ svn_revnum_t base_rev, apr_uint32_t flags,
+ apr_pool_t *pool);
+
+
+/*** Node Caching in the Roots. ***/
+
+/* Return NODE for PATH from ROOT's node cache, or NULL if the node
+ isn't cached. */
+static dag_node_t *
+dag_node_cache_get(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ base_root_data_t *brd = root->fsap_data;
+ struct dag_node_cache_t *cache_item;
+
+ /* Assert valid input. */
+ assert(*path == '/');
+
+ /* Only allow revision roots. */
+ if (root->is_txn_root)
+ return NULL;
+
+ /* Look in the cache for our desired item. */
+ cache_item = svn_hash_gets(brd->node_cache, path);
+ if (cache_item)
+ return svn_fs_base__dag_dup(cache_item->node, pool);
+
+ return NULL;
+}
+
+
+/* Add the NODE for PATH to ROOT's node cache. Callers should *NOT*
+ call this unless they are adding a currently un-cached item to the
+ cache, or are replacing the NODE for PATH with a new (different)
+ one. */
+static void
+dag_node_cache_set(svn_fs_root_t *root,
+ const char *path,
+ dag_node_t *node)
+{
+ base_root_data_t *brd = root->fsap_data;
+ const char *cache_path;
+ apr_pool_t *cache_pool;
+ struct dag_node_cache_t *cache_item;
+ int num_keys = apr_hash_count(brd->node_cache);
+
+ /* What? No POOL passed to this function?
+
+ To ensure that our cache values live as long as the svn_fs_root_t
+ in which they are ultimately stored, and to allow us to free()
+ them individually without harming the rest, they are each
+ allocated from a subpool of ROOT's pool. We'll keep one subpool
+ around for each cache slot -- as we start expiring stuff
+ to make room for more entries, we'll re-use the expired thing's
+ pool. */
+
+ /* Assert valid input and state. */
+ assert(*path == '/');
+ assert((brd->node_cache_idx <= num_keys)
+ && (num_keys <= NODE_CACHE_MAX_KEYS));
+
+ /* Only allow revision roots. */
+ if (root->is_txn_root)
+ return;
+
+ /* Special case: the caller wants us to replace an existing cached
+ node with a new one. If the callers aren't mindless, this should
+ only happen when a node is made mutable under a transaction
+ root, and that only happens once under that root. So, we'll be a
+ little bit sloppy here, and count on callers doing the right
+ thing. */
+ cache_item = svn_hash_gets(brd->node_cache, path);
+ if (cache_item)
+ {
+ /* ### This section is somehow broken. I don't know how, but it
+ ### is. And I don't want to spend any more time on it. So,
+ ### callers, use only revision root and don't try to update
+ ### an already-cached thing. -- cmpilato */
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+
+#if 0
+ int cache_index = cache_item->idx;
+ cache_path = brd->node_cache_keys[cache_index];
+ cache_pool = cache_item->pool;
+ cache_item->node = svn_fs_base__dag_dup(node, cache_pool);
+
+ /* Now, move the cache key reference to the end of the keys in
+ the keys array (unless it's already at the end). ### Yes,
+ it's a memmove(), but we're not talking about pages of memory
+ here. */
+ if (cache_index != (num_keys - 1))
+ {
+ int move_num = NODE_CACHE_MAX_KEYS - cache_index - 1;
+ memmove(brd->node_cache_keys + cache_index,
+ brd->node_cache_keys + cache_index + 1,
+ move_num * sizeof(const char *));
+ cache_index = num_keys - 1;
+ brd->node_cache_keys[cache_index] = cache_path;
+ }
+
+ /* Advance the cache pointers. */
+ cache_item->idx = cache_index;
+ brd->node_cache_idx = (cache_index + 1) % NODE_CACHE_MAX_KEYS;
+ return;
+#endif
+ }
+
+ /* We're adding a new cache item. First, see if we have room for it
+ (otherwise, make some room). */
+ if (apr_hash_count(brd->node_cache) == NODE_CACHE_MAX_KEYS)
+ {
+ /* No room. Expire the oldest thing. */
+ cache_path = brd->node_cache_keys[brd->node_cache_idx];
+ cache_item = svn_hash_gets(brd->node_cache, cache_path);
+ svn_hash_sets(brd->node_cache, cache_path, NULL);
+ cache_pool = cache_item->pool;
+ svn_pool_clear(cache_pool);
+ }
+ else
+ {
+ cache_pool = svn_pool_create(root->pool);
+ }
+
+ /* Make the cache item, allocated in its own pool. */
+ cache_item = apr_palloc(cache_pool, sizeof(*cache_item));
+ cache_item->node = svn_fs_base__dag_dup(node, cache_pool);
+ cache_item->idx = brd->node_cache_idx;
+ cache_item->pool = cache_pool;
+
+ /* Now add it to the cache. */
+ cache_path = apr_pstrdup(cache_pool, path);
+ svn_hash_sets(brd->node_cache, cache_path, cache_item);
+ brd->node_cache_keys[brd->node_cache_idx] = cache_path;
+
+ /* Advance the cache pointer. */
+ brd->node_cache_idx = (brd->node_cache_idx + 1) % NODE_CACHE_MAX_KEYS;
+}
+
+
+
+
+/* Creating transaction and revision root nodes. */
+
+struct txn_root_args
+{
+ svn_fs_root_t **root_p;
+ svn_fs_txn_t *txn;
+};
+
+
+static svn_error_t *
+txn_body_txn_root(void *baton,
+ trail_t *trail)
+{
+ struct txn_root_args *args = baton;
+ svn_fs_root_t **root_p = args->root_p;
+ svn_fs_txn_t *txn = args->txn;
+ svn_fs_t *fs = txn->fs;
+ const char *svn_txn_id = txn->id;
+ const svn_fs_id_t *root_id, *base_root_id;
+ svn_fs_root_t *root;
+ apr_hash_t *txnprops;
+ apr_uint32_t flags = 0;
+
+ /* Verify that the transaction actually exists. */
+ SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &base_root_id, fs,
+ svn_txn_id, trail, trail->pool));
+
+ /* Look for special txn props that represent the 'flags' behavior of
+ the transaction. */
+ SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, svn_txn_id, trail));
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
+ flags |= SVN_FS_TXN_CHECK_OOD;
+
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
+ flags |= SVN_FS_TXN_CHECK_LOCKS;
+
+ root = make_txn_root(fs, svn_txn_id, txn->base_rev, flags, trail->pool);
+
+ *root_p = root;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__txn_root(svn_fs_root_t **root_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ struct txn_root_args args;
+
+ args.root_p = &root;
+ args.txn = txn;
+ SVN_ERR(svn_fs_base__retry_txn(txn->fs, txn_body_txn_root, &args,
+ FALSE, pool));
+
+ *root_p = root;
+ return SVN_NO_ERROR;
+}
+
+
+struct revision_root_args
+{
+ svn_fs_root_t **root_p;
+ svn_revnum_t rev;
+};
+
+
+static svn_error_t *
+txn_body_revision_root(void *baton,
+ trail_t *trail)
+{
+ struct revision_root_args *args = baton;
+ dag_node_t *root_dir;
+ svn_fs_root_t *root;
+
+ SVN_ERR(svn_fs_base__dag_revision_root(&root_dir, trail->fs, args->rev,
+ trail, trail->pool));
+ root = make_revision_root(trail->fs, args->rev, root_dir, trail->pool);
+
+ *args->root_p = root;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__revision_root(svn_fs_root_t **root_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ struct revision_root_args args;
+ svn_fs_root_t *root;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.root_p = &root;
+ args.rev = rev;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_root, &args,
+ FALSE, pool));
+
+ *root_p = root;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Getting dag nodes for roots. */
+
+
+/* Set *NODE_P to a freshly opened dag node referring to the root
+ directory of ROOT, as part of TRAIL. */
+static svn_error_t *
+root_node(dag_node_t **node_p,
+ svn_fs_root_t *root,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_root_data_t *brd = root->fsap_data;
+
+ if (! root->is_txn_root)
+ {
+ /* It's a revision root, so we already have its root directory
+ opened. */
+ *node_p = svn_fs_base__dag_dup(brd->root_dir, pool);
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* It's a transaction root. Open a fresh copy. */
+ return svn_fs_base__dag_txn_root(node_p, root->fs, root->txn,
+ trail, pool);
+ }
+}
+
+
+/* Set *NODE_P to a mutable root directory for ROOT, cloning if
+ necessary, as part of TRAIL. ROOT must be a transaction root. Use
+ ERROR_PATH in error messages. */
+static svn_error_t *
+mutable_root_node(dag_node_t **node_p,
+ svn_fs_root_t *root,
+ const char *error_path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ if (root->is_txn_root)
+ return svn_fs_base__dag_clone_root(node_p, root->fs, root->txn,
+ trail, pool);
+ else
+ /* If it's not a transaction root, we can't change its contents. */
+ return SVN_FS__ERR_NOT_MUTABLE(root->fs, root->rev, error_path);
+}
+
+
+
+/* Traversing directory paths. */
+
+typedef enum copy_id_inherit_t
+{
+ copy_id_inherit_unknown = 0,
+ copy_id_inherit_self,
+ copy_id_inherit_parent,
+ copy_id_inherit_new
+
+} copy_id_inherit_t;
+
+/* A linked list representing the path from a node up to a root
+ directory. We use this for cloning, and for operations that need
+ to deal with both a node and its parent directory. For example, a
+ `delete' operation needs to know that the node actually exists, but
+ also needs to change the parent directory. */
+typedef struct parent_path_t
+{
+
+ /* A node along the path. This could be the final node, one of its
+ parents, or the root. Every parent path ends with an element for
+ the root directory. */
+ dag_node_t *node;
+
+ /* The name NODE has in its parent directory. This is zero for the
+ root directory, which (obviously) has no name in its parent. */
+ char *entry;
+
+ /* The parent of NODE, or zero if NODE is the root directory. */
+ struct parent_path_t *parent;
+
+ /* The copy ID inheritance style. */
+ copy_id_inherit_t copy_inherit;
+
+ /* If copy ID inheritance style is copy_id_inherit_new, this is the
+ path which should be implicitly copied; otherwise, this is NULL. */
+ const char *copy_src_path;
+
+} parent_path_t;
+
+
+/* Return the FS path for the parent path chain object PARENT_PATH,
+ allocated in POOL. */
+static const char *
+parent_path_path(parent_path_t *parent_path,
+ apr_pool_t *pool)
+{
+ const char *path_so_far = "/";
+ if (parent_path->parent)
+ path_so_far = parent_path_path(parent_path->parent, pool);
+ return parent_path->entry
+ ? svn_fspath__join(path_so_far, parent_path->entry, pool)
+ : path_so_far;
+}
+
+
+/* Return the FS path for the parent path chain object CHILD relative
+ to its ANCESTOR in the same chain, allocated in POOL. */
+static const char *
+parent_path_relpath(parent_path_t *child,
+ parent_path_t *ancestor,
+ apr_pool_t *pool)
+{
+ const char *path_so_far = "";
+ parent_path_t *this_node = child;
+ while (this_node != ancestor)
+ {
+ assert(this_node != NULL);
+ path_so_far = svn_relpath_join(this_node->entry, path_so_far, pool);
+ this_node = this_node->parent;
+ }
+ return path_so_far;
+}
+
+
+/* Choose a copy ID inheritance method *INHERIT_P to be used in the
+ event that immutable node CHILD in FS needs to be made mutable. If
+ the inheritance method is copy_id_inherit_new, also return a
+ *COPY_SRC_PATH on which to base the new copy ID (else return NULL
+ for that path). CHILD must have a parent (it cannot be the root
+ node). TXN_ID is the transaction in which these items might be
+ mutable. */
+static svn_error_t *
+get_copy_inheritance(copy_id_inherit_t *inherit_p,
+ const char **copy_src_path,
+ svn_fs_t *fs,
+ parent_path_t *child,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *child_id, *parent_id;
+ const char *child_copy_id, *parent_copy_id;
+ const char *id_path = NULL;
+
+ SVN_ERR_ASSERT(child && child->parent && txn_id);
+
+ /* Initialize our return variables (default: self-inheritance). */
+ *inherit_p = copy_id_inherit_self;
+ *copy_src_path = NULL;
+
+ /* Initialize some convenience variables. */
+ child_id = svn_fs_base__dag_get_id(child->node);
+ parent_id = svn_fs_base__dag_get_id(child->parent->node);
+ child_copy_id = svn_fs_base__id_copy_id(child_id);
+ parent_copy_id = svn_fs_base__id_copy_id(parent_id);
+
+ /* Easy out: if this child is already mutable, we have nothing to do. */
+ if (svn_fs_base__key_compare(svn_fs_base__id_txn_id(child_id), txn_id) == 0)
+ return SVN_NO_ERROR;
+
+ /* If the child and its parent are on the same branch, then the
+ child will inherit the copy ID of its parent when made mutable.
+ This is trivially detectable when the child and its parent have
+ the same copy ID. But that's not the sole indicator of
+ same-branchness. It might be the case that the parent was the
+ result of a copy, but the child has not yet been cloned for
+ mutability since that copy. Detection of this latter case
+ basically means making sure the copy IDs don't differ for some
+ other reason, such as that the child was the direct target of the
+ copy whose ID it has. There is a special case here, too -- if
+ the child's copy ID is the special ID "0", it can't have been the
+ target of any copy, and therefore must be on the same branch as
+ its parent. */
+ if ((strcmp(child_copy_id, "0") == 0)
+ || (svn_fs_base__key_compare(child_copy_id, parent_copy_id) == 0))
+ {
+ *inherit_p = copy_id_inherit_parent;
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ copy_t *copy;
+ SVN_ERR(svn_fs_bdb__get_copy(&copy, fs, child_copy_id, trail, pool));
+ if (svn_fs_base__id_compare(copy->dst_noderev_id, child_id) == -1)
+ {
+ *inherit_p = copy_id_inherit_parent;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* If we get here, the child and its parent are not on speaking
+ terms -- there will be no parental inheritance handed down in
+ *this* generation. */
+
+ /* If the child was created at a different path than the one we are
+ expecting its clone to live, one of its parents must have been
+ created via a copy since the child was created. The child isn't
+ on the same branch as its parent (we caught those cases early);
+ it can't keep its current copy ID because there's been an
+ affecting copy (its clone won't be on the same branch as the
+ child is). That leaves only one course of action -- to assign
+ the child a brand new "soft" copy ID. */
+ id_path = svn_fs_base__dag_get_created_path(child->node);
+ if (strcmp(id_path, parent_path_path(child, pool)) != 0)
+ {
+ *inherit_p = copy_id_inherit_new;
+ *copy_src_path = id_path;
+ return SVN_NO_ERROR;
+ }
+
+ /* The node gets to keep its own ID. */
+ return SVN_NO_ERROR;
+}
+
+
+/* Allocate a new parent_path_t node from POOL, referring to NODE,
+ ENTRY, PARENT, and COPY_ID. */
+static parent_path_t *
+make_parent_path(dag_node_t *node,
+ char *entry,
+ parent_path_t *parent,
+ apr_pool_t *pool)
+{
+ parent_path_t *parent_path = apr_pcalloc(pool, sizeof(*parent_path));
+ parent_path->node = node;
+ parent_path->entry = entry;
+ parent_path->parent = parent;
+ parent_path->copy_inherit = copy_id_inherit_unknown;
+ parent_path->copy_src_path = NULL;
+ return parent_path;
+}
+
+
+/* Flags for open_path. */
+typedef enum open_path_flags_t {
+
+ /* The last component of the PATH need not exist. (All parent
+ directories must exist, as usual.) If the last component doesn't
+ exist, simply leave the `node' member of the bottom parent_path
+ component zero. */
+ open_path_last_optional = 1
+
+} open_path_flags_t;
+
+
+/* Open the node identified by PATH in ROOT, as part of TRAIL. Set
+ *PARENT_PATH_P to a path from the node up to ROOT, allocated in
+ TRAIL->pool. The resulting *PARENT_PATH_P value is guaranteed to
+ contain at least one element, for the root directory.
+
+ If resulting *PARENT_PATH_P will eventually be made mutable and
+ modified, or if copy ID inheritance information is otherwise
+ needed, TXN_ID should be the ID of the mutability transaction. If
+ TXN_ID is NULL, no copy ID in heritance information will be
+ calculated for the *PARENT_PATH_P chain.
+
+ If FLAGS & open_path_last_optional is zero, return the error
+ SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist. If
+ non-zero, require all the parent directories to exist as normal,
+ but if the final path component doesn't exist, simply return a path
+ whose bottom `node' member is zero. This option is useful for
+ callers that create new nodes --- we find the parent directory for
+ them, and tell them whether the entry exists already.
+
+ NOTE: Public interfaces which only *read* from the filesystem
+ should not call this function directly, but should instead use
+ get_dag().
+*/
+static svn_error_t *
+open_path(parent_path_t **parent_path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ int flags,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = root->fs;
+ dag_node_t *here; /* The directory we're currently looking at. */
+ parent_path_t *parent_path; /* The path from HERE up to the root. */
+ const char *rest; /* The portion of PATH we haven't traversed yet. */
+ const char *canon_path = svn_fs__canonicalize_abspath(path, pool);
+ const char *path_so_far = "/";
+
+ /* Make a parent_path item for the root node, using its own current
+ copy id. */
+ SVN_ERR(root_node(&here, root, trail, pool));
+ parent_path = make_parent_path(here, 0, 0, pool);
+ parent_path->copy_inherit = copy_id_inherit_self;
+
+ rest = canon_path + 1; /* skip the leading '/', it saves in iteration */
+
+ /* Whenever we are at the top of this loop:
+ - HERE is our current directory,
+ - ID is the node revision ID of HERE,
+ - REST is the path we're going to find in HERE, and
+ - PARENT_PATH includes HERE and all its parents. */
+ for (;;)
+ {
+ const char *next;
+ char *entry;
+ dag_node_t *child;
+
+ /* Parse out the next entry from the path. */
+ entry = svn_fs__next_entry_name(&next, rest, pool);
+
+ /* Calculate the path traversed thus far. */
+ path_so_far = svn_fspath__join(path_so_far, entry, pool);
+
+ if (*entry == '\0')
+ {
+ /* Given the behavior of svn_fs__next_entry_name(), this
+ happens when the path either starts or ends with a slash.
+ In either case, we stay put: the current directory stays
+ the same, and we add nothing to the parent path. */
+ child = here;
+ }
+ else
+ {
+ copy_id_inherit_t inherit;
+ const char *copy_path = NULL;
+ svn_error_t *err = SVN_NO_ERROR;
+ dag_node_t *cached_node;
+
+ /* If we found a directory entry, follow it. First, we
+ check our node cache, and, failing that, we hit the DAG
+ layer. */
+ cached_node = dag_node_cache_get(root, path_so_far, pool);
+ if (cached_node)
+ child = cached_node;
+ else
+ err = svn_fs_base__dag_open(&child, here, entry, trail, pool);
+
+ /* "file not found" requires special handling. */
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ /* If this was the last path component, and the caller
+ said it was optional, then don't return an error;
+ just put a NULL node pointer in the path. */
+
+ svn_error_clear(err);
+
+ if ((flags & open_path_last_optional)
+ && (! next || *next == '\0'))
+ {
+ parent_path = make_parent_path(NULL, entry, parent_path,
+ pool);
+ break;
+ }
+ else
+ {
+ /* Build a better error message than svn_fs_base__dag_open
+ can provide, giving the root and full path name. */
+ return SVN_FS__NOT_FOUND(root, path);
+ }
+ }
+
+ /* Other errors we return normally. */
+ SVN_ERR(err);
+
+ /* Now, make a parent_path item for CHILD. */
+ parent_path = make_parent_path(child, entry, parent_path, pool);
+ if (txn_id)
+ {
+ SVN_ERR(get_copy_inheritance(&inherit, &copy_path,
+ fs, parent_path, txn_id,
+ trail, pool));
+ parent_path->copy_inherit = inherit;
+ parent_path->copy_src_path = apr_pstrdup(pool, copy_path);
+ }
+
+ /* Cache the node we found (if it wasn't already cached). */
+ if (! cached_node)
+ dag_node_cache_set(root, path_so_far, child);
+ }
+
+ /* Are we finished traversing the path? */
+ if (! next)
+ break;
+
+ /* The path isn't finished yet; we'd better be in a directory. */
+ if (svn_fs_base__dag_node_kind(child) != svn_node_dir)
+ SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far),
+ apr_psprintf(pool, _("Failure opening '%s'"), path));
+
+ rest = next;
+ here = child;
+ }
+
+ *parent_path_p = parent_path;
+ return SVN_NO_ERROR;
+}
+
+
+/* Make the node referred to by PARENT_PATH mutable, if it isn't
+ already, as part of TRAIL. ROOT must be the root from which
+ PARENT_PATH descends. Clone any parent directories as needed.
+ Adjust the dag nodes in PARENT_PATH to refer to the clones. Use
+ ERROR_PATH in error messages. */
+static svn_error_t *
+make_path_mutable(svn_fs_root_t *root,
+ parent_path_t *parent_path,
+ const char *error_path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *cloned_node;
+ const char *txn_id = root->txn;
+ svn_fs_t *fs = root->fs;
+
+ /* Is the node mutable already? */
+ if (svn_fs_base__dag_check_mutable(parent_path->node, txn_id))
+ return SVN_NO_ERROR;
+
+ /* Are we trying to clone the root, or somebody's child node? */
+ if (parent_path->parent)
+ {
+ const svn_fs_id_t *parent_id;
+ const svn_fs_id_t *node_id = svn_fs_base__dag_get_id(parent_path->node);
+ const char *copy_id = NULL;
+ const char *copy_src_path = parent_path->copy_src_path;
+ copy_id_inherit_t inherit = parent_path->copy_inherit;
+ const char *clone_path;
+
+ /* We're trying to clone somebody's child. Make sure our parent
+ is mutable. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent,
+ error_path, trail, pool));
+
+ switch (inherit)
+ {
+ case copy_id_inherit_parent:
+ parent_id = svn_fs_base__dag_get_id(parent_path->parent->node);
+ copy_id = svn_fs_base__id_copy_id(parent_id);
+ break;
+
+ case copy_id_inherit_new:
+ SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, pool));
+ break;
+
+ case copy_id_inherit_self:
+ copy_id = NULL;
+ break;
+
+ case copy_id_inherit_unknown:
+ default:
+ SVN_ERR_MALFUNCTION(); /* uh-oh -- somebody didn't calculate copy-ID
+ inheritance data. */
+ }
+
+ /* Now make this node mutable. */
+ clone_path = parent_path_path(parent_path->parent, pool);
+ SVN_ERR(svn_fs_base__dag_clone_child(&cloned_node,
+ parent_path->parent->node,
+ clone_path,
+ parent_path->entry,
+ copy_id, txn_id,
+ trail, pool));
+
+ /* If we just created a brand new copy ID, we need to store a
+ `copies' table entry for it, as well as a notation in the
+ transaction that should this transaction be terminated, our
+ new copy needs to be removed. */
+ if (inherit == copy_id_inherit_new)
+ {
+ const svn_fs_id_t *new_node_id =
+ svn_fs_base__dag_get_id(cloned_node);
+ SVN_ERR(svn_fs_bdb__create_copy(fs, copy_id, copy_src_path,
+ svn_fs_base__id_txn_id(node_id),
+ new_node_id,
+ copy_kind_soft, trail, pool));
+ SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id,
+ trail, pool));
+ }
+ }
+ else
+ {
+ /* We're trying to clone the root directory. */
+ SVN_ERR(mutable_root_node(&cloned_node, root, error_path, trail, pool));
+ }
+
+ /* Update the PARENT_PATH link to refer to the clone. */
+ parent_path->node = cloned_node;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Walk up PARENT_PATH to the root of the tree, adjusting each node's
+ mergeinfo count by COUNT_DELTA as part of Subversion transaction
+ TXN_ID and TRAIL. Use POOL for allocations. */
+static svn_error_t *
+adjust_parent_mergeinfo_counts(parent_path_t *parent_path,
+ apr_int64_t count_delta,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool;
+ parent_path_t *pp = parent_path;
+
+ if (count_delta == 0)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(pool);
+
+ while (pp)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_fs_base__dag_adjust_mergeinfo_count(pp->node, count_delta,
+ txn_id, trail,
+ iterpool));
+ pp = pp->parent;
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Open the node identified by PATH in ROOT, as part of TRAIL. Set
+ *DAG_NODE_P to the node we find, allocated in TRAIL->pool. Return
+ the error SVN_ERR_FS_NOT_FOUND if this node doesn't exist. */
+static svn_error_t *
+get_dag(dag_node_t **dag_node_p,
+ svn_fs_root_t *root,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ parent_path_t *parent_path;
+ dag_node_t *node = NULL;
+
+ /* Canonicalize the input PATH. */
+ path = svn_fs__canonicalize_abspath(path, pool);
+
+ /* If ROOT is a revision root, we'll look for the DAG in our cache. */
+ node = dag_node_cache_get(root, path, pool);
+ if (! node)
+ {
+ /* Call open_path with no flags, as we want this to return an error
+ if the node for which we are searching doesn't exist. */
+ SVN_ERR(open_path(&parent_path, root, path, 0, NULL, trail, pool));
+ node = parent_path->node;
+
+ /* No need to cache our find -- open_path() will do that for us. */
+ }
+
+ *dag_node_p = node;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Populating the `changes' table. */
+
+/* Add a change to the changes table in FS, keyed on transaction id
+ TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
+ PATH (whose node revision id is--or was, in the case of a
+ deletion--NODEREV_ID), and optionally that TEXT_MODs or PROP_MODs
+ occurred. Do all this as part of TRAIL. */
+static svn_error_t *
+add_change(svn_fs_t *fs,
+ const char *txn_id,
+ const char *path,
+ const svn_fs_id_t *noderev_id,
+ svn_fs_path_change_kind_t change_kind,
+ svn_boolean_t text_mod,
+ svn_boolean_t prop_mod,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ change_t change;
+ change.path = svn_fs__canonicalize_abspath(path, pool);
+ change.noderev_id = noderev_id;
+ change.kind = change_kind;
+ change.text_mod = text_mod;
+ change.prop_mod = prop_mod;
+ return svn_fs_bdb__changes_add(fs, txn_id, &change, trail, pool);
+}
+
+
+
+/* Generic node operations. */
+
+
+struct node_id_args {
+ const svn_fs_id_t **id_p;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_node_id(void *baton, trail_t *trail)
+{
+ struct node_id_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ *args->id_p = svn_fs_base__id_copy(svn_fs_base__dag_get_id(node),
+ trail->pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_node_id(const svn_fs_id_t **id_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ base_root_data_t *brd = root->fsap_data;
+
+ if (! root->is_txn_root
+ && (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0'))))
+ {
+ /* Optimize the case where we don't need any db access at all.
+ The root directory ("" or "/") node is stored in the
+ svn_fs_root_t object, and never changes when it's a revision
+ root, so we can just reach in and grab it directly. */
+ *id_p = svn_fs_base__id_copy(svn_fs_base__dag_get_id(brd->root_dir),
+ pool);
+ }
+ else
+ {
+ const svn_fs_id_t *id;
+ struct node_id_args args;
+
+ args.id_p = &id;
+ args.root = root;
+ args.path = path;
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_id, &args,
+ FALSE, pool));
+ *id_p = id;
+ }
+ return SVN_NO_ERROR;
+}
+
+
+struct node_created_rev_args {
+ svn_revnum_t revision;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_node_created_rev(void *baton, trail_t *trail)
+{
+ struct node_created_rev_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ return svn_fs_base__dag_get_revision(&(args->revision), node,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_node_created_rev(svn_revnum_t *revision,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct node_created_rev_args args;
+
+ args.revision = SVN_INVALID_REVNUM;
+ args.root = root;
+ args.path = path;
+ SVN_ERR(svn_fs_base__retry_txn
+ (root->fs, txn_body_node_created_rev, &args, TRUE, pool));
+ *revision = args.revision;
+ return SVN_NO_ERROR;
+}
+
+
+struct node_created_path_args {
+ const char **created_path;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_node_created_path(void *baton, trail_t *trail)
+{
+ struct node_created_path_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ *args->created_path = svn_fs_base__dag_get_created_path(node);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_node_created_path(const char **created_path,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct node_created_path_args args;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ args.created_path = created_path;
+ args.root = root;
+ args.path = path;
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_created_path, &args,
+ FALSE, scratch_pool));
+ if (*created_path)
+ *created_path = apr_pstrdup(pool, *created_path);
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+struct node_kind_args {
+ const svn_fs_id_t *id;
+ svn_node_kind_t kind; /* OUT parameter */
+};
+
+
+static svn_error_t *
+txn_body_node_kind(void *baton, trail_t *trail)
+{
+ struct node_kind_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id,
+ trail, trail->pool));
+ args->kind = svn_fs_base__dag_node_kind(node);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+node_kind(svn_node_kind_t *kind_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct node_kind_args args;
+ const svn_fs_id_t *node_id;
+
+ /* Get the node id. */
+ SVN_ERR(base_node_id(&node_id, root, path, pool));
+
+ /* Use the node id to get the real kind. */
+ args.id = node_id;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_kind, &args,
+ TRUE, pool));
+
+ *kind_p = args.kind;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_check_path(svn_node_kind_t *kind_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = node_kind(kind_p, root, path, pool);
+ if (err &&
+ ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ *kind_p = svn_node_none;
+ }
+
+ return svn_error_trace(err);
+}
+
+
+struct node_prop_args
+{
+ svn_string_t **value_p;
+ svn_fs_root_t *root;
+ const char *path;
+ const char *propname;
+};
+
+
+static svn_error_t *
+txn_body_node_prop(void *baton,
+ trail_t *trail)
+{
+ struct node_prop_args *args = baton;
+ dag_node_t *node;
+ apr_hash_t *proplist;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, node,
+ trail, trail->pool));
+ *(args->value_p) = NULL;
+ if (proplist)
+ *(args->value_p) = svn_hash_gets(proplist, args->propname);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_node_prop(svn_string_t **value_p,
+ svn_fs_root_t *root,
+ const char *path,
+ const char *propname,
+ apr_pool_t *pool)
+{
+ struct node_prop_args args;
+ svn_string_t *value;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ args.value_p = &value;
+ args.root = root;
+ args.path = path;
+ args.propname = propname;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_prop, &args,
+ FALSE, scratch_pool));
+ *value_p = value ? svn_string_dup(value, pool) : NULL;
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+struct node_proplist_args {
+ apr_hash_t **table_p;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_node_proplist(void *baton, trail_t *trail)
+{
+ struct node_proplist_args *args = baton;
+ dag_node_t *node;
+ apr_hash_t *proplist;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, node,
+ trail, trail->pool));
+ *args->table_p = proplist ? proplist : apr_hash_make(trail->pool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_node_proplist(apr_hash_t **table_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_hash_t *table;
+ struct node_proplist_args args;
+
+ args.table_p = &table;
+ args.root = root;
+ args.path = path;
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_proplist, &args,
+ FALSE, pool));
+
+ *table_p = table;
+ return SVN_NO_ERROR;
+}
+
+
+struct change_node_prop_args {
+ svn_fs_root_t *root;
+ const char *path;
+ const char *name;
+ const svn_string_t *value;
+};
+
+
+static svn_error_t *
+txn_body_change_node_prop(void *baton,
+ trail_t *trail)
+{
+ struct change_node_prop_args *args = baton;
+ parent_path_t *parent_path;
+ apr_hash_t *proplist;
+ const char *txn_id = args->root->txn;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ SVN_ERR(open_path(&parent_path, args->root, args->path, 0, txn_id,
+ trail, trail->pool));
+
+ /* Check to see if path is locked; if so, check that we can use it.
+ Notice that we're doing this non-recursively, regardless of node kind. */
+ if (args->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_base__allow_locked_operation
+ (args->path, FALSE, trail, trail->pool));
+
+ SVN_ERR(make_path_mutable(args->root, parent_path, args->path,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, parent_path->node,
+ trail, trail->pool));
+
+ /* If there's no proplist, but we're just deleting a property, exit now. */
+ if ((! proplist) && (! args->value))
+ return SVN_NO_ERROR;
+
+ /* Now, if there's no proplist, we know we need to make one. */
+ if (! proplist)
+ proplist = apr_hash_make(trail->pool);
+
+ /* Set the property. */
+ svn_hash_sets(proplist, args->name, args->value);
+
+ /* Overwrite the node's proplist. */
+ SVN_ERR(svn_fs_base__dag_set_proplist(parent_path->node, proplist,
+ txn_id, trail, trail->pool));
+
+ /* If this was a change to the mergeinfo property, and our version
+ of the filesystem cares, we have some extra recording to do.
+
+ ### If the format *doesn't* support mergeinfo recording, should
+ ### we fuss about attempts to change the svn:mergeinfo property
+ ### in any way save to delete it? */
+ if ((bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ && (strcmp(args->name, SVN_PROP_MERGEINFO) == 0))
+ {
+ svn_boolean_t had_mergeinfo, has_mergeinfo = args->value != NULL;
+
+ /* First, note on our node that it has mergeinfo. */
+ SVN_ERR(svn_fs_base__dag_set_has_mergeinfo(parent_path->node,
+ has_mergeinfo,
+ &had_mergeinfo, txn_id,
+ trail, trail->pool));
+
+ /* If this is a change from the old state, we need to update our
+ node's parents' mergeinfo counts by a factor of 1. */
+ if (parent_path->parent && ((! had_mergeinfo) != (! has_mergeinfo)))
+ SVN_ERR(adjust_parent_mergeinfo_counts(parent_path->parent,
+ has_mergeinfo ? 1 : -1,
+ txn_id, trail, trail->pool));
+ }
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(args->root->fs, txn_id,
+ args->path, svn_fs_base__dag_get_id(parent_path->node),
+ svn_fs_path_change_modify, FALSE, TRUE, trail,
+ trail->pool);
+}
+
+
+static svn_error_t *
+base_change_node_prop(svn_fs_root_t *root,
+ const char *path,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct change_node_prop_args args;
+
+ if (! root->is_txn_root)
+ return SVN_FS__NOT_TXN(root);
+
+ args.root = root;
+ args.path = path;
+ args.name = name;
+ args.value = value;
+ return svn_fs_base__retry_txn(root->fs, txn_body_change_node_prop, &args,
+ TRUE, pool);
+}
+
+
+struct things_changed_args
+{
+ svn_boolean_t *changed_p;
+ svn_fs_root_t *root1;
+ svn_fs_root_t *root2;
+ const char *path1;
+ const char *path2;
+ apr_pool_t *pool;
+};
+
+
+static svn_error_t *
+txn_body_props_changed(void *baton, trail_t *trail)
+{
+ struct things_changed_args *args = baton;
+ dag_node_t *node1, *node2;
+
+ SVN_ERR(get_dag(&node1, args->root1, args->path1, trail, trail->pool));
+ SVN_ERR(get_dag(&node2, args->root2, args->path2, trail, trail->pool));
+ return svn_fs_base__things_different(args->changed_p, NULL,
+ node1, node2, trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_props_changed(svn_boolean_t *changed_p,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ struct things_changed_args args;
+
+ /* Check that roots are in the same fs. */
+ if (root1->fs != root2->fs)
+ return svn_error_create
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Cannot compare property value between two different filesystems"));
+
+ args.root1 = root1;
+ args.root2 = root2;
+ args.path1 = path1;
+ args.path2 = path2;
+ args.changed_p = changed_p;
+ args.pool = pool;
+
+ return svn_fs_base__retry_txn(root1->fs, txn_body_props_changed, &args,
+ TRUE, pool);
+}
+
+
+
+/* Miscellaneous table handling */
+
+struct miscellaneous_set_args
+{
+ const char *key;
+ const char *val;
+};
+
+static svn_error_t *
+txn_body_miscellaneous_set(void *baton, trail_t *trail)
+{
+ struct miscellaneous_set_args *msa = baton;
+
+ return svn_fs_bdb__miscellaneous_set(trail->fs, msa->key, msa->val, trail,
+ trail->pool);
+}
+
+svn_error_t *
+svn_fs_base__miscellaneous_set(svn_fs_t *fs,
+ const char *key,
+ const char *val,
+ apr_pool_t *pool)
+{
+ struct miscellaneous_set_args msa;
+ msa.key = key;
+ msa.val = val;
+
+ return svn_fs_base__retry_txn(fs, txn_body_miscellaneous_set, &msa,
+ TRUE, pool);
+}
+
+struct miscellaneous_get_args
+{
+ const char *key;
+ const char **val;
+};
+
+static svn_error_t *
+txn_body_miscellaneous_get(void *baton, trail_t *trail)
+{
+ struct miscellaneous_get_args *mga = baton;
+ return svn_fs_bdb__miscellaneous_get(mga->val, trail->fs, mga->key, trail,
+ trail->pool);
+}
+
+svn_error_t *
+svn_fs_base__miscellaneous_get(const char **val,
+ svn_fs_t *fs,
+ const char *key,
+ apr_pool_t *pool)
+{
+ struct miscellaneous_get_args mga;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ mga.key = key;
+ mga.val = val;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_miscellaneous_get, &mga,
+ FALSE, scratch_pool));
+ if (*val)
+ *val = apr_pstrdup(pool, *val);
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Getting a directory's entries */
+
+
+struct dir_entries_args
+{
+ apr_hash_t **table_p;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+/* *(BATON->table_p) will never be NULL on successful return */
+static svn_error_t *
+txn_body_dir_entries(void *baton,
+ trail_t *trail)
+{
+ struct dir_entries_args *args = baton;
+ dag_node_t *node;
+ apr_hash_t *entries;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+
+ /* Get the entries for PARENT_PATH. */
+ SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, trail->pool));
+
+ /* Potentially initialize the return value to an empty hash. */
+ *args->table_p = entries ? entries : apr_hash_make(trail->pool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_dir_entries(apr_hash_t **table_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct dir_entries_args args;
+ apr_pool_t *iterpool;
+ apr_hash_t *table;
+ svn_fs_t *fs = root->fs;
+ apr_hash_index_t *hi;
+
+ args.table_p = &table;
+ args.root = root;
+ args.path = path;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_dir_entries, &args,
+ FALSE, pool));
+
+ iterpool = svn_pool_create(pool);
+
+ /* Add in the kind data. */
+ for (hi = apr_hash_first(pool, table); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *entry;
+ struct node_kind_args nk_args;
+ void *val;
+
+ svn_pool_clear(iterpool);
+
+ /* KEY will be the entry name in ancestor (about which we
+ simply don't care), VAL the dirent. */
+ apr_hash_this(hi, NULL, NULL, &val);
+ entry = val;
+ nk_args.id = entry->id;
+
+ /* We don't need to have the retry function destroy the trail
+ pool because we're already doing that via the use of an
+ iteration pool. */
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_node_kind, &nk_args,
+ FALSE, iterpool));
+ entry->kind = nk_args.kind;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ *table_p = table;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Merges and commits. */
+
+
+struct deltify_committed_args
+{
+ svn_fs_t *fs; /* the filesystem */
+ svn_revnum_t rev; /* revision just committed */
+ const char *txn_id; /* transaction just committed */
+};
+
+
+struct txn_deltify_args
+{
+ /* The transaction ID whose nodes are being deltified. */
+ const char *txn_id;
+
+ /* The target is what we're deltifying. */
+ const svn_fs_id_t *tgt_id;
+
+ /* The base is what we're deltifying against. It's not necessarily
+ the "next" revision of the node; skip deltas mean we sometimes
+ deltify against a successor many generations away. This may be
+ NULL, in which case we'll avoid deltification and simply index
+ TGT_ID's data checksum. */
+ const svn_fs_id_t *base_id;
+
+ /* We only deltify props for directories.
+ ### Didn't we try removing this horrid little optimization once?
+ ### What was the result? I would have thought that skip deltas
+ ### mean directory undeltification is cheap enough now. */
+ svn_boolean_t is_dir;
+};
+
+
+static svn_error_t *
+txn_body_txn_deltify(void *baton, trail_t *trail)
+{
+ struct txn_deltify_args *args = baton;
+ dag_node_t *tgt_node, *base_node;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ SVN_ERR(svn_fs_base__dag_get_node(&tgt_node, trail->fs, args->tgt_id,
+ trail, trail->pool));
+ /* If we have something to deltify against, do so. */
+ if (args->base_id)
+ {
+ SVN_ERR(svn_fs_base__dag_get_node(&base_node, trail->fs, args->base_id,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_deltify(tgt_node, base_node, args->is_dir,
+ args->txn_id, trail, trail->pool));
+ }
+
+ /* If we support rep sharing, and this isn't a directory, record a
+ mapping of TGT_NODE's data checksum to its representation key. */
+ if (bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
+ SVN_ERR(svn_fs_base__dag_index_checksums(tgt_node, trail, trail->pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+struct txn_pred_count_args
+{
+ const svn_fs_id_t *id;
+ int pred_count;
+};
+
+
+static svn_error_t *
+txn_body_pred_count(void *baton, trail_t *trail)
+{
+ node_revision_t *noderev;
+ struct txn_pred_count_args *args = baton;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, trail->fs,
+ args->id, trail, trail->pool));
+ args->pred_count = noderev->predecessor_count;
+ return SVN_NO_ERROR;
+}
+
+
+struct txn_pred_id_args
+{
+ const svn_fs_id_t *id; /* The node id whose predecessor we want. */
+ const svn_fs_id_t *pred_id; /* The returned predecessor id. */
+ apr_pool_t *pool; /* The pool in which to allocate pred_id. */
+};
+
+
+static svn_error_t *
+txn_body_pred_id(void *baton, trail_t *trail)
+{
+ node_revision_t *nr;
+ struct txn_pred_id_args *args = baton;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&nr, trail->fs, args->id,
+ trail, trail->pool));
+ if (nr->predecessor_id)
+ args->pred_id = svn_fs_base__id_copy(nr->predecessor_id, args->pool);
+ else
+ args->pred_id = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Deltify PATH in ROOT's predecessor iff PATH is mutable under TXN_ID
+ in FS. If PATH is a mutable directory, recurse.
+
+ NODE_ID is the node revision ID for PATH in ROOT, or NULL if that
+ value isn't known. KIND is the node kind for PATH in ROOT, or
+ svn_node_unknown is the kind isn't known.
+
+ Use POOL for necessary allocations. */
+static svn_error_t *
+deltify_mutable(svn_fs_t *fs,
+ svn_fs_root_t *root,
+ const char *path,
+ const svn_fs_id_t *node_id,
+ svn_node_kind_t kind,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *id = node_id;
+ apr_hash_t *entries = NULL;
+ struct txn_deltify_args td_args;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* Get the ID for PATH under ROOT if it wasn't provided. */
+ if (! node_id)
+ SVN_ERR(base_node_id(&id, root, path, pool));
+
+ /* Check for mutability. Not mutable? Go no further. This is safe
+ to do because for items in the tree to be mutable, their parent
+ dirs must also be mutable. Therefore, if a directory is not
+ mutable under TXN_ID, its children cannot be. */
+ if (strcmp(svn_fs_base__id_txn_id(id), txn_id))
+ return SVN_NO_ERROR;
+
+ /* Is this a directory? */
+ if (kind == svn_node_unknown)
+ SVN_ERR(base_check_path(&kind, root, path, pool));
+
+ /* If this is a directory, read its entries. */
+ if (kind == svn_node_dir)
+ SVN_ERR(base_dir_entries(&entries, root, path, pool));
+
+ /* If there are entries, recurse on 'em. */
+ if (entries)
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ /* KEY will be the entry name, VAL the dirent */
+ const void *key;
+ void *val;
+ svn_fs_dirent_t *entry;
+ svn_pool_clear(subpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ entry = val;
+ SVN_ERR(deltify_mutable(fs, root,
+ svn_fspath__join(path, key, subpool),
+ entry->id, entry->kind, txn_id, subpool));
+ }
+
+ svn_pool_destroy(subpool);
+ }
+
+ /* Index ID's data checksum. */
+ td_args.txn_id = txn_id;
+ td_args.tgt_id = id;
+ td_args.base_id = NULL;
+ td_args.is_dir = (kind == svn_node_dir);
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_deltify, &td_args,
+ TRUE, pool));
+
+ /* Finally, deltify old data against this node. */
+ {
+ /* Prior to 1.6, we use the following algorithm to deltify nodes:
+
+ Redeltify predecessor node-revisions of the one we added. The
+ idea is to require at most 2*lg(N) deltas to be applied to get
+ to any node-revision in a chain of N predecessors. We do this
+ using a technique derived from skip lists:
+
+ - Always redeltify the immediate parent
+
+ - If the number of predecessors is divisible by 2,
+ redeltify the revision two predecessors back
+
+ - If the number of predecessors is divisible by 4,
+ redeltify the revision four predecessors back
+
+ ... and so on.
+
+ That's the theory, anyway. Unfortunately, if we strictly
+ follow that theory we get a bunch of overhead up front and no
+ great benefit until the number of predecessors gets large. So,
+ stop at redeltifying the parent if the number of predecessors
+ is less than 32, and also skip the second level (redeltifying
+ two predecessors back), since that doesn't help much. Also,
+ don't redeltify the oldest node-revision; it's potentially
+ expensive and doesn't help retrieve any other revision.
+ (Retrieving the oldest node-revision will still be fast, just
+ not as blindingly so.)
+
+ For 1.6 and beyond, we just deltify the current node against its
+ predecessors, using skip deltas similar to the way FSFS does. */
+
+ int pred_count;
+ const svn_fs_id_t *pred_id;
+ struct txn_pred_count_args tpc_args;
+ apr_pool_t *subpools[2];
+ int active_subpool = 0;
+ svn_revnum_t forward_delta_rev = 0;
+
+ tpc_args.id = id;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_count, &tpc_args,
+ TRUE, pool));
+ pred_count = tpc_args.pred_count;
+
+ /* If nothing to deltify, then we're done. */
+ if (pred_count == 0)
+ return SVN_NO_ERROR;
+
+ subpools[0] = svn_pool_create(pool);
+ subpools[1] = svn_pool_create(pool);
+
+ /* If we support the 'miscellaneous' table, check it to see if
+ there is a point in time before which we don't want to do
+ deltification. */
+ /* ### FIXME: I think this is an unnecessary restriction. We
+ ### should be able to do something meaningful for most
+ ### deltification requests -- what that is depends on the
+ ### directory of the deltas for that revision, though. */
+ if (bfd->format >= SVN_FS_BASE__MIN_MISCELLANY_FORMAT)
+ {
+ const char *val;
+ SVN_ERR(svn_fs_base__miscellaneous_get
+ (&val, fs, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE, pool));
+ if (val)
+ SVN_ERR(svn_revnum_parse(&forward_delta_rev, val, NULL));
+ }
+
+ if (bfd->format >= SVN_FS_BASE__MIN_FORWARD_DELTAS_FORMAT
+ && forward_delta_rev <= root->rev)
+ {
+ /**** FORWARD DELTA STORAGE ****/
+
+ /* Decide which predecessor to deltify against. Flip the rightmost '1'
+ bit of the predecessor count to determine which file rev (counting
+ from 0) we want to use. (To see why count & (count - 1) unsets the
+ rightmost set bit, think about how you decrement a binary number. */
+ pred_count = pred_count & (pred_count - 1);
+
+ /* Walk back a number of predecessors equal to the difference between
+ pred_count and the original predecessor count. (For example, if
+ the node has ten predecessors and we want the eighth node, walk back
+ two predecessors. */
+ pred_id = id;
+
+ /* We need to use two alternating pools because the id used in the
+ call to txn_body_pred_id is allocated by the previous inner
+ loop iteration. If we would clear the pool each iteration we
+ would free the previous result. */
+ while ((pred_count++) < tpc_args.pred_count)
+ {
+ struct txn_pred_id_args tpi_args;
+
+ active_subpool = !active_subpool;
+ svn_pool_clear(subpools[active_subpool]);
+
+ tpi_args.id = pred_id;
+ tpi_args.pool = subpools[active_subpool];
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_id, &tpi_args,
+ FALSE, subpools[active_subpool]));
+ pred_id = tpi_args.pred_id;
+
+ if (pred_id == NULL)
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: faulty predecessor count"));
+
+ }
+
+ /* Finally, do the deltification. */
+ td_args.txn_id = txn_id;
+ td_args.tgt_id = id;
+ td_args.base_id = pred_id;
+ td_args.is_dir = (kind == svn_node_dir);
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_deltify, &td_args,
+ TRUE, subpools[active_subpool]));
+ }
+ else
+ {
+ int nlevels, lev, count;
+
+ /**** REVERSE DELTA STORAGE ****/
+
+ /* Decide how many predecessors to redeltify. To save overhead,
+ don't redeltify anything but the immediate predecessor if there
+ are less than 32 predecessors. */
+ nlevels = 1;
+ if (pred_count >= 32)
+ {
+ while (pred_count % 2 == 0)
+ {
+ pred_count /= 2;
+ nlevels++;
+ }
+
+ /* Don't redeltify the oldest revision. */
+ if (1 << (nlevels - 1) == pred_count)
+ nlevels--;
+ }
+
+ /* Redeltify the desired number of predecessors. */
+ count = 0;
+ pred_id = id;
+
+ /* We need to use two alternating pools because the id used in the
+ call to txn_body_pred_id is allocated by the previous inner
+ loop iteration. If we would clear the pool each iteration we
+ would free the previous result. */
+ for (lev = 0; lev < nlevels; lev++)
+ {
+ /* To save overhead, skip the second level (that is, never
+ redeltify the node-revision two predecessors back). */
+ if (lev == 1)
+ continue;
+
+ /* Note that COUNT is not reset between levels, and neither is
+ PREDNODE; we just keep counting from where we were up to
+ where we're supposed to get. */
+ while (count < (1 << lev))
+ {
+ struct txn_pred_id_args tpi_args;
+
+ active_subpool = !active_subpool;
+ svn_pool_clear(subpools[active_subpool]);
+
+ tpi_args.id = pred_id;
+ tpi_args.pool = subpools[active_subpool];
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_id,
+ &tpi_args, FALSE,
+ subpools[active_subpool]));
+ pred_id = tpi_args.pred_id;
+
+ if (pred_id == NULL)
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: faulty predecessor count"));
+
+ count++;
+ }
+
+ /* Finally, do the deltification. */
+ td_args.txn_id = NULL; /* Don't require mutable reps */
+ td_args.tgt_id = pred_id;
+ td_args.base_id = id;
+ td_args.is_dir = (kind == svn_node_dir);
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_deltify, &td_args,
+ TRUE, subpools[active_subpool]));
+
+ }
+ }
+
+ svn_pool_destroy(subpools[0]);
+ svn_pool_destroy(subpools[1]);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct get_root_args
+{
+ svn_fs_root_t *root;
+ dag_node_t *node;
+};
+
+
+/* Set ARGS->node to the root node of ARGS->root. */
+static svn_error_t *
+txn_body_get_root(void *baton, trail_t *trail)
+{
+ struct get_root_args *args = baton;
+ return get_dag(&(args->node), args->root, "", trail, trail->pool);
+}
+
+
+
+static svn_error_t *
+update_ancestry(svn_fs_t *fs,
+ const svn_fs_id_t *source_id,
+ const svn_fs_id_t *target_id,
+ const char *txn_id,
+ const char *target_path,
+ int source_pred_count,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ /* Set target's predecessor-id to source_id. */
+ if (strcmp(svn_fs_base__id_txn_id(target_id), txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Unexpected immutable node at '%s'"), target_path);
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, target_id,
+ trail, pool));
+ noderev->predecessor_id = source_id;
+ noderev->predecessor_count = source_pred_count;
+ if (noderev->predecessor_count != -1)
+ noderev->predecessor_count++;
+ return svn_fs_bdb__put_node_revision(fs, target_id, noderev, trail, pool);
+}
+
+
+/* Set the contents of CONFLICT_PATH to PATH, and return an
+ SVN_ERR_FS_CONFLICT error that indicates that there was a conflict
+ at PATH. Perform all allocations in POOL (except the allocation of
+ CONFLICT_PATH, which should be handled outside this function). */
+static svn_error_t *
+conflict_err(svn_stringbuf_t *conflict_path,
+ const char *path)
+{
+ svn_stringbuf_set(conflict_path, path);
+ return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
+ _("Conflict at '%s'"), path);
+}
+
+
+/* Merge changes between ANCESTOR and SOURCE into TARGET as part of
+ * TRAIL. ANCESTOR and TARGET must be distinct node revisions.
+ * TARGET_PATH should correspond to TARGET's full path in its
+ * filesystem, and is used for reporting conflict location.
+ *
+ * SOURCE, TARGET, and ANCESTOR are generally directories; this
+ * function recursively merges the directories' contents. If any are
+ * files, this function simply returns an error whenever SOURCE,
+ * TARGET, and ANCESTOR are all distinct node revisions.
+ *
+ * If there are differences between ANCESTOR and SOURCE that conflict
+ * with changes between ANCESTOR and TARGET, this function returns an
+ * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the
+ * conflicting node in TARGET, with TARGET_PATH prepended as a path.
+ *
+ * If there are no conflicting differences, CONFLICT_P is updated to
+ * the empty string.
+ *
+ * CONFLICT_P must point to a valid svn_stringbuf_t.
+ *
+ * Do any necessary temporary allocation in POOL.
+ */
+static svn_error_t *
+merge(svn_stringbuf_t *conflict_p,
+ const char *target_path,
+ dag_node_t *target,
+ dag_node_t *source,
+ dag_node_t *ancestor,
+ const char *txn_id,
+ apr_int64_t *mergeinfo_increment_out,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *source_id, *target_id, *ancestor_id;
+ apr_hash_t *s_entries, *t_entries, *a_entries;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ svn_fs_t *fs;
+ int pred_count;
+ apr_int64_t mergeinfo_increment = 0;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ /* Make sure everyone comes from the same filesystem. */
+ fs = svn_fs_base__dag_get_fs(ancestor);
+ if ((fs != svn_fs_base__dag_get_fs(source))
+ || (fs != svn_fs_base__dag_get_fs(target)))
+ {
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Bad merge; ancestor, source, and target not all in same fs"));
+ }
+
+ /* We have the same fs, now check it. */
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ source_id = svn_fs_base__dag_get_id(source);
+ target_id = svn_fs_base__dag_get_id(target);
+ ancestor_id = svn_fs_base__dag_get_id(ancestor);
+
+ /* It's improper to call this function with ancestor == target. */
+ if (svn_fs_base__id_eq(ancestor_id, target_id))
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(target_id, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Bad merge; target '%s' has id '%s', same as ancestor"),
+ target_path, id_str->data);
+ }
+
+ svn_stringbuf_setempty(conflict_p);
+
+ /* Base cases:
+ * Either no change made in source, or same change as made in target.
+ * Both mean nothing to merge here.
+ */
+ if (svn_fs_base__id_eq(ancestor_id, source_id)
+ || (svn_fs_base__id_eq(source_id, target_id)))
+ return SVN_NO_ERROR;
+
+ /* Else proceed, knowing all three are distinct node revisions.
+ *
+ * How to merge from this point:
+ *
+ * if (not all 3 are directories)
+ * {
+ * early exit with conflict;
+ * }
+ *
+ * // Property changes may only be made to up-to-date
+ * // directories, because once the client commits the prop
+ * // change, it bumps the directory's revision, and therefore
+ * // must be able to depend on there being no other changes to
+ * // that directory in the repository.
+ * if (target's property list differs from ancestor's)
+ * conflict;
+ *
+ * For each entry NAME in the directory ANCESTOR:
+ *
+ * Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
+ * the name within ANCESTOR, SOURCE, and TARGET respectively.
+ * (Possibly null if NAME does not exist in SOURCE or TARGET.)
+ *
+ * If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
+ * No changes were made to this entry while the transaction was in
+ * progress, so do nothing to the target.
+ *
+ * Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
+ * A change was made to this entry while the transaction was in
+ * process, but the transaction did not touch this entry. Replace
+ * TARGET-ENTRY with SOURCE-ENTRY.
+ *
+ * Else:
+ * Changes were made to this entry both within the transaction and
+ * to the repository while the transaction was in progress. They
+ * must be merged or declared to be in conflict.
+ *
+ * If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
+ * double delete; flag a conflict.
+ *
+ * If any of the three entries is of type file, declare a conflict.
+ *
+ * If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
+ * modification of ANCESTOR-ENTRY (determine by comparing the
+ * node-id fields), declare a conflict. A replacement is
+ * incompatible with a modification or other replacement--even
+ * an identical replacement.
+ *
+ * Direct modifications were made to the directory ANCESTOR-ENTRY
+ * in both SOURCE and TARGET. Recursively merge these
+ * modifications.
+ *
+ * For each leftover entry NAME in the directory SOURCE:
+ *
+ * If NAME exists in TARGET, declare a conflict. Even if SOURCE and
+ * TARGET are adding exactly the same thing, two additions are not
+ * auto-mergeable with each other.
+ *
+ * Add NAME to TARGET with the entry from SOURCE.
+ *
+ * Now that we are done merging the changes from SOURCE into the
+ * directory TARGET, update TARGET's predecessor to be SOURCE.
+ */
+
+ if ((svn_fs_base__dag_node_kind(source) != svn_node_dir)
+ || (svn_fs_base__dag_node_kind(target) != svn_node_dir)
+ || (svn_fs_base__dag_node_kind(ancestor) != svn_node_dir))
+ {
+ return conflict_err(conflict_p, target_path);
+ }
+
+
+ /* Possible early merge failure: if target and ancestor have
+ different property lists, then the merge should fail.
+ Propchanges can *only* be committed on an up-to-date directory.
+ ### TODO: see issue #418 about the inelegance of this.
+
+ Another possible, similar, early merge failure: if source and
+ ancestor have different property lists (meaning someone else
+ changed directory properties while our commit transaction was
+ happening), the merge should fail. See issue #2751.
+ */
+ {
+ node_revision_t *tgt_nr, *anc_nr, *src_nr;
+
+ /* Get node revisions for our id's. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&tgt_nr, fs, target_id,
+ trail, pool));
+ SVN_ERR(svn_fs_bdb__get_node_revision(&anc_nr, fs, ancestor_id,
+ trail, pool));
+ SVN_ERR(svn_fs_bdb__get_node_revision(&src_nr, fs, source_id,
+ trail, pool));
+
+ /* Now compare the prop-keys of the skels. Note that just because
+ the keys are different -doesn't- mean the proplists have
+ different contents. But merge() isn't concerned with contents;
+ it doesn't do a brute-force comparison on textual contents, so
+ it won't do that here either. Checking to see if the propkey
+ atoms are `equal' is enough. */
+ if (! svn_fs_base__same_keys(tgt_nr->prop_key, anc_nr->prop_key))
+ return conflict_err(conflict_p, target_path);
+ if (! svn_fs_base__same_keys(src_nr->prop_key, anc_nr->prop_key))
+ return conflict_err(conflict_p, target_path);
+ }
+
+ /* ### todo: it would be more efficient to simply check for a NULL
+ entries hash where necessary below than to allocate an empty hash
+ here, but another day, another day... */
+ SVN_ERR(svn_fs_base__dag_dir_entries(&s_entries, source, trail, pool));
+ if (! s_entries)
+ s_entries = apr_hash_make(pool);
+ SVN_ERR(svn_fs_base__dag_dir_entries(&t_entries, target, trail, pool));
+ if (! t_entries)
+ t_entries = apr_hash_make(pool);
+ SVN_ERR(svn_fs_base__dag_dir_entries(&a_entries, ancestor, trail, pool));
+ if (! a_entries)
+ a_entries = apr_hash_make(pool);
+
+ /* for each entry E in a_entries... */
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, a_entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *s_entry, *t_entry, *a_entry;
+
+ const void *key;
+ void *val;
+ apr_ssize_t klen;
+
+ svn_pool_clear(iterpool);
+
+ /* KEY will be the entry name in ancestor, VAL the dirent */
+ apr_hash_this(hi, &key, &klen, &val);
+ a_entry = val;
+
+ s_entry = apr_hash_get(s_entries, key, klen);
+ t_entry = apr_hash_get(t_entries, key, klen);
+
+ /* No changes were made to this entry while the transaction was
+ in progress, so do nothing to the target. */
+ if (s_entry && svn_fs_base__id_eq(a_entry->id, s_entry->id))
+ goto end;
+
+ /* A change was made to this entry while the transaction was in
+ process, but the transaction did not touch this entry. */
+ else if (t_entry && svn_fs_base__id_eq(a_entry->id, t_entry->id))
+ {
+ dag_node_t *t_ent_node;
+ apr_int64_t mergeinfo_start;
+ SVN_ERR(svn_fs_base__dag_get_node(&t_ent_node, fs,
+ t_entry->id, trail, iterpool));
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, &mergeinfo_start,
+ t_ent_node, trail,
+ iterpool));
+ mergeinfo_increment -= mergeinfo_start;
+
+ if (s_entry)
+ {
+ dag_node_t *s_ent_node;
+ apr_int64_t mergeinfo_end;
+ SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node, fs,
+ s_entry->id, trail,
+ iterpool));
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL,
+ &mergeinfo_end,
+ s_ent_node, trail,
+ iterpool));
+ mergeinfo_increment += mergeinfo_end;
+ SVN_ERR(svn_fs_base__dag_set_entry(target, key, s_entry->id,
+ txn_id, trail, iterpool));
+ }
+ else
+ {
+ SVN_ERR(svn_fs_base__dag_delete(target, key, txn_id,
+ trail, iterpool));
+ }
+ }
+
+ /* Changes were made to this entry both within the transaction
+ and to the repository while the transaction was in progress.
+ They must be merged or declared to be in conflict. */
+ else
+ {
+ dag_node_t *s_ent_node, *t_ent_node, *a_ent_node;
+ const char *new_tpath;
+ apr_int64_t sub_mergeinfo_increment;
+
+ /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
+ double delete; if one of them is null, that's a delete versus
+ a modification. In any of these cases, flag a conflict. */
+ if (s_entry == NULL || t_entry == NULL)
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ a_entry->name,
+ iterpool));
+
+ /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
+ modification of ANCESTOR-ENTRY, declare a conflict. */
+ if (strcmp(svn_fs_base__id_node_id(s_entry->id),
+ svn_fs_base__id_node_id(a_entry->id)) != 0
+ || strcmp(svn_fs_base__id_copy_id(s_entry->id),
+ svn_fs_base__id_copy_id(a_entry->id)) != 0
+ || strcmp(svn_fs_base__id_node_id(t_entry->id),
+ svn_fs_base__id_node_id(a_entry->id)) != 0
+ || strcmp(svn_fs_base__id_copy_id(t_entry->id),
+ svn_fs_base__id_copy_id(a_entry->id)) != 0)
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ a_entry->name,
+ iterpool));
+
+ /* Fetch the nodes for our entries. */
+ SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node, fs,
+ s_entry->id, trail, iterpool));
+ SVN_ERR(svn_fs_base__dag_get_node(&t_ent_node, fs,
+ t_entry->id, trail, iterpool));
+ SVN_ERR(svn_fs_base__dag_get_node(&a_ent_node, fs,
+ a_entry->id, trail, iterpool));
+
+ /* If any of the three entries is of type file, flag a conflict. */
+ if ((svn_fs_base__dag_node_kind(s_ent_node) == svn_node_file)
+ || (svn_fs_base__dag_node_kind(t_ent_node) == svn_node_file)
+ || (svn_fs_base__dag_node_kind(a_ent_node) == svn_node_file))
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ a_entry->name,
+ iterpool));
+
+ /* Direct modifications were made to the directory
+ ANCESTOR-ENTRY in both SOURCE and TARGET. Recursively
+ merge these modifications. */
+ new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool);
+ SVN_ERR(merge(conflict_p, new_tpath,
+ t_ent_node, s_ent_node, a_ent_node,
+ txn_id, &sub_mergeinfo_increment, trail, iterpool));
+ mergeinfo_increment += sub_mergeinfo_increment;
+ }
+
+ /* We've taken care of any possible implications E could have.
+ Remove it from source_entries, so it's easy later to loop
+ over all the source entries that didn't exist in
+ ancestor_entries. */
+ end:
+ apr_hash_set(s_entries, key, klen, NULL);
+ }
+
+ /* For each entry E in source but not in ancestor */
+ for (hi = apr_hash_first(pool, s_entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *s_entry, *t_entry;
+ const void *key;
+ void *val;
+ apr_ssize_t klen;
+ dag_node_t *s_ent_node;
+ apr_int64_t mergeinfo_s;
+
+ svn_pool_clear(iterpool);
+
+ apr_hash_this(hi, &key, &klen, &val);
+ s_entry = val;
+ t_entry = apr_hash_get(t_entries, key, klen);
+
+ /* If NAME exists in TARGET, declare a conflict. */
+ if (t_entry)
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ t_entry->name,
+ iterpool));
+
+ SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node, fs,
+ s_entry->id, trail, iterpool));
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, &mergeinfo_s,
+ s_ent_node, trail,
+ iterpool));
+ mergeinfo_increment += mergeinfo_s;
+ SVN_ERR(svn_fs_base__dag_set_entry
+ (target, s_entry->name, s_entry->id, txn_id, trail, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Now that TARGET has absorbed all of the history between ANCESTOR
+ and SOURCE, we can update its predecessor to point to SOURCE. */
+ SVN_ERR(svn_fs_base__dag_get_predecessor_count(&pred_count, source,
+ trail, pool));
+ SVN_ERR(update_ancestry(fs, source_id, target_id, txn_id, target_path,
+ pred_count, trail, pool));
+
+ /* Tweak mergeinfo data if our format supports it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ {
+ SVN_ERR(svn_fs_base__dag_adjust_mergeinfo_count(target,
+ mergeinfo_increment,
+ txn_id, trail, pool));
+ }
+
+ if (mergeinfo_increment_out)
+ *mergeinfo_increment_out = mergeinfo_increment;
+
+ return SVN_NO_ERROR;
+}
+
+
+struct merge_args
+{
+ /* The ancestor for the merge. If this is null, then TXN's base is
+ used as the ancestor for the merge. */
+ dag_node_t *ancestor_node;
+
+ /* This is the SOURCE node for the merge. It may not be null. */
+ dag_node_t *source_node;
+
+ /* This is the TARGET of the merge. It may not be null. If
+ ancestor_node above is null, then this txn's base is used as the
+ ancestor for the merge. */
+ svn_fs_txn_t *txn;
+
+ /* If a conflict results, this is updated to the path in the txn that
+ conflicted. It must point to a valid svn_stringbuf_t before calling
+ svn_fs_base__retry_txn, as this determines the pool used to allocate any
+ required memory. */
+ svn_stringbuf_t *conflict;
+};
+
+
+/* Merge changes between an ancestor and BATON->source_node into
+ BATON->txn. The ancestor is either BATON->ancestor_node, or if
+ that is null, BATON->txn's base node.
+
+ If the merge is successful, BATON->txn's base will become
+ BATON->source_node, and its root node will have a new ID, a
+ successor of BATON->source_node. */
+static svn_error_t *
+txn_body_merge(void *baton, trail_t *trail)
+{
+ struct merge_args *args = baton;
+ dag_node_t *source_node, *txn_root_node, *ancestor_node;
+ const svn_fs_id_t *source_id;
+ svn_fs_t *fs = args->txn->fs;
+ const char *txn_id = args->txn->id;
+
+ source_node = args->source_node;
+ ancestor_node = args->ancestor_node;
+ source_id = svn_fs_base__dag_get_id(source_node);
+
+ SVN_ERR(svn_fs_base__dag_txn_root(&txn_root_node, fs, txn_id,
+ trail, trail->pool));
+
+ if (ancestor_node == NULL)
+ {
+ SVN_ERR(svn_fs_base__dag_txn_base_root(&ancestor_node, fs,
+ txn_id, trail, trail->pool));
+ }
+
+ if (svn_fs_base__id_eq(svn_fs_base__dag_get_id(ancestor_node),
+ svn_fs_base__dag_get_id(txn_root_node)))
+ {
+ /* If no changes have been made in TXN since its current base,
+ then it can't conflict with any changes since that base. So
+ we just set *both* its base and root to source, making TXN
+ in effect a repeat of source. */
+
+ /* ### kff todo: this would, of course, be a mighty silly thing
+ for the caller to do, and we might want to consider whether
+ this response is really appropriate. */
+
+ SVN_ERR(svn_fs_base__set_txn_base(fs, txn_id, source_id,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__set_txn_root(fs, txn_id, source_id,
+ trail, trail->pool));
+ }
+ else
+ {
+ int pred_count;
+
+ SVN_ERR(merge(args->conflict, "/", txn_root_node, source_node,
+ ancestor_node, txn_id, NULL, trail, trail->pool));
+
+ SVN_ERR(svn_fs_base__dag_get_predecessor_count(&pred_count,
+ source_node, trail,
+ trail->pool));
+
+ /* After the merge, txn's new "ancestor" is now really the node
+ at source_id, so record that fact. Think of this as
+ ratcheting the txn forward in time, so it can't backslide and
+ forget the merging work that's already been done. */
+ SVN_ERR(update_ancestry(fs, source_id,
+ svn_fs_base__dag_get_id(txn_root_node),
+ txn_id, "/", pred_count, trail, trail->pool));
+ SVN_ERR(svn_fs_base__set_txn_base(fs, txn_id, source_id,
+ trail, trail->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Verify that there are registered with TRAIL->fs all the locks
+ necessary to permit all the changes associated with TXN_NAME. */
+static svn_error_t *
+verify_locks(const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_t *changes;
+ apr_hash_index_t *hi;
+ apr_array_header_t *changed_paths;
+ svn_stringbuf_t *last_recursed = NULL;
+ int i;
+
+ /* Fetch the changes for this transaction. */
+ SVN_ERR(svn_fs_bdb__changes_fetch(&changes, trail->fs, txn_name,
+ trail, pool));
+
+ /* Make an array of the changed paths, and sort them depth-first-ily. */
+ changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
+ sizeof(const char *));
+ for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_hash_this(hi, &key, NULL, NULL);
+ APR_ARRAY_PUSH(changed_paths, const char *) = key;
+ }
+ qsort(changed_paths->elts, changed_paths->nelts,
+ changed_paths->elt_size, svn_sort_compare_paths);
+
+ /* Now, traverse the array of changed paths, verify locks. Note
+ that if we need to do a recursive verification a path, we'll skip
+ over children of that path when we get to them. */
+ for (i = 0; i < changed_paths->nelts; i++)
+ {
+ const char *path;
+ svn_fs_path_change2_t *change;
+ svn_boolean_t recurse = TRUE;
+
+ svn_pool_clear(subpool);
+ path = APR_ARRAY_IDX(changed_paths, i, const char *);
+
+ /* If this path has already been verified as part of a recursive
+ check of one of its parents, no need to do it again. */
+ if (last_recursed
+ && svn_fspath__skip_ancestor(last_recursed->data, path))
+ continue;
+
+ /* Fetch the change associated with our path. */
+ change = svn_hash_gets(changes, path);
+
+ /* What does it mean to succeed at lock verification for a given
+ path? For an existing file or directory getting modified
+ (text, props), it means we hold the lock on the file or
+ directory. For paths being added or removed, we need to hold
+ the locks for that path and any children of that path.
+
+ WHEW! We have no reliable way to determine the node kind of
+ deleted items, but fortunately we are going to do a recursive
+ check on deleted paths regardless of their kind. */
+ if (change->change_kind == svn_fs_path_change_modify)
+ recurse = FALSE;
+ SVN_ERR(svn_fs_base__allow_locked_operation(path, recurse,
+ trail, subpool));
+
+ /* If we just did a recursive check, remember the path we
+ checked (so children can be skipped). */
+ if (recurse)
+ {
+ if (! last_recursed)
+ last_recursed = svn_stringbuf_create(path, pool);
+ else
+ svn_stringbuf_set(last_recursed, path);
+ }
+ }
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+struct commit_args
+{
+ svn_fs_txn_t *txn;
+ svn_revnum_t new_rev;
+};
+
+
+/* Commit ARGS->txn, setting ARGS->new_rev to the resulting new
+ * revision, if ARGS->txn is up-to-date with respect to the repository.
+ *
+ * Up-to-date means that ARGS->txn's base root is the same as the root
+ * of the youngest revision. If ARGS->txn is not up-to-date, the
+ * error SVN_ERR_FS_TXN_OUT_OF_DATE is returned, and the commit fails: no
+ * new revision is created, and ARGS->new_rev is not touched.
+ *
+ * If the commit succeeds, ARGS->txn is destroyed.
+ */
+static svn_error_t *
+txn_body_commit(void *baton, trail_t *trail)
+{
+ struct commit_args *args = baton;
+
+ svn_fs_txn_t *txn = args->txn;
+ svn_fs_t *fs = txn->fs;
+ const char *txn_name = txn->id;
+
+ svn_revnum_t youngest_rev;
+ const svn_fs_id_t *y_rev_root_id;
+ dag_node_t *txn_base_root_node;
+
+ /* Getting the youngest revision locks the revisions table until
+ this trail is done. */
+ SVN_ERR(svn_fs_bdb__youngest_rev(&youngest_rev, fs, trail, trail->pool));
+
+ /* If the root of the youngest revision is the same as txn's base,
+ then no further merging is necessary and we can commit. */
+ SVN_ERR(svn_fs_base__rev_get_root(&y_rev_root_id, fs, youngest_rev,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_txn_base_root(&txn_base_root_node, fs, txn_name,
+ trail, trail->pool));
+ /* ### kff todo: it seems weird to grab the ID for one, and the node
+ for the other. We can certainly do the comparison we need, but
+ it would be nice to grab the same type of information from the
+ start, instead of having to transform one of them. */
+ if (! svn_fs_base__id_eq(y_rev_root_id,
+ svn_fs_base__dag_get_id(txn_base_root_node)))
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(y_rev_root_id,
+ trail->pool);
+ return svn_error_createf
+ (SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
+ _("Transaction '%s' out-of-date with respect to revision '%s'"),
+ txn_name, id_str->data);
+ }
+
+ /* Locks may have been added (or stolen) between the calling of
+ previous svn_fs.h functions and svn_fs_commit_txn(), so we need
+ to re-examine every changed-path in the txn and re-verify all
+ discovered locks. */
+ SVN_ERR(verify_locks(txn_name, trail, trail->pool));
+
+ /* Else, commit the txn. */
+ return svn_fs_base__dag_commit_txn(&(args->new_rev), txn, trail,
+ trail->pool);
+}
+
+
+/* Note: it is acceptable for this function to call back into
+ top-level FS interfaces because it does not itself use trails. */
+svn_error_t *
+svn_fs_base__commit_txn(const char **conflict_p,
+ svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ /* How do commits work in Subversion?
+ *
+ * When you're ready to commit, here's what you have:
+ *
+ * 1. A transaction, with a mutable tree hanging off it.
+ * 2. A base revision, against which TXN_TREE was made.
+ * 3. A latest revision, which may be newer than the base rev.
+ *
+ * The problem is that if latest != base, then one can't simply
+ * attach the txn root as the root of the new revision, because that
+ * would lose all the changes between base and latest. It is also
+ * not acceptable to insist that base == latest; in a busy
+ * repository, commits happen too fast to insist that everyone keep
+ * their entire tree up-to-date at all times. Non-overlapping
+ * changes should not interfere with each other.
+ *
+ * The solution is to merge the changes between base and latest into
+ * the txn tree [see the function merge()]. The txn tree is the
+ * only one of the three trees that is mutable, so it has to be the
+ * one to adjust.
+ *
+ * You might have to adjust it more than once, if a new latest
+ * revision gets committed while you were merging in the previous
+ * one. For example:
+ *
+ * 1. Jane starts txn T, based at revision 6.
+ * 2. Someone commits (or already committed) revision 7.
+ * 3. Jane's starts merging the changes between 6 and 7 into T.
+ * 4. Meanwhile, someone commits revision 8.
+ * 5. Jane finishes the 6-->7 merge. T could now be committed
+ * against a latest revision of 7, if only that were still the
+ * latest. Unfortunately, 8 is now the latest, so...
+ * 6. Jane starts merging the changes between 7 and 8 into T.
+ * 7. Meanwhile, no one commits any new revisions. Whew.
+ * 8. Jane commits T, creating revision 9, whose tree is exactly
+ * T's tree, except immutable now.
+ *
+ * Lather, rinse, repeat.
+ */
+
+ svn_error_t *err;
+ svn_fs_t *fs = txn->fs;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Initialize output params. */
+ *new_rev = SVN_INVALID_REVNUM;
+ if (conflict_p)
+ *conflict_p = NULL;
+
+ while (1729)
+ {
+ struct get_root_args get_root_args;
+ struct merge_args merge_args;
+ struct commit_args commit_args;
+ svn_revnum_t youngish_rev;
+ svn_fs_root_t *youngish_root;
+ dag_node_t *youngish_root_node;
+
+ svn_pool_clear(subpool);
+
+ /* Get the *current* youngest revision, in one short-lived
+ Berkeley transaction. (We don't want the revisions table
+ locked while we do the main merge.) We call it "youngish"
+ because new revisions might get committed after we've
+ obtained it. */
+
+ SVN_ERR(svn_fs_base__youngest_rev(&youngish_rev, fs, subpool));
+ SVN_ERR(svn_fs_base__revision_root(&youngish_root, fs, youngish_rev,
+ subpool));
+
+ /* Get the dag node for the youngest revision, also in one
+ Berkeley transaction. Later we'll use it as the SOURCE
+ argument to a merge, and if the merge succeeds, this youngest
+ root node will become the new base root for the svn txn that
+ was the target of the merge (but note that the youngest rev
+ may have changed by then -- that's why we're careful to get
+ this root in its own bdb txn here). */
+ get_root_args.root = youngish_root;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_root, &get_root_args,
+ FALSE, subpool));
+ youngish_root_node = get_root_args.node;
+
+ /* Try to merge. If the merge succeeds, the base root node of
+ TARGET's txn will become the same as youngish_root_node, so
+ any future merges will only be between that node and whatever
+ the root node of the youngest rev is by then. */
+ merge_args.ancestor_node = NULL;
+ merge_args.source_node = youngish_root_node;
+ merge_args.txn = txn;
+ merge_args.conflict = svn_stringbuf_create_empty(pool); /* use pool */
+ err = svn_fs_base__retry_txn(fs, txn_body_merge, &merge_args,
+ FALSE, subpool);
+ if (err)
+ {
+ if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
+ *conflict_p = merge_args.conflict->data;
+ return svn_error_trace(err);
+ }
+
+ /* Try to commit. */
+ commit_args.txn = txn;
+ err = svn_fs_base__retry_txn(fs, txn_body_commit, &commit_args,
+ FALSE, subpool);
+ if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE))
+ {
+ /* Did someone else finish committing a new revision while we
+ were in mid-merge or mid-commit? If so, we'll need to
+ loop again to merge the new changes in, then try to
+ commit again. Or if that's not what happened, then just
+ return the error. */
+ svn_revnum_t youngest_rev;
+ svn_error_t *err2 = svn_fs_base__youngest_rev(&youngest_rev, fs,
+ subpool);
+ if (err2)
+ {
+ svn_error_clear(err);
+ return svn_error_trace(err2); /* err2 is bad,
+ it should not occur */
+ }
+ else if (youngest_rev == youngish_rev)
+ return svn_error_trace(err);
+ else
+ svn_error_clear(err);
+ }
+ else if (err)
+ {
+ return svn_error_trace(err);
+ }
+ else
+ {
+ /* Set the return value -- our brand spankin' new revision! */
+ *new_rev = commit_args.new_rev;
+ break;
+ }
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+/* Note: it is acceptable for this function to call back into
+ public FS API interfaces because it does not itself use trails. */
+static svn_error_t *
+base_merge(const char **conflict_p,
+ svn_fs_root_t *source_root,
+ const char *source_path,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ svn_fs_root_t *ancestor_root,
+ const char *ancestor_path,
+ apr_pool_t *pool)
+{
+ dag_node_t *source, *ancestor;
+ struct get_root_args get_root_args;
+ struct merge_args merge_args;
+ svn_fs_txn_t *txn;
+ svn_error_t *err;
+ svn_fs_t *fs;
+
+ if (! target_root->is_txn_root)
+ return SVN_FS__NOT_TXN(target_root);
+
+ /* Paranoia. */
+ fs = ancestor_root->fs;
+ if ((source_root->fs != fs) || (target_root->fs != fs))
+ {
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Bad merge; ancestor, source, and target not all in same fs"));
+ }
+
+ /* ### kff todo: is there any compelling reason to get the nodes in
+ one db transaction? Right now we don't; txn_body_get_root() gets
+ one node at a time. This will probably need to change:
+
+ Jim Blandy <jimb@zwingli.cygnus.com> writes:
+ > svn_fs_merge needs to be a single transaction, to protect it against
+ > people deleting parents of nodes it's working on, etc.
+ */
+
+ /* Get the ancestor node. */
+ get_root_args.root = ancestor_root;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_root, &get_root_args,
+ FALSE, pool));
+ ancestor = get_root_args.node;
+
+ /* Get the source node. */
+ get_root_args.root = source_root;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_root, &get_root_args,
+ FALSE, pool));
+ source = get_root_args.node;
+
+ /* Open a txn for the txn root into which we're merging. */
+ SVN_ERR(svn_fs_base__open_txn(&txn, fs, target_root->txn, pool));
+
+ /* Merge changes between ANCESTOR and SOURCE into TXN. */
+ merge_args.source_node = source;
+ merge_args.ancestor_node = ancestor;
+ merge_args.txn = txn;
+ merge_args.conflict = svn_stringbuf_create_empty(pool);
+ err = svn_fs_base__retry_txn(fs, txn_body_merge, &merge_args, FALSE, pool);
+ if (err)
+ {
+ if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
+ *conflict_p = merge_args.conflict->data;
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct rev_get_txn_id_args
+{
+ const char **txn_id;
+ svn_revnum_t revision;
+};
+
+
+static svn_error_t *
+txn_body_rev_get_txn_id(void *baton, trail_t *trail)
+{
+ struct rev_get_txn_id_args *args = baton;
+ return svn_fs_base__rev_get_txn_id(args->txn_id, trail->fs,
+ args->revision, trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__deltify(svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ const char *txn_id;
+ struct rev_get_txn_id_args args;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ if (bfd->format >= SVN_FS_BASE__MIN_MISCELLANY_FORMAT)
+ {
+ const char *val;
+ svn_revnum_t forward_delta_rev = 0;
+
+ SVN_ERR(svn_fs_base__miscellaneous_get
+ (&val, fs, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE, pool));
+ if (val)
+ SVN_ERR(svn_revnum_parse(&forward_delta_rev, val, NULL));
+
+ /* ### FIXME: Unnecessarily harsh requirement? (cmpilato). */
+ if (revision <= forward_delta_rev)
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot deltify revisions prior to r%ld"), forward_delta_rev+1);
+ }
+
+ SVN_ERR(svn_fs_base__revision_root(&root, fs, revision, pool));
+
+ args.txn_id = &txn_id;
+ args.revision = revision;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_rev_get_txn_id, &args,
+ FALSE, pool));
+
+ return deltify_mutable(fs, root, "/", NULL, svn_node_dir, txn_id, pool);
+}
+
+
+/* Modifying directories */
+
+
+struct make_dir_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_make_dir(void *baton,
+ trail_t *trail)
+{
+ struct make_dir_args *args = baton;
+ svn_fs_root_t *root = args->root;
+ const char *path = args->path;
+ parent_path_t *parent_path;
+ dag_node_t *sub_dir;
+ const char *txn_id = root->txn;
+
+ SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
+ txn_id, trail, trail->pool));
+
+ /* If there's already a sub-directory by that name, complain. This
+ also catches the case of trying to make a subdirectory named `/'. */
+ if (parent_path->node)
+ return SVN_FS__ALREADY_EXISTS(root, path);
+
+ /* Check to see if some lock is 'reserving' a file-path or dir-path
+ at that location, or even some child-path; if so, check that we
+ can use it. */
+ if (args->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ SVN_ERR(svn_fs_base__allow_locked_operation(path, TRUE,
+ trail, trail->pool));
+ }
+
+ /* Create the subdirectory. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent, path,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_make_dir(&sub_dir,
+ parent_path->parent->node,
+ parent_path_path(parent_path->parent,
+ trail->pool),
+ parent_path->entry,
+ txn_id,
+ trail, trail->pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(root->fs, txn_id, path,
+ svn_fs_base__dag_get_id(sub_dir),
+ svn_fs_path_change_add, FALSE, FALSE,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_make_dir(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct make_dir_args args;
+
+ if (! root->is_txn_root)
+ return SVN_FS__NOT_TXN(root);
+
+ args.root = root;
+ args.path = path;
+ return svn_fs_base__retry_txn(root->fs, txn_body_make_dir, &args,
+ TRUE, pool);
+}
+
+
+struct delete_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+/* If this returns SVN_ERR_FS_NO_SUCH_ENTRY, it means that the
+ basename of PATH is missing from its parent, that is, the final
+ target of the deletion is missing. */
+static svn_error_t *
+txn_body_delete(void *baton,
+ trail_t *trail)
+{
+ struct delete_args *args = baton;
+ svn_fs_root_t *root = args->root;
+ const char *path = args->path;
+ parent_path_t *parent_path;
+ const char *txn_id = root->txn;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ if (! root->is_txn_root)
+ return SVN_FS__NOT_TXN(root);
+
+ SVN_ERR(open_path(&parent_path, root, path, 0, txn_id,
+ trail, trail->pool));
+
+ /* We can't remove the root of the filesystem. */
+ if (! parent_path->parent)
+ return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL,
+ _("The root directory cannot be deleted"));
+
+ /* Check to see if path (or any child thereof) is locked; if so,
+ check that we can use the existing lock(s). */
+ if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ SVN_ERR(svn_fs_base__allow_locked_operation(path, TRUE,
+ trail, trail->pool));
+ }
+
+ /* Make the parent directory mutable. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent, path,
+ trail, trail->pool));
+
+ /* Decrement mergeinfo counts on the parents of this node by the
+ count it previously carried, if our format supports it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ {
+ apr_int64_t mergeinfo_count;
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, &mergeinfo_count,
+ parent_path->node,
+ trail, trail->pool));
+ SVN_ERR(adjust_parent_mergeinfo_counts(parent_path->parent,
+ -mergeinfo_count, txn_id,
+ trail, trail->pool));
+ }
+
+ /* Do the deletion. */
+ SVN_ERR(svn_fs_base__dag_delete(parent_path->parent->node,
+ parent_path->entry,
+ txn_id, trail, trail->pool));
+
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(root->fs, txn_id, path,
+ svn_fs_base__dag_get_id(parent_path->node),
+ svn_fs_path_change_delete, FALSE, FALSE, trail,
+ trail->pool);
+}
+
+
+static svn_error_t *
+base_delete_node(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct delete_args args;
+
+ args.root = root;
+ args.path = path;
+ return svn_fs_base__retry_txn(root->fs, txn_body_delete, &args,
+ TRUE, pool);
+}
+
+
+struct copy_args
+{
+ svn_fs_root_t *from_root;
+ const char *from_path;
+ svn_fs_root_t *to_root;
+ const char *to_path;
+ svn_boolean_t preserve_history;
+};
+
+
+static svn_error_t *
+txn_body_copy(void *baton,
+ trail_t *trail)
+{
+ struct copy_args *args = baton;
+ svn_fs_root_t *from_root = args->from_root;
+ const char *from_path = args->from_path;
+ svn_fs_root_t *to_root = args->to_root;
+ const char *to_path = args->to_path;
+ dag_node_t *from_node;
+ parent_path_t *to_parent_path;
+ const char *txn_id = to_root->txn;
+
+ /* Get the NODE for FROM_PATH in FROM_ROOT.*/
+ SVN_ERR(get_dag(&from_node, from_root, from_path, trail, trail->pool));
+
+ /* Build up the parent path from TO_PATH in TO_ROOT. If the last
+ component does not exist, it's not that big a deal. We'll just
+ make one there. */
+ SVN_ERR(open_path(&to_parent_path, to_root, to_path,
+ open_path_last_optional, txn_id, trail, trail->pool));
+
+ /* Check to see if to-path (or any child thereof) is locked, or at
+ least 'reserved', whether it exists or not; if so, check that we
+ can use the existing lock(s). */
+ if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ SVN_ERR(svn_fs_base__allow_locked_operation(to_path, TRUE,
+ trail, trail->pool));
+ }
+
+ /* If the destination node already exists as the same node as the
+ source (in other words, this operation would result in nothing
+ happening at all), just do nothing an return successfully,
+ proud that you saved yourself from a tiresome task. */
+ if ((to_parent_path->node)
+ && (svn_fs_base__id_compare(svn_fs_base__dag_get_id(from_node),
+ svn_fs_base__dag_get_id
+ (to_parent_path->node)) == 0))
+ return SVN_NO_ERROR;
+
+ if (! from_root->is_txn_root)
+ {
+ svn_fs_path_change_kind_t kind;
+ dag_node_t *new_node;
+ apr_int64_t old_mergeinfo_count = 0, mergeinfo_count;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ /* If TO_PATH already existed prior to the copy, note that this
+ operation is a replacement, not an addition. */
+ if (to_parent_path->node)
+ kind = svn_fs_path_change_replace;
+ else
+ kind = svn_fs_path_change_add;
+
+ /* Make sure the target node's parents are mutable. */
+ SVN_ERR(make_path_mutable(to_root, to_parent_path->parent,
+ to_path, trail, trail->pool));
+
+ /* If this is a replacement operation, we need to know the old
+ node's mergeinfo count. */
+ if (to_parent_path->node)
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL,
+ &old_mergeinfo_count,
+ to_parent_path->node,
+ trail, trail->pool));
+ /* Do the copy. */
+ SVN_ERR(svn_fs_base__dag_copy(to_parent_path->parent->node,
+ to_parent_path->entry,
+ from_node,
+ args->preserve_history,
+ from_root->rev,
+ from_path, txn_id, trail, trail->pool));
+
+ /* Adjust the mergeinfo counts of the destination's parents if
+ our format supports it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ {
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL,
+ &mergeinfo_count,
+ from_node, trail,
+ trail->pool));
+ SVN_ERR(adjust_parent_mergeinfo_counts
+ (to_parent_path->parent,
+ mergeinfo_count - old_mergeinfo_count,
+ txn_id, trail, trail->pool));
+ }
+
+ /* Make a record of this modification in the changes table. */
+ SVN_ERR(get_dag(&new_node, to_root, to_path, trail, trail->pool));
+ SVN_ERR(add_change(to_root->fs, txn_id, to_path,
+ svn_fs_base__dag_get_id(new_node),
+ kind, FALSE, FALSE, trail, trail->pool));
+ }
+ else
+ {
+ /* See IZ Issue #436 */
+ /* Copying from transaction roots not currently available.
+
+ ### cmpilato todo someday: make this not so. :-) Note that
+ when copying from mutable trees, you have to make sure that
+ you aren't creating a cyclic graph filesystem, and a simple
+ referencing operation won't cut it. Currently, we should not
+ be able to reach this clause, and the interface reports that
+ this only works from immutable trees anyway, but JimB has
+ stated that this requirement need not be necessary in the
+ future. */
+
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE.
+ Use POOL for temporary allocation only.
+ Note: this code is duplicated between libsvn_fs_fs and libsvn_fs_base. */
+static svn_error_t *
+fs_same_p(svn_boolean_t *same_p,
+ svn_fs_t *fs1,
+ svn_fs_t *fs2,
+ apr_pool_t *pool)
+{
+ *same_p = ! strcmp(fs1->uuid, fs2->uuid);
+ return SVN_NO_ERROR;
+}
+
+/* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under
+ TO_ROOT. If PRESERVE_HISTORY is set, then the copy is recorded in
+ the copies table. Perform temporary allocations in POOL. */
+static svn_error_t *
+copy_helper(svn_fs_root_t *from_root,
+ const char *from_path,
+ svn_fs_root_t *to_root,
+ const char *to_path,
+ svn_boolean_t preserve_history,
+ apr_pool_t *pool)
+{
+ struct copy_args args;
+ svn_boolean_t same_p;
+
+ /* Use an error check, not an assert, because even the caller cannot
+ guarantee that a filesystem's UUID has not changed "on the fly". */
+ SVN_ERR(fs_same_p(&same_p, from_root->fs, to_root->fs, pool));
+ if (! same_p)
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot copy between two different filesystems ('%s' and '%s')"),
+ from_root->fs->path, to_root->fs->path);
+
+ if (! to_root->is_txn_root)
+ return SVN_FS__NOT_TXN(to_root);
+
+ if (from_root->is_txn_root)
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Copy from mutable tree not currently supported"));
+
+ args.from_root = from_root;
+ args.from_path = from_path;
+ args.to_root = to_root;
+ args.to_path = to_path;
+ args.preserve_history = preserve_history;
+
+ return svn_fs_base__retry_txn(to_root->fs, txn_body_copy, &args,
+ TRUE, pool);
+}
+
+static svn_error_t *
+base_copy(svn_fs_root_t *from_root,
+ const char *from_path,
+ svn_fs_root_t *to_root,
+ const char *to_path,
+ apr_pool_t *pool)
+{
+ return copy_helper(from_root, from_path, to_root, to_path, TRUE, pool);
+}
+
+
+static svn_error_t *
+base_revision_link(svn_fs_root_t *from_root,
+ svn_fs_root_t *to_root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ return copy_helper(from_root, path, to_root, path, FALSE, pool);
+}
+
+
+struct copied_from_args
+{
+ svn_fs_root_t *root; /* Root for the node whose ancestry we seek. */
+ const char *path; /* Path for the node whose ancestry we seek. */
+
+ svn_revnum_t result_rev; /* Revision, if any, of the ancestor. */
+ const char *result_path; /* Path, if any, of the ancestor. */
+
+ apr_pool_t *pool; /* Allocate `result_path' here. */
+};
+
+
+static svn_error_t *
+txn_body_copied_from(void *baton, trail_t *trail)
+{
+ struct copied_from_args *args = baton;
+ const svn_fs_id_t *node_id, *pred_id;
+ dag_node_t *node;
+ svn_fs_t *fs = args->root->fs;
+
+ /* Clear the return variables. */
+ args->result_path = NULL;
+ args->result_rev = SVN_INVALID_REVNUM;
+
+ /* Fetch the NODE in question. */
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ node_id = svn_fs_base__dag_get_id(node);
+
+ /* Check the node's predecessor-ID. If it doesn't have one, it
+ isn't a copy. */
+ SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id, node,
+ trail, trail->pool));
+ if (! pred_id)
+ return SVN_NO_ERROR;
+
+ /* If NODE's copy-ID is the same as that of its predecessor... */
+ if (svn_fs_base__key_compare(svn_fs_base__id_copy_id(node_id),
+ svn_fs_base__id_copy_id(pred_id)) != 0)
+ {
+ /* ... then NODE was either the target of a copy operation,
+ a copied subtree item. We examine the actual copy record
+ to determine which is the case. */
+ copy_t *copy;
+ SVN_ERR(svn_fs_bdb__get_copy(&copy, fs,
+ svn_fs_base__id_copy_id(node_id),
+ trail, trail->pool));
+ if ((copy->kind == copy_kind_real)
+ && svn_fs_base__id_eq(copy->dst_noderev_id, node_id))
+ {
+ args->result_path = copy->src_path;
+ SVN_ERR(svn_fs_base__txn_get_revision(&(args->result_rev), fs,
+ copy->src_txn_id,
+ trail, trail->pool));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_copied_from(svn_revnum_t *rev_p,
+ const char **path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct copied_from_args args;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ args.root = root;
+ args.path = path;
+ args.pool = pool;
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_copied_from, &args,
+ FALSE, scratch_pool));
+
+ *rev_p = args.result_rev;
+ *path_p = args.result_path ? apr_pstrdup(pool, args.result_path) : NULL;
+
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Files. */
+
+
+struct make_file_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_make_file(void *baton,
+ trail_t *trail)
+{
+ struct make_file_args *args = baton;
+ svn_fs_root_t *root = args->root;
+ const char *path = args->path;
+ parent_path_t *parent_path;
+ dag_node_t *child;
+ const char *txn_id = root->txn;
+
+ SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
+ txn_id, trail, trail->pool));
+
+ /* If there's already a file by that name, complain.
+ This also catches the case of trying to make a file named `/'. */
+ if (parent_path->node)
+ return SVN_FS__ALREADY_EXISTS(root, path);
+
+ /* Check to see if some lock is 'reserving' a file-path or dir-path
+ at that location, or even some child-path; if so, check that we
+ can use it. */
+ if (args->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ SVN_ERR(svn_fs_base__allow_locked_operation(path, TRUE,
+ trail, trail->pool));
+ }
+
+ /* Create the file. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent, path,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_make_file(&child,
+ parent_path->parent->node,
+ parent_path_path(parent_path->parent,
+ trail->pool),
+ parent_path->entry,
+ txn_id,
+ trail, trail->pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(root->fs, txn_id, path,
+ svn_fs_base__dag_get_id(child),
+ svn_fs_path_change_add, TRUE, FALSE,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_make_file(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct make_file_args args;
+
+ args.root = root;
+ args.path = path;
+ return svn_fs_base__retry_txn(root->fs, txn_body_make_file, &args,
+ TRUE, pool);
+}
+
+
+
+struct file_length_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+ svn_filesize_t length; /* OUT parameter */
+};
+
+static svn_error_t *
+txn_body_file_length(void *baton,
+ trail_t *trail)
+{
+ struct file_length_args *args = baton;
+ dag_node_t *file;
+
+ /* First create a dag_node_t from the root/path pair. */
+ SVN_ERR(get_dag(&file, args->root, args->path, trail, trail->pool));
+
+ /* Now fetch its length */
+ return svn_fs_base__dag_file_length(&args->length, file,
+ trail, trail->pool);
+}
+
+static svn_error_t *
+base_file_length(svn_filesize_t *length_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct file_length_args args;
+
+ args.root = root;
+ args.path = path;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_file_length, &args,
+ TRUE, pool));
+
+ *length_p = args.length;
+ return SVN_NO_ERROR;
+}
+
+
+struct file_checksum_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+ svn_checksum_kind_t kind;
+ svn_checksum_t **checksum; /* OUT parameter */
+};
+
+static svn_error_t *
+txn_body_file_checksum(void *baton,
+ trail_t *trail)
+{
+ struct file_checksum_args *args = baton;
+ dag_node_t *file;
+
+ SVN_ERR(get_dag(&file, args->root, args->path, trail, trail->pool));
+
+ return svn_fs_base__dag_file_checksum(args->checksum, args->kind, file,
+ trail, trail->pool);
+}
+
+static svn_error_t *
+base_file_checksum(svn_checksum_t **checksum,
+ svn_checksum_kind_t kind,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct file_checksum_args args;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ args.root = root;
+ args.path = path;
+ args.kind = kind;
+ args.checksum = checksum;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_file_checksum, &args,
+ FALSE, scratch_pool));
+ *checksum = svn_checksum_dup(*checksum, pool);
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* --- Machinery for svn_fs_file_contents() --- */
+
+
+/* Local baton type for txn_body_get_file_contents. */
+typedef struct file_contents_baton_t
+{
+ /* The file we want to read. */
+ svn_fs_root_t *root;
+ const char *path;
+
+ /* The dag_node that will be made from the above. */
+ dag_node_t *node;
+
+ /* The pool in which `file_stream' (below) is allocated. */
+ apr_pool_t *pool;
+
+ /* The readable file stream that will be made from the
+ dag_node. (And returned to the caller.) */
+ svn_stream_t *file_stream;
+
+} file_contents_baton_t;
+
+
+/* Main body of svn_fs_file_contents; converts a root/path pair into
+ a readable file stream (in the context of a db txn). */
+static svn_error_t *
+txn_body_get_file_contents(void *baton, trail_t *trail)
+{
+ file_contents_baton_t *fb = (file_contents_baton_t *) baton;
+
+ /* First create a dag_node_t from the root/path pair. */
+ SVN_ERR(get_dag(&(fb->node), fb->root, fb->path, trail, trail->pool));
+
+ /* Then create a readable stream from the dag_node_t. */
+ return svn_fs_base__dag_get_contents(&(fb->file_stream),
+ fb->node, trail, fb->pool);
+}
+
+
+
+static svn_error_t *
+base_file_contents(svn_stream_t **contents,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ file_contents_baton_t *fb = apr_pcalloc(pool, sizeof(*fb));
+ fb->root = root;
+ fb->path = path;
+ fb->pool = pool;
+
+ /* Create the readable stream in the context of a db txn. */
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_get_file_contents, fb,
+ FALSE, pool));
+
+ *contents = fb->file_stream;
+ return SVN_NO_ERROR;
+}
+
+/* --- End machinery for svn_fs_file_contents() --- */
+
+
+
+/* --- Machinery for svn_fs_apply_textdelta() --- */
+
+
+/* Local baton type for all the helper functions below. */
+typedef struct txdelta_baton_t
+{
+ /* This is the custom-built window consumer given to us by the delta
+ library; it uniquely knows how to read data from our designated
+ "source" stream, interpret the window, and write data to our
+ designated "target" stream (in this case, our repos file.) */
+ svn_txdelta_window_handler_t interpreter;
+ void *interpreter_baton;
+
+ /* The original file info */
+ svn_fs_root_t *root;
+ const char *path;
+
+ /* Derived from the file info */
+ dag_node_t *node;
+
+ svn_stream_t *source_stream;
+ svn_stream_t *target_stream;
+ svn_stream_t *string_stream;
+ svn_stringbuf_t *target_string;
+
+ /* Checksums for the base text against which a delta is to be
+ applied, and for the resultant fulltext, respectively. Either or
+ both may be null, in which case ignored. */
+ svn_checksum_t *base_checksum;
+ svn_checksum_t *result_checksum;
+
+ /* Pool used by db txns */
+ apr_pool_t *pool;
+
+} txdelta_baton_t;
+
+
+/* A trail-ready wrapper around svn_fs_base__dag_finalize_edits.
+ * This closes BATON->target_stream.
+ *
+ * Note: If you're confused about how this function relates to another
+ * of similar name, think of it this way:
+ *
+ * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
+ * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits()
+ */
+static svn_error_t *
+txn_body_txdelta_finalize_edits(void *baton, trail_t *trail)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+ SVN_ERR(svn_fs_base__dag_finalize_edits(tb->node,
+ tb->result_checksum,
+ tb->root->txn,
+ trail, trail->pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(tb->root->fs, tb->root->txn, tb->path,
+ svn_fs_base__dag_get_id(tb->node),
+ svn_fs_path_change_modify, TRUE, FALSE, trail,
+ trail->pool);
+}
+
+
+/* ### see comment in window_consumer() regarding this function. */
+
+/* Helper function of generic type `svn_write_fn_t'. Implements a
+ writable stream which appends to an svn_stringbuf_t. */
+static svn_error_t *
+write_to_string(void *baton, const char *data, apr_size_t *len)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+ svn_stringbuf_appendbytes(tb->target_string, data, *len);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* The main window handler returned by svn_fs_apply_textdelta. */
+static svn_error_t *
+window_consumer(svn_txdelta_window_t *window, void *baton)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+
+ /* Send the window right through to the custom window interpreter.
+ In theory, the interpreter will then write more data to
+ cb->target_string. */
+ SVN_ERR(tb->interpreter(window, tb->interpreter_baton));
+
+ /* ### the write_to_string() callback for the txdelta's output stream
+ ### should be doing all the flush determination logic, not here.
+ ### in a drastic case, a window could generate a LOT more than the
+ ### maximum buffer size. we want to flush to the underlying target
+ ### stream much sooner (e.g. also in a streamy fashion). also, by
+ ### moving this logic inside the stream, the stream becomes nice
+ ### and encapsulated: it holds all the logic about buffering and
+ ### flushing.
+ ###
+ ### further: I believe the buffering should be removed from tree.c
+ ### the buffering should go into the target_stream itself, which
+ ### is defined by reps-string.c. Specifically, I think the
+ ### rep_write_contents() function will handle the buffering and
+ ### the spill to the underlying DB. by locating it there, then
+ ### anybody who gets a writable stream for FS content can take
+ ### advantage of the buffering capability. this will be important
+ ### when we export an FS API function for writing a fulltext into
+ ### the FS, rather than forcing that fulltext thru apply_textdelta.
+ */
+
+ /* Check to see if we need to purge the portion of the contents that
+ have been written thus far. */
+ if ((! window) || (tb->target_string->len > WRITE_BUFFER_SIZE))
+ {
+ apr_size_t len = tb->target_string->len;
+ SVN_ERR(svn_stream_write(tb->target_stream,
+ tb->target_string->data,
+ &len));
+ svn_stringbuf_setempty(tb->target_string);
+ }
+
+ /* Is the window NULL? If so, we're done. */
+ if (! window)
+ {
+ /* Close the internal-use stream. ### This used to be inside of
+ txn_body_fulltext_finalize_edits(), but that invoked a nested
+ Berkeley DB transaction -- scandalous! */
+ SVN_ERR(svn_stream_close(tb->target_stream));
+
+ /* Tell the dag subsystem that we're finished with our edits. */
+ SVN_ERR(svn_fs_base__retry_txn(tb->root->fs,
+ txn_body_txdelta_finalize_edits, tb,
+ FALSE, tb->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+txn_body_apply_textdelta(void *baton, trail_t *trail)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+ parent_path_t *parent_path;
+ const char *txn_id = tb->root->txn;
+
+ /* Call open_path with no flags, as we want this to return an error
+ if the node for which we are searching doesn't exist. */
+ SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, txn_id,
+ trail, trail->pool));
+
+ /* Check to see if path is locked; if so, check that we can use it. */
+ if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_base__allow_locked_operation(tb->path, FALSE,
+ trail, trail->pool));
+
+ /* Now, make sure this path is mutable. */
+ SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path,
+ trail, trail->pool));
+ tb->node = parent_path->node;
+
+ if (tb->base_checksum)
+ {
+ svn_checksum_t *checksum;
+
+ /* Until we finalize the node, its data_key points to the old
+ contents, in other words, the base text. */
+ SVN_ERR(svn_fs_base__dag_file_checksum(&checksum,
+ tb->base_checksum->kind,
+ tb->node, trail, trail->pool));
+ /* TODO: This only compares checksums if they are the same kind, but
+ we're calculating both SHA1 and MD5 checksums somewhere in
+ reps-strings.c. Could we keep them both around somehow so this
+ check could be more comprehensive? */
+ if (!svn_checksum_match(tb->base_checksum, checksum))
+ return svn_checksum_mismatch_err(tb->base_checksum, checksum,
+ trail->pool,
+ _("Base checksum mismatch on '%s'"),
+ tb->path);
+ }
+
+ /* Make a readable "source" stream out of the current contents of
+ ROOT/PATH; obviously, this must done in the context of a db_txn.
+ The stream is returned in tb->source_stream. */
+ SVN_ERR(svn_fs_base__dag_get_contents(&(tb->source_stream),
+ tb->node, trail, tb->pool));
+
+ /* Make a writable "target" stream */
+ SVN_ERR(svn_fs_base__dag_get_edit_stream(&(tb->target_stream), tb->node,
+ txn_id, trail, tb->pool));
+
+ /* Make a writable "string" stream which writes data to
+ tb->target_string. */
+ tb->target_string = svn_stringbuf_create_empty(tb->pool);
+ tb->string_stream = svn_stream_create(tb, tb->pool);
+ svn_stream_set_write(tb->string_stream, write_to_string);
+
+ /* Now, create a custom window handler that uses our two streams. */
+ svn_txdelta_apply(tb->source_stream,
+ tb->string_stream,
+ NULL,
+ tb->path,
+ tb->pool,
+ &(tb->interpreter),
+ &(tb->interpreter_baton));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_apply_textdelta(svn_txdelta_window_handler_t *contents_p,
+ void **contents_baton_p,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_checksum_t *base_checksum,
+ svn_checksum_t *result_checksum,
+ apr_pool_t *pool)
+{
+ txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
+
+ tb->root = root;
+ tb->path = path;
+ tb->pool = pool;
+ tb->base_checksum = svn_checksum_dup(base_checksum, pool);
+ tb->result_checksum = svn_checksum_dup(result_checksum, pool);
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_apply_textdelta, tb,
+ FALSE, pool));
+
+ *contents_p = window_consumer;
+ *contents_baton_p = tb;
+ return SVN_NO_ERROR;
+}
+
+/* --- End machinery for svn_fs_apply_textdelta() --- */
+
+/* --- Machinery for svn_fs_apply_text() --- */
+
+/* Baton for svn_fs_apply_text(). */
+struct text_baton_t
+{
+ /* The original file info */
+ svn_fs_root_t *root;
+ const char *path;
+
+ /* Derived from the file info */
+ dag_node_t *node;
+
+ /* The returned stream that will accept the file's new contents. */
+ svn_stream_t *stream;
+
+ /* The actual fs stream that the returned stream will write to. */
+ svn_stream_t *file_stream;
+
+ /* Checksum for the final fulltext written to the file. May
+ be null, in which case ignored. */
+ svn_checksum_t *result_checksum;
+
+ /* Pool used by db txns */
+ apr_pool_t *pool;
+};
+
+
+/* A trail-ready wrapper around svn_fs_base__dag_finalize_edits, but for
+ * fulltext data, not text deltas. Closes BATON->file_stream.
+ *
+ * Note: If you're confused about how this function relates to another
+ * of similar name, think of it this way:
+ *
+ * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
+ * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits()
+ */
+static svn_error_t *
+txn_body_fulltext_finalize_edits(void *baton, trail_t *trail)
+{
+ struct text_baton_t *tb = baton;
+ SVN_ERR(svn_fs_base__dag_finalize_edits(tb->node,
+ tb->result_checksum,
+ tb->root->txn,
+ trail, trail->pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(tb->root->fs, tb->root->txn, tb->path,
+ svn_fs_base__dag_get_id(tb->node),
+ svn_fs_path_change_modify, TRUE, FALSE, trail,
+ trail->pool);
+}
+
+/* Write function for the publically returned stream. */
+static svn_error_t *
+text_stream_writer(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ struct text_baton_t *tb = baton;
+
+ /* Psst, here's some data. Pass it on to the -real- file stream. */
+ return svn_stream_write(tb->file_stream, data, len);
+}
+
+/* Close function for the publically returned stream. */
+static svn_error_t *
+text_stream_closer(void *baton)
+{
+ struct text_baton_t *tb = baton;
+
+ /* Close the internal-use stream. ### This used to be inside of
+ txn_body_fulltext_finalize_edits(), but that invoked a nested
+ Berkeley DB transaction -- scandalous! */
+ SVN_ERR(svn_stream_close(tb->file_stream));
+
+ /* Need to tell fs that we're done sending text */
+ return svn_fs_base__retry_txn(tb->root->fs,
+ txn_body_fulltext_finalize_edits, tb,
+ FALSE, tb->pool);
+}
+
+
+static svn_error_t *
+txn_body_apply_text(void *baton, trail_t *trail)
+{
+ struct text_baton_t *tb = baton;
+ parent_path_t *parent_path;
+ const char *txn_id = tb->root->txn;
+
+ /* Call open_path with no flags, as we want this to return an error
+ if the node for which we are searching doesn't exist. */
+ SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, txn_id,
+ trail, trail->pool));
+
+ /* Check to see if path is locked; if so, check that we can use it. */
+ if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_base__allow_locked_operation(tb->path, FALSE,
+ trail, trail->pool));
+
+ /* Now, make sure this path is mutable. */
+ SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path,
+ trail, trail->pool));
+ tb->node = parent_path->node;
+
+ /* Make a writable stream for replacing the file's text. */
+ SVN_ERR(svn_fs_base__dag_get_edit_stream(&(tb->file_stream), tb->node,
+ txn_id, trail, tb->pool));
+
+ /* Create a 'returnable' stream which writes to the file_stream. */
+ tb->stream = svn_stream_create(tb, tb->pool);
+ svn_stream_set_write(tb->stream, text_stream_writer);
+ svn_stream_set_close(tb->stream, text_stream_closer);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_apply_text(svn_stream_t **contents_p,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_checksum_t *result_checksum,
+ apr_pool_t *pool)
+{
+ struct text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
+
+ tb->root = root;
+ tb->path = path;
+ tb->pool = pool;
+ tb->result_checksum = svn_checksum_dup(result_checksum, pool);
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_apply_text, tb,
+ FALSE, pool));
+
+ *contents_p = tb->stream;
+ return SVN_NO_ERROR;
+}
+
+/* --- End machinery for svn_fs_apply_text() --- */
+
+
+/* Note: we're sharing the `things_changed_args' struct with
+ svn_fs_props_changed(). */
+
+static svn_error_t *
+txn_body_contents_changed(void *baton, trail_t *trail)
+{
+ struct things_changed_args *args = baton;
+ dag_node_t *node1, *node2;
+
+ SVN_ERR(get_dag(&node1, args->root1, args->path1, trail, trail->pool));
+ SVN_ERR(get_dag(&node2, args->root2, args->path2, trail, trail->pool));
+ return svn_fs_base__things_different(NULL, args->changed_p,
+ node1, node2, trail, trail->pool);
+}
+
+
+/* Note: it is acceptable for this function to call back into
+ top-level interfaces because it does not itself use trails. */
+static svn_error_t *
+base_contents_changed(svn_boolean_t *changed_p,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ struct things_changed_args args;
+
+ /* Check that roots are in the same fs. */
+ if (root1->fs != root2->fs)
+ return svn_error_create
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Cannot compare file contents between two different filesystems"));
+
+ /* Check that both paths are files. */
+ {
+ svn_node_kind_t kind;
+
+ SVN_ERR(base_check_path(&kind, root1, path1, pool));
+ if (kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
+
+ SVN_ERR(base_check_path(&kind, root2, path2, pool));
+ if (kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
+ }
+
+ args.root1 = root1;
+ args.root2 = root2;
+ args.path1 = path1;
+ args.path2 = path2;
+ args.changed_p = changed_p;
+ args.pool = pool;
+
+ return svn_fs_base__retry_txn(root1->fs, txn_body_contents_changed, &args,
+ TRUE, pool);
+}
+
+
+
+/* Public interface to computing file text deltas. */
+
+/* Note: it is acceptable for this function to call back into
+ public FS API interfaces because it does not itself use trails. */
+static svn_error_t *
+base_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ svn_fs_root_t *source_root,
+ const char *source_path,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ apr_pool_t *pool)
+{
+ svn_stream_t *source, *target;
+ svn_txdelta_stream_t *delta_stream;
+
+ /* Get read functions for the source file contents. */
+ if (source_root && source_path)
+ SVN_ERR(base_file_contents(&source, source_root, source_path, pool));
+ else
+ source = svn_stream_empty(pool);
+
+ /* Get read functions for the target file contents. */
+ SVN_ERR(base_file_contents(&target, target_root, target_path, pool));
+
+ /* Create a delta stream that turns the ancestor into the target. */
+ svn_txdelta2(&delta_stream, source, target, TRUE, pool);
+
+ *stream_p = delta_stream;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Finding Changes */
+
+struct paths_changed_args
+{
+ apr_hash_t *changes;
+ svn_fs_root_t *root;
+};
+
+
+static svn_error_t *
+txn_body_paths_changed(void *baton,
+ trail_t *trail)
+{
+ /* WARNING: This is called *without* the protection of a Berkeley DB
+ transaction. If you modify this function, keep that in mind. */
+
+ struct paths_changed_args *args = baton;
+ const char *txn_id;
+ svn_fs_t *fs = args->root->fs;
+
+ /* Get the transaction ID from ROOT. */
+ if (! args->root->is_txn_root)
+ SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id, fs, args->root->rev,
+ trail, trail->pool));
+ else
+ txn_id = args->root->txn;
+
+ return svn_fs_bdb__changes_fetch(&(args->changes), fs, txn_id,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_paths_changed(apr_hash_t **changed_paths_p,
+ svn_fs_root_t *root,
+ apr_pool_t *pool)
+{
+ struct paths_changed_args args;
+ args.root = root;
+ args.changes = NULL;
+ SVN_ERR(svn_fs_base__retry(root->fs, txn_body_paths_changed, &args,
+ FALSE, pool));
+ *changed_paths_p = args.changes;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Our coolio opaque history object. */
+typedef struct base_history_data_t
+{
+ /* filesystem object */
+ svn_fs_t *fs;
+
+ /* path and revision of historical location */
+ const char *path;
+ svn_revnum_t revision;
+
+ /* internal-use hints about where to resume the history search. */
+ const char *path_hint;
+ svn_revnum_t rev_hint;
+
+ /* FALSE until the first call to svn_fs_history_prev(). */
+ svn_boolean_t is_interesting;
+} base_history_data_t;
+
+
+static svn_fs_history_t *assemble_history(svn_fs_t *fs, const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t is_interesting,
+ const char *path_hint,
+ svn_revnum_t rev_hint,
+ apr_pool_t *pool);
+
+
+static svn_error_t *
+base_node_history(svn_fs_history_t **history_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+
+ /* We require a revision root. */
+ if (root->is_txn_root)
+ return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
+
+ /* And we require that the path exist in the root. */
+ SVN_ERR(base_check_path(&kind, root, path, pool));
+ if (kind == svn_node_none)
+ return SVN_FS__NOT_FOUND(root, path);
+
+ /* Okay, all seems well. Build our history object and return it. */
+ *history_p = assemble_history(root->fs,
+ svn_fs__canonicalize_abspath(path, pool),
+ root->rev, FALSE, NULL,
+ SVN_INVALID_REVNUM, pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Examine the PARENT_PATH structure chain to determine how copy IDs
+ would be doled out in the event that PARENT_PATH was made mutable.
+ Return the ID of the copy that last affected PARENT_PATH (and the
+ COPY itself, if we've already fetched it).
+*/
+static svn_error_t *
+examine_copy_inheritance(const char **copy_id,
+ copy_t **copy,
+ svn_fs_t *fs,
+ parent_path_t *parent_path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* The default response -- our current copy ID, and no fetched COPY. */
+ *copy_id = svn_fs_base__id_copy_id
+ (svn_fs_base__dag_get_id(parent_path->node));
+ *copy = NULL;
+
+ /* If we have no parent (we are looking at the root node), or if
+ this node is supposed to inherit from itself, return that fact. */
+ if (! parent_path->parent)
+ return SVN_NO_ERROR;
+
+ /* We could be a branch destination (which would answer our question
+ altogether)! But then, again, we might just have been modified
+ in this revision, so all bets are off. */
+ if (parent_path->copy_inherit == copy_id_inherit_self)
+ {
+ /* A copy ID of "0" means we've never been branched. Therefore,
+ there are no copies relevant to our history. */
+ if (((*copy_id)[0] == '0') && ((*copy_id)[1] == '\0'))
+ return SVN_NO_ERROR;
+
+ /* Get the COPY record. If it was a real copy (not an implicit
+ one), we have our answer. Otherwise, we fall through to the
+ recursive case. */
+ SVN_ERR(svn_fs_bdb__get_copy(copy, fs, *copy_id, trail, pool));
+ if ((*copy)->kind != copy_kind_soft)
+ return SVN_NO_ERROR;
+ }
+
+ /* Otherwise, our answer is dependent upon our parent. */
+ return examine_copy_inheritance(copy_id, copy, fs,
+ parent_path->parent, trail, pool);
+}
+
+
+struct history_prev_args
+{
+ svn_fs_history_t **prev_history_p;
+ svn_fs_history_t *history;
+ svn_boolean_t cross_copies;
+ apr_pool_t *pool;
+};
+
+
+static svn_error_t *
+txn_body_history_prev(void *baton, trail_t *trail)
+{
+ struct history_prev_args *args = baton;
+ svn_fs_history_t **prev_history = args->prev_history_p;
+ svn_fs_history_t *history = args->history;
+ base_history_data_t *bhd = history->fsap_data;
+ const char *commit_path, *src_path, *path = bhd->path;
+ svn_revnum_t commit_rev, src_rev, dst_rev, revision = bhd->revision;
+ apr_pool_t *retpool = args->pool;
+ svn_fs_t *fs = bhd->fs;
+ parent_path_t *parent_path;
+ dag_node_t *node;
+ svn_fs_root_t *root;
+ const svn_fs_id_t *node_id;
+ const char *end_copy_id = NULL;
+ struct revision_root_args rr_args;
+ svn_boolean_t reported = bhd->is_interesting;
+ const char *txn_id;
+ copy_t *copy = NULL;
+ svn_boolean_t retry = FALSE;
+
+ /* Initialize our return value. */
+ *prev_history = NULL;
+
+ /* If our last history report left us hints about where to pickup
+ the chase, then our last report was on the destination of a
+ copy. If we are crossing copies, start from those locations,
+ otherwise, we're all done here. */
+ if (bhd->path_hint && SVN_IS_VALID_REVNUM(bhd->rev_hint))
+ {
+ reported = FALSE;
+ if (! args->cross_copies)
+ return SVN_NO_ERROR;
+ path = bhd->path_hint;
+ revision = bhd->rev_hint;
+ }
+
+ /* Construct a ROOT for the current revision. */
+ rr_args.root_p = &root;
+ rr_args.rev = revision;
+ SVN_ERR(txn_body_revision_root(&rr_args, trail));
+
+ /* Open PATH/REVISION, and get its node and a bunch of other
+ goodies. */
+ SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id, fs, revision, trail,
+ trail->pool));
+ SVN_ERR(open_path(&parent_path, root, path, 0, txn_id,
+ trail, trail->pool));
+ node = parent_path->node;
+ node_id = svn_fs_base__dag_get_id(node);
+ commit_path = svn_fs_base__dag_get_created_path(node);
+ SVN_ERR(svn_fs_base__dag_get_revision(&commit_rev, node,
+ trail, trail->pool));
+
+ /* The Subversion filesystem is written in such a way that a given
+ line of history may have at most one interesting history point
+ per filesystem revision. Either that node was edited (and
+ possibly copied), or it was copied but not edited. And a copy
+ source cannot be from the same revision as its destination. So,
+ if our history revision matches its node's commit revision, we
+ know that ... */
+ if (revision == commit_rev)
+ {
+ if (! reported)
+ {
+ /* ... we either have not yet reported on this revision (and
+ need now to do so) ... */
+ *prev_history = assemble_history(fs,
+ apr_pstrdup(retpool, commit_path),
+ commit_rev, TRUE, NULL,
+ SVN_INVALID_REVNUM, retpool);
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* ... or we *have* reported on this revision, and must now
+ progress toward this node's predecessor (unless there is
+ no predecessor, in which case we're all done!). */
+ const svn_fs_id_t *pred_id;
+
+ SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id, node,
+ trail, trail->pool));
+ if (! pred_id)
+ return SVN_NO_ERROR;
+
+ /* Replace NODE and friends with the information from its
+ predecessor. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, fs, pred_id,
+ trail, trail->pool));
+ node_id = svn_fs_base__dag_get_id(node);
+ commit_path = svn_fs_base__dag_get_created_path(node);
+ SVN_ERR(svn_fs_base__dag_get_revision(&commit_rev, node,
+ trail, trail->pool));
+ }
+ }
+
+ /* Calculate a possibly relevant copy ID. */
+ SVN_ERR(examine_copy_inheritance(&end_copy_id, &copy, fs,
+ parent_path, trail, trail->pool));
+
+ /* Initialize some state variables. */
+ src_path = NULL;
+ src_rev = SVN_INVALID_REVNUM;
+ dst_rev = SVN_INVALID_REVNUM;
+
+ /* If our current copy ID (which is either the real copy ID of our
+ node, or the last copy ID which would affect our node if it were
+ to be made mutable) diffs at all from that of its predecessor
+ (which is either a real predecessor, or is the node itself
+ playing the predecessor role to an imaginary mutable successor),
+ then we need to report a copy. */
+ if (svn_fs_base__key_compare(svn_fs_base__id_copy_id(node_id),
+ end_copy_id) != 0)
+ {
+ const char *remainder;
+ dag_node_t *dst_node;
+ const char *copy_dst;
+
+ /* Get the COPY record if we haven't already fetched it. */
+ if (! copy)
+ SVN_ERR(svn_fs_bdb__get_copy(&copy, fs, end_copy_id, trail,
+ trail->pool));
+
+ /* Figure out the destination path of the copy operation. */
+ SVN_ERR(svn_fs_base__dag_get_node(&dst_node, fs,
+ copy->dst_noderev_id,
+ trail, trail->pool));
+ copy_dst = svn_fs_base__dag_get_created_path(dst_node);
+
+ /* If our current path was the very destination of the copy,
+ then our new current path will be the copy source. If our
+ current path was instead the *child* of the destination of
+ the copy, then figure out its previous location by taking its
+ path relative to the copy destination and appending that to
+ the copy source. Finally, if our current path doesn't meet
+ one of these other criteria ... ### for now just fallback to
+ the old copy hunt algorithm. */
+ remainder = svn_fspath__skip_ancestor(copy_dst, path);
+
+ if (remainder)
+ {
+ /* If we get here, then our current path is the destination
+ of, or the child of the destination of, a copy. Fill
+ in the return values and get outta here. */
+ SVN_ERR(svn_fs_base__txn_get_revision
+ (&src_rev, fs, copy->src_txn_id, trail, trail->pool));
+ SVN_ERR(svn_fs_base__txn_get_revision
+ (&dst_rev, fs,
+ svn_fs_base__id_txn_id(copy->dst_noderev_id),
+ trail, trail->pool));
+ src_path = svn_fspath__join(copy->src_path, remainder,
+ trail->pool);
+ if (copy->kind == copy_kind_soft)
+ retry = TRUE;
+ }
+ }
+
+ /* If we calculated a copy source path and revision, and the
+ copy source revision doesn't pre-date a revision in which we
+ *know* our node was modified, we'll make a 'copy-style' history
+ object. */
+ if (src_path && SVN_IS_VALID_REVNUM(src_rev) && (src_rev >= commit_rev))
+ {
+ /* It's possible for us to find a copy location that is the same
+ as the history point we've just reported. If that happens,
+ we simply need to take another trip through this history
+ search. */
+ if ((dst_rev == revision) && reported)
+ retry = TRUE;
+
+ *prev_history = assemble_history(fs, apr_pstrdup(retpool, path),
+ dst_rev, ! retry,
+ src_path, src_rev, retpool);
+ }
+ else
+ {
+ *prev_history = assemble_history(fs, apr_pstrdup(retpool, commit_path),
+ commit_rev, TRUE, NULL,
+ SVN_INVALID_REVNUM, retpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_history_prev(svn_fs_history_t **prev_history_p,
+ svn_fs_history_t *history,
+ svn_boolean_t cross_copies,
+ apr_pool_t *pool)
+{
+ svn_fs_history_t *prev_history = NULL;
+ base_history_data_t *bhd = history->fsap_data;
+ svn_fs_t *fs = bhd->fs;
+
+ /* Special case: the root directory changes in every single
+ revision, no exceptions. And, the root can't be the target (or
+ child of a target -- duh) of a copy. So, if that's our path,
+ then we need only decrement our revision by 1, and there you go. */
+ if (strcmp(bhd->path, "/") == 0)
+ {
+ if (! bhd->is_interesting)
+ prev_history = assemble_history(fs, "/", bhd->revision,
+ 1, NULL, SVN_INVALID_REVNUM, pool);
+ else if (bhd->revision > 0)
+ prev_history = assemble_history(fs, "/", bhd->revision - 1,
+ 1, NULL, SVN_INVALID_REVNUM, pool);
+ }
+ else
+ {
+ struct history_prev_args args;
+ prev_history = history;
+
+ while (1)
+ {
+ /* Get a trail, and get to work. */
+
+ args.prev_history_p = &prev_history;
+ args.history = prev_history;
+ args.cross_copies = cross_copies;
+ args.pool = pool;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_history_prev, &args,
+ FALSE, pool));
+ if (! prev_history)
+ break;
+ bhd = prev_history->fsap_data;
+ if (bhd->is_interesting)
+ break;
+ }
+ }
+
+ *prev_history_p = prev_history;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_history_location(const char **path,
+ svn_revnum_t *revision,
+ svn_fs_history_t *history,
+ apr_pool_t *pool)
+{
+ base_history_data_t *bhd = history->fsap_data;
+
+ *path = apr_pstrdup(pool, bhd->path);
+ *revision = bhd->revision;
+ return SVN_NO_ERROR;
+}
+
+
+static history_vtable_t history_vtable = {
+ base_history_prev,
+ base_history_location
+};
+
+
+
+struct closest_copy_args
+{
+ svn_fs_root_t **root_p;
+ const char **path_p;
+ svn_fs_root_t *root;
+ const char *path;
+ apr_pool_t *pool;
+};
+
+
+static svn_error_t *
+txn_body_closest_copy(void *baton, trail_t *trail)
+{
+ struct closest_copy_args *args = baton;
+ svn_fs_root_t *root = args->root;
+ const char *path = args->path;
+ svn_fs_t *fs = root->fs;
+ parent_path_t *parent_path;
+ const svn_fs_id_t *node_id;
+ const char *txn_id, *copy_id;
+ copy_t *copy = NULL;
+ svn_fs_root_t *copy_dst_root;
+ dag_node_t *path_node_in_copy_dst, *copy_dst_node, *copy_dst_root_node;
+ const char *copy_dst_path;
+ svn_revnum_t copy_dst_rev, created_rev;
+ svn_error_t *err;
+
+ *(args->path_p) = NULL;
+ *(args->root_p) = NULL;
+
+ /* Get the transaction ID associated with our root. */
+ if (root->is_txn_root)
+ txn_id = root->txn;
+ else
+ SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id, fs, root->rev,
+ trail, trail->pool));
+
+ /* Open PATH in ROOT -- it must exist. */
+ SVN_ERR(open_path(&parent_path, root, path, 0, txn_id,
+ trail, trail->pool));
+ node_id = svn_fs_base__dag_get_id(parent_path->node);
+
+ /* Now, examine the copy inheritance rules in play should our path
+ be made mutable in the future (if it isn't already). This will
+ tell us about the youngest affecting copy. */
+ SVN_ERR(examine_copy_inheritance(&copy_id, &copy, fs, parent_path,
+ trail, trail->pool));
+
+ /* Easy out: if the copy ID is 0, there's nothing of interest here. */
+ if (((copy_id)[0] == '0') && ((copy_id)[1] == '\0'))
+ return SVN_NO_ERROR;
+
+ /* Fetch our copy if examine_copy_inheritance() didn't do it for us. */
+ if (! copy)
+ SVN_ERR(svn_fs_bdb__get_copy(&copy, fs, copy_id, trail, trail->pool));
+
+ /* Figure out the destination path and revision of the copy operation. */
+ SVN_ERR(svn_fs_base__dag_get_node(&copy_dst_node, fs, copy->dst_noderev_id,
+ trail, trail->pool));
+ copy_dst_path = svn_fs_base__dag_get_created_path(copy_dst_node);
+ SVN_ERR(svn_fs_base__dag_get_revision(&copy_dst_rev, copy_dst_node,
+ trail, trail->pool));
+
+ /* Turn that revision into a revision root. */
+ SVN_ERR(svn_fs_base__dag_revision_root(&copy_dst_root_node, fs,
+ copy_dst_rev, trail, args->pool));
+ copy_dst_root = make_revision_root(fs, copy_dst_rev,
+ copy_dst_root_node, args->pool);
+
+ /* It is possible that this node was created from scratch at some
+ revision between COPY_DST_REV and the transaction associated with
+ our ROOT. Make sure that PATH exists as of COPY_DST_REV and is
+ related to this node-rev. */
+ err = get_dag(&path_node_in_copy_dst, copy_dst_root, path,
+ trail, trail->pool);
+ if (err)
+ {
+ if ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+ }
+ if ((svn_fs_base__dag_node_kind(path_node_in_copy_dst) == svn_node_none)
+ || (! (svn_fs_base__id_check_related
+ (node_id, svn_fs_base__dag_get_id(path_node_in_copy_dst)))))
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* One final check must be done here. If you copy a directory and
+ create a new entity somewhere beneath that directory in the same
+ txn, then we can't claim that the copy affected the new entity.
+ For example, if you do:
+
+ copy dir1 dir2
+ create dir2/new-thing
+ commit
+
+ then dir2/new-thing was not affected by the copy of dir1 to dir2.
+ We detect this situation by asking if PATH@COPY_DST_REV's
+ created-rev is COPY_DST_REV, and that node-revision has no
+ predecessors, then there is no relevant closest copy.
+ */
+ SVN_ERR(svn_fs_base__dag_get_revision(&created_rev, path_node_in_copy_dst,
+ trail, trail->pool));
+ if (created_rev == copy_dst_rev)
+ {
+ const svn_fs_id_t *pred_id;
+ SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id,
+ path_node_in_copy_dst,
+ trail, trail->pool));
+ if (! pred_id)
+ return SVN_NO_ERROR;
+ }
+
+ *(args->path_p) = apr_pstrdup(args->pool, copy_dst_path);
+ *(args->root_p) = copy_dst_root;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_closest_copy(svn_fs_root_t **root_p,
+ const char **path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct closest_copy_args args;
+ svn_fs_t *fs = root->fs;
+ svn_fs_root_t *closest_root = NULL;
+ const char *closest_path = NULL;
+
+ args.root_p = &closest_root;
+ args.path_p = &closest_path;
+ args.root = root;
+ args.path = path;
+ args.pool = pool;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_closest_copy, &args,
+ FALSE, pool));
+ *root_p = closest_root;
+ *path_p = closest_path;
+ return SVN_NO_ERROR;
+}
+
+
+/* Return a new history object (marked as "interesting") for PATH and
+ REVISION, allocated in POOL, and with its members set to the values
+ of the parameters provided. Note that PATH and PATH_HINT are not
+ duped into POOL -- it is the responsibility of the caller to ensure
+ that this happens. */
+static svn_fs_history_t *
+assemble_history(svn_fs_t *fs,
+ const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t is_interesting,
+ const char *path_hint,
+ svn_revnum_t rev_hint,
+ apr_pool_t *pool)
+{
+ svn_fs_history_t *history = apr_pcalloc(pool, sizeof(*history));
+ base_history_data_t *bhd = apr_pcalloc(pool, sizeof(*bhd));
+ bhd->path = path;
+ bhd->revision = revision;
+ bhd->is_interesting = is_interesting;
+ bhd->path_hint = path_hint;
+ bhd->rev_hint = rev_hint;
+ bhd->fs = fs;
+ history->vtable = &history_vtable;
+ history->fsap_data = bhd;
+ return history;
+}
+
+
+svn_error_t *
+svn_fs_base__get_path_kind(svn_node_kind_t *kind,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_revnum_t head_rev;
+ svn_fs_root_t *root;
+ dag_node_t *root_dir, *path_node;
+ svn_error_t *err;
+
+ /* Get HEAD revision, */
+ SVN_ERR(svn_fs_bdb__youngest_rev(&head_rev, trail->fs, trail, pool));
+
+ /* Then convert it into a root_t, */
+ SVN_ERR(svn_fs_base__dag_revision_root(&root_dir, trail->fs, head_rev,
+ trail, pool));
+ root = make_revision_root(trail->fs, head_rev, root_dir, pool);
+
+ /* And get the dag_node for path in the root_t. */
+ err = get_dag(&path_node, root, path, trail, pool);
+ if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND))
+ {
+ svn_error_clear(err);
+ *kind = svn_node_none;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ *kind = svn_fs_base__dag_node_kind(path_node);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__get_path_created_rev(svn_revnum_t *rev,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_revnum_t head_rev, created_rev;
+ svn_fs_root_t *root;
+ dag_node_t *root_dir, *path_node;
+ svn_error_t *err;
+
+ /* Get HEAD revision, */
+ SVN_ERR(svn_fs_bdb__youngest_rev(&head_rev, trail->fs, trail, pool));
+
+ /* Then convert it into a root_t, */
+ SVN_ERR(svn_fs_base__dag_revision_root(&root_dir, trail->fs, head_rev,
+ trail, pool));
+ root = make_revision_root(trail->fs, head_rev, root_dir, pool);
+
+ /* And get the dag_node for path in the root_t. */
+ err = get_dag(&path_node, root, path, trail, pool);
+ if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND))
+ {
+ svn_error_clear(err);
+ *rev = SVN_INVALID_REVNUM;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ /* Find the created_rev of the dag_node. */
+ SVN_ERR(svn_fs_base__dag_get_revision(&created_rev, path_node,
+ trail, pool));
+
+ *rev = created_rev;
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Finding the Origin of a Line of History ***/
+
+/* Set *PREV_PATH and *PREV_REV to the path and revision which
+ represent the location at which PATH in FS was located immediately
+ prior to REVISION iff there was a copy operation (to PATH or one of
+ its parent directories) between that previous location and
+ PATH@REVISION.
+
+ If there was no such copy operation in that portion of PATH's
+ history, set *PREV_PATH to NULL and *PREV_REV to SVN_INVALID_REVNUM.
+
+ WARNING: Do *not* call this from inside a trail. */
+static svn_error_t *
+prev_location(const char **prev_path,
+ svn_revnum_t *prev_rev,
+ svn_fs_t *fs,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *copy_path, *copy_src_path, *remainder;
+ svn_fs_root_t *copy_root;
+ svn_revnum_t copy_src_rev;
+
+ /* Ask about the most recent copy which affected PATH@REVISION. If
+ there was no such copy, we're done. */
+ SVN_ERR(base_closest_copy(&copy_root, &copy_path, root, path, pool));
+ if (! copy_root)
+ {
+ *prev_rev = SVN_INVALID_REVNUM;
+ *prev_path = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Ultimately, it's not the path of the closest copy's source that
+ we care about -- it's our own path's location in the copy source
+ revision. So we'll tack the relative path that expresses the
+ difference between the copy destination and our path in the copy
+ revision onto the copy source path to determine this information.
+
+ In other words, if our path is "/branches/my-branch/foo/bar", and
+ we know that the closest relevant copy was a copy of "/trunk" to
+ "/branches/my-branch", then that relative path under the copy
+ destination is "/foo/bar". Tacking that onto the copy source
+ path tells us that our path was located at "/trunk/foo/bar"
+ before the copy.
+ */
+ SVN_ERR(base_copied_from(&copy_src_rev, &copy_src_path,
+ copy_root, copy_path, pool));
+ remainder = svn_fspath__skip_ancestor(copy_path, path);
+ *prev_path = svn_fspath__join(copy_src_path, remainder, pool);
+ *prev_rev = copy_src_rev;
+ return SVN_NO_ERROR;
+}
+
+
+struct id_created_rev_args {
+ svn_revnum_t revision;
+ const svn_fs_id_t *id;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_id_created_rev(void *baton, trail_t *trail)
+{
+ struct id_created_rev_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id,
+ trail, trail->pool));
+ return svn_fs_base__dag_get_revision(&(args->revision), node,
+ trail, trail->pool);
+}
+
+
+struct get_set_node_origin_args {
+ const svn_fs_id_t *origin_id;
+ const char *node_id;
+};
+
+
+static svn_error_t *
+txn_body_get_node_origin(void *baton, trail_t *trail)
+{
+ struct get_set_node_origin_args *args = baton;
+ return svn_fs_bdb__get_node_origin(&(args->origin_id), trail->fs,
+ args->node_id, trail, trail->pool);
+}
+
+static svn_error_t *
+txn_body_set_node_origin(void *baton, trail_t *trail)
+{
+ struct get_set_node_origin_args *args = baton;
+ return svn_fs_bdb__set_node_origin(trail->fs, args->node_id,
+ args->origin_id, trail, trail->pool);
+}
+
+static svn_error_t *
+base_node_origin_rev(svn_revnum_t *revision,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = root->fs;
+ base_fs_data_t *bfd = fs->fsap_data;
+ struct get_set_node_origin_args args;
+ const svn_fs_id_t *origin_id = NULL;
+ struct id_created_rev_args icr_args;
+
+ /* Canonicalize the input path so that the path-math that
+ prev_location() does below will work. */
+ path = svn_fs__canonicalize_abspath(path, pool);
+
+ /* If we have support for the node-origins table, we'll try to use
+ it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT)
+ {
+ const svn_fs_id_t *id;
+ svn_error_t *err;
+
+ SVN_ERR(base_node_id(&id, root, path, pool));
+ args.node_id = svn_fs_base__id_node_id(id);
+ err = svn_fs_base__retry_txn(root->fs, txn_body_get_node_origin, &args,
+ FALSE, pool);
+
+ /* If we got a value for the origin node-revision-ID, that's
+ great. If we didn't, that's sad but non-fatal -- we'll just
+ figure it out the hard way, then record it so we don't have
+ suffer again the next time. */
+ if (! err)
+ {
+ origin_id = args.origin_id;
+ }
+ else if (err->apr_err == SVN_ERR_FS_NO_SUCH_NODE_ORIGIN)
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+ }
+
+ /* If we haven't yet found a node origin ID, we'll go spelunking for one. */
+ if (! origin_id)
+ {
+ svn_fs_root_t *curroot = root;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_pool_t *predidpool = svn_pool_create(pool);
+ svn_stringbuf_t *lastpath =
+ svn_stringbuf_create(path, pool);
+ svn_revnum_t lastrev = SVN_INVALID_REVNUM;
+ const svn_fs_id_t *pred_id;
+
+ /* Walk the closest-copy chain back to the first copy in our history.
+
+ NOTE: We merely *assume* that this is faster than walking the
+ predecessor chain, because we *assume* that copies of parent
+ directories happen less often than modifications to a given item. */
+ while (1)
+ {
+ svn_revnum_t currev;
+ const char *curpath = lastpath->data;
+
+ /* Get a root pointing to LASTREV. (The first time around,
+ LASTREV is invalid, but that's cool because CURROOT is
+ already initialized.) */
+ if (SVN_IS_VALID_REVNUM(lastrev))
+ SVN_ERR(svn_fs_base__revision_root(&curroot, fs,
+ lastrev, subpool));
+
+ /* Find the previous location using the closest-copy shortcut. */
+ SVN_ERR(prev_location(&curpath, &currev, fs, curroot,
+ curpath, subpool));
+ if (! curpath)
+ break;
+
+ /* Update our LASTPATH and LASTREV variables (which survive
+ SUBPOOL). */
+ svn_stringbuf_set(lastpath, curpath);
+ lastrev = currev;
+ }
+
+ /* Walk the predecessor links back to origin. */
+ SVN_ERR(base_node_id(&pred_id, curroot, lastpath->data, pool));
+ while (1)
+ {
+ struct txn_pred_id_args pid_args;
+ svn_pool_clear(subpool);
+ pid_args.id = pred_id;
+ pid_args.pred_id = NULL;
+ pid_args.pool = subpool;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_id, &pid_args,
+ FALSE, subpool));
+ if (! pid_args.pred_id)
+ break;
+ svn_pool_clear(predidpool);
+ pred_id = svn_fs_base__id_copy(pid_args.pred_id, predidpool);
+ }
+
+ /* Okay. PRED_ID should hold our origin ID now. */
+ origin_id = svn_fs_base__id_copy(pred_id, pool);
+
+ /* If our filesystem version supports it, let's remember this
+ value from now on. */
+ if (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT)
+ {
+ args.origin_id = origin_id;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_set_node_origin,
+ &args, TRUE, subpool));
+ }
+
+ svn_pool_destroy(predidpool);
+ svn_pool_destroy(subpool);
+ }
+
+ /* Okay. We have an origin node-revision-ID. Let's get a created
+ revision from it. */
+ icr_args.id = origin_id;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_id_created_rev, &icr_args,
+ TRUE, pool));
+ *revision = icr_args.revision;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Mergeinfo Queries */
+
+
+/* Examine directory NODE's immediately children for mergeinfo.
+
+ For those which have explicit mergeinfo, add their mergeinfo to
+ RESULT_CATALOG (allocated in RESULT_CATALOG's pool).
+
+ For those which don't, but sit atop trees which contain mergeinfo
+ somewhere deeper, add them to *CHILDREN_ATOP_MERGEINFO_TREES, a
+ hash mapping dirent names to dag_node_t * objects, allocated
+ from that hash's pool.
+
+ For those which neither have explicit mergeinfo nor sit atop trees
+ which contain mergeinfo, ignore them.
+
+ Use TRAIL->pool for temporary allocations. */
+
+struct get_mergeinfo_data_and_entries_baton
+{
+ svn_mergeinfo_catalog_t result_catalog;
+ apr_hash_t *children_atop_mergeinfo_trees;
+ dag_node_t *node;
+ const char *node_path;
+};
+
+static svn_error_t *
+txn_body_get_mergeinfo_data_and_entries(void *baton, trail_t *trail)
+{
+ struct get_mergeinfo_data_and_entries_baton *args = baton;
+ dag_node_t *node = args->node;
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(trail->pool);
+ apr_pool_t *result_pool = apr_hash_pool_get(args->result_catalog);
+ apr_pool_t *children_pool =
+ apr_hash_pool_get(args->children_atop_mergeinfo_trees);
+
+ SVN_ERR_ASSERT(svn_fs_base__dag_node_kind(node) == svn_node_dir);
+
+ SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, trail->pool));
+ for (hi = apr_hash_first(trail->pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ void *val;
+ svn_fs_dirent_t *dirent;
+ const svn_fs_id_t *child_id;
+ dag_node_t *child_node;
+ svn_boolean_t has_mergeinfo;
+ apr_int64_t kid_count;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, NULL, NULL, &val);
+ dirent = val;
+ child_id = dirent->id;
+
+ /* Get the node for this child. */
+ SVN_ERR(svn_fs_base__dag_get_node(&child_node, trail->fs, child_id,
+ trail, iterpool));
+
+ /* Query the child node's mergeinfo stats. */
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(&has_mergeinfo, &kid_count,
+ child_node, trail,
+ iterpool));
+
+ /* If the child has mergeinfo, add it to the result catalog. */
+ if (has_mergeinfo)
+ {
+ apr_hash_t *plist;
+ svn_mergeinfo_t child_mergeinfo;
+ svn_string_t *pval;
+ svn_error_t *err;
+
+ SVN_ERR(svn_fs_base__dag_get_proplist(&plist, child_node,
+ trail, iterpool));
+ pval = svn_hash_gets(plist, SVN_PROP_MERGEINFO);
+ if (! pval)
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(child_id,
+ iterpool);
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Node-revision '%s' claims to have "
+ "mergeinfo but doesn't"),
+ id_str->data);
+ }
+ /* Issue #3896: If syntactically invalid mergeinfo is present on
+ CHILD_NODE then treat it as if no mergeinfo is present rather
+ than raising a parse error. */
+ err = svn_mergeinfo_parse(&child_mergeinfo, pval->data,
+ result_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ svn_hash_sets(args->result_catalog,
+ svn_fspath__join(args->node_path, dirent->name,
+ result_pool),
+ child_mergeinfo);
+ }
+ }
+
+ /* If the child has descendants with mergeinfo -- that is, if
+ the count of descendants beneath it carrying mergeinfo, not
+ including itself, is non-zero -- then add it to the
+ children_atop_mergeinfo_trees hash to be crawled later. */
+ if ((kid_count - (has_mergeinfo ? 1 : 0)) > 0)
+ {
+ if (svn_fs_base__dag_node_kind(child_node) != svn_node_dir)
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(child_id,
+ iterpool);
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Node-revision '%s' claims to sit "
+ "atop a tree containing mergeinfo "
+ "but is not a directory"),
+ id_str->data);
+ }
+ svn_hash_sets(args->children_atop_mergeinfo_trees,
+ apr_pstrdup(children_pool, dirent->name),
+ svn_fs_base__dag_dup(child_node, children_pool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+crawl_directory_for_mergeinfo(svn_fs_t *fs,
+ dag_node_t *node,
+ const char *node_path,
+ svn_mergeinfo_catalog_t result_catalog,
+ apr_pool_t *pool)
+{
+ struct get_mergeinfo_data_and_entries_baton gmdae_args;
+ apr_hash_t *children_atop_mergeinfo_trees = apr_hash_make(pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ /* Add mergeinfo for immediate children that have it, and fetch
+ immediate children that *don't* have it but sit atop trees that do. */
+ gmdae_args.result_catalog = result_catalog;
+ gmdae_args.children_atop_mergeinfo_trees = children_atop_mergeinfo_trees;
+ gmdae_args.node = node;
+ gmdae_args.node_path = node_path;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_mergeinfo_data_and_entries,
+ &gmdae_args, FALSE, pool));
+
+ /* If no children sit atop trees with mergeinfo, we're done.
+ Otherwise, recurse on those children. */
+
+ if (apr_hash_count(children_atop_mergeinfo_trees) == 0)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, children_atop_mergeinfo_trees);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ SVN_ERR(crawl_directory_for_mergeinfo(fs, val,
+ svn_fspath__join(node_path, key,
+ iterpool),
+ result_catalog, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Calculate the mergeinfo for PATH under revision ROOT using
+ inheritance type INHERIT. Set *MERGEINFO to the mergeinfo, or to
+ NULL if there is none. Results are allocated in POOL; TRAIL->pool
+ is used for temporary allocations. */
+
+struct get_mergeinfo_for_path_baton
+{
+ svn_mergeinfo_t *mergeinfo;
+ svn_fs_root_t *root;
+ const char *path;
+ svn_mergeinfo_inheritance_t inherit;
+ svn_boolean_t adjust_inherited_mergeinfo;
+ apr_pool_t *pool;
+};
+
+static svn_error_t *
+txn_body_get_mergeinfo_for_path(void *baton, trail_t *trail)
+{
+ struct get_mergeinfo_for_path_baton *args = baton;
+ parent_path_t *parent_path, *nearest_ancestor;
+ apr_hash_t *proplist;
+ svn_string_t *mergeinfo_string;
+ apr_pool_t *iterpool;
+ dag_node_t *node = NULL;
+
+ *(args->mergeinfo) = NULL;
+
+ SVN_ERR(open_path(&parent_path, args->root, args->path, 0,
+ NULL, trail, trail->pool));
+
+ /* Init the nearest ancestor. */
+ nearest_ancestor = parent_path;
+ if (args->inherit == svn_mergeinfo_nearest_ancestor)
+ {
+ if (! parent_path->parent)
+ return SVN_NO_ERROR;
+ nearest_ancestor = parent_path->parent;
+ }
+
+ iterpool = svn_pool_create(trail->pool);
+ while (TRUE)
+ {
+ svn_boolean_t has_mergeinfo;
+ apr_int64_t count;
+
+ svn_pool_clear(iterpool);
+
+ node = nearest_ancestor->node;
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(&has_mergeinfo, &count,
+ node, trail, iterpool));
+ if (has_mergeinfo)
+ break;
+
+ /* No need to loop if we're looking for explicit mergeinfo. */
+ if (args->inherit == svn_mergeinfo_explicit)
+ {
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+
+ nearest_ancestor = nearest_ancestor->parent;
+
+ /* Run out? There's no mergeinfo. */
+ if (! nearest_ancestor)
+ {
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, node, trail, trail->pool));
+ mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
+ if (! mergeinfo_string)
+ {
+ svn_string_t *id_str =
+ svn_fs_base__id_unparse(svn_fs_base__dag_get_id(node), trail->pool);
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Node-revision '%s' claims to have "
+ "mergeinfo but doesn't"), id_str->data);
+ }
+
+ /* Parse the mergeinfo; store the result in ARGS->MERGEINFO. */
+ {
+ /* Issue #3896: If a node has syntactically invalid mergeinfo, then
+ treat it as if no mergeinfo is present rather than raising a parse
+ error. */
+ svn_error_t *err = svn_mergeinfo_parse(args->mergeinfo,
+ mergeinfo_string->data,
+ args->pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ svn_error_clear(err);
+ err = NULL;
+ args->mergeinfo = NULL;
+ }
+ return svn_error_trace(err);
+ }
+ }
+
+ /* If our nearest ancestor is the very path we inquired about, we
+ can return the mergeinfo results directly. Otherwise, we're
+ inheriting the mergeinfo, so we need to a) remove non-inheritable
+ ranges and b) telescope the merged-from paths. */
+ if (args->adjust_inherited_mergeinfo && (nearest_ancestor != parent_path))
+ {
+ svn_mergeinfo_t tmp_mergeinfo;
+
+ SVN_ERR(svn_mergeinfo_inheritable2(&tmp_mergeinfo, *args->mergeinfo,
+ NULL, SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM, TRUE,
+ trail->pool, trail->pool));
+ SVN_ERR(svn_fs__append_to_merged_froms(args->mergeinfo, tmp_mergeinfo,
+ parent_path_relpath(
+ parent_path, nearest_ancestor,
+ trail->pool),
+ args->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set **NODE to the dag node for PATH in ROOT (allocated in POOL),
+ and query its mergeinfo stats, setting HAS_MERGEINFO and
+ CHILD_MERGEINFO_COUNT appropriately. */
+
+struct get_node_mergeinfo_stats_baton
+{
+ dag_node_t *node;
+ svn_boolean_t has_mergeinfo;
+ apr_int64_t child_mergeinfo_count;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+static svn_error_t *
+txn_body_get_node_mergeinfo_stats(void *baton, trail_t *trail)
+{
+ struct get_node_mergeinfo_stats_baton *args = baton;
+
+ SVN_ERR(get_dag(&(args->node), args->root, args->path,
+ trail, trail->pool));
+ return svn_fs_base__dag_get_mergeinfo_stats(&(args->has_mergeinfo),
+ &(args->child_mergeinfo_count),
+ args->node, trail,
+ trail->pool);
+}
+
+
+/* Get the mergeinfo for a set of paths, returned in
+ *MERGEINFO_CATALOG. Returned values are allocated in POOL, while
+ temporary values are allocated in a sub-pool. */
+static svn_error_t *
+get_mergeinfos_for_paths(svn_fs_root_t *root,
+ svn_mergeinfo_catalog_t *mergeinfo_catalog,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_catalog_t result_catalog = apr_hash_make(result_pool);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ svn_mergeinfo_t path_mergeinfo;
+ struct get_mergeinfo_for_path_baton gmfp_args;
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ path = svn_fs__canonicalize_abspath(path, iterpool);
+
+ /* Get the mergeinfo for PATH itself. */
+ gmfp_args.mergeinfo = &path_mergeinfo;
+ gmfp_args.root = root;
+ gmfp_args.path = path;
+ gmfp_args.inherit = inherit;
+ gmfp_args.pool = result_pool;
+ gmfp_args.adjust_inherited_mergeinfo = adjust_inherited_mergeinfo;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs,
+ txn_body_get_mergeinfo_for_path,
+ &gmfp_args, FALSE, iterpool));
+ if (path_mergeinfo)
+ svn_hash_sets(result_catalog, apr_pstrdup(result_pool, path),
+ path_mergeinfo);
+
+ /* If we're including descendants, do so. */
+ if (include_descendants)
+ {
+ svn_boolean_t do_crawl;
+ struct get_node_mergeinfo_stats_baton gnms_args;
+
+ /* Query the node and its mergeinfo stats. */
+ gnms_args.root = root;
+ gnms_args.path = path;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs,
+ txn_body_get_node_mergeinfo_stats,
+ &gnms_args, FALSE, iterpool));
+
+ /* Determine if there's anything worth crawling here. */
+ if (svn_fs_base__dag_node_kind(gnms_args.node) != svn_node_dir)
+ do_crawl = FALSE;
+ else
+ do_crawl = ((gnms_args.child_mergeinfo_count > 1)
+ || ((gnms_args.child_mergeinfo_count == 1)
+ && (! gnms_args.has_mergeinfo)));
+
+ /* If it's worth crawling, crawl. */
+ if (do_crawl)
+ SVN_ERR(crawl_directory_for_mergeinfo(root->fs, gnms_args.node,
+ path, result_catalog,
+ iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ *mergeinfo_catalog = result_catalog;
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_fs_get_mergeinfo. */
+static svn_error_t *
+base_get_mergeinfo(svn_mergeinfo_catalog_t *catalog,
+ svn_fs_root_t *root,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* Verify that our filesystem version supports mergeinfo stuff. */
+ SVN_ERR(svn_fs_base__test_required_feature_format
+ (root->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
+
+ /* We require a revision root. */
+ if (root->is_txn_root)
+ return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
+
+ /* Retrieve a path -> mergeinfo mapping. */
+ return get_mergeinfos_for_paths(root, catalog, paths,
+ inherit, include_descendants,
+ adjust_inherited_mergeinfo,
+ result_pool, scratch_pool);
+}
+
+
+
+/* Creating root objects. */
+
+
+static root_vtable_t root_vtable = {
+ base_paths_changed,
+ base_check_path,
+ base_node_history,
+ base_node_id,
+ base_node_created_rev,
+ base_node_origin_rev,
+ base_node_created_path,
+ base_delete_node,
+ base_copied_from,
+ base_closest_copy,
+ base_node_prop,
+ base_node_proplist,
+ base_change_node_prop,
+ base_props_changed,
+ base_dir_entries,
+ base_make_dir,
+ base_copy,
+ base_revision_link,
+ base_file_length,
+ base_file_checksum,
+ base_file_contents,
+ NULL,
+ base_make_file,
+ base_apply_textdelta,
+ base_apply_text,
+ base_contents_changed,
+ base_get_file_delta_stream,
+ base_merge,
+ base_get_mergeinfo,
+};
+
+
+/* Construct a new root object in FS, allocated from POOL. */
+static svn_fs_root_t *
+make_root(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root = apr_pcalloc(pool, sizeof(*root));
+ base_root_data_t *brd = apr_palloc(pool, sizeof(*brd));
+
+ root->fs = fs;
+ root->pool = pool;
+
+ /* Init the node ID cache. */
+ brd->node_cache = apr_hash_make(pool);
+ brd->node_cache_idx = 0;
+ root->vtable = &root_vtable;
+ root->fsap_data = brd;
+
+ return root;
+}
+
+
+/* Construct a root object referring to the root of REVISION in FS,
+ whose root directory is ROOT_DIR. Create the new root in POOL. */
+static svn_fs_root_t *
+make_revision_root(svn_fs_t *fs,
+ svn_revnum_t rev,
+ dag_node_t *root_dir,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root = make_root(fs, pool);
+ base_root_data_t *brd = root->fsap_data;
+
+ root->is_txn_root = FALSE;
+ root->rev = rev;
+ brd->root_dir = root_dir;
+
+ return root;
+}
+
+
+/* Construct a root object referring to the root of the transaction
+ named TXN and based on revision BASE_REV in FS. FLAGS represents
+ the behavior of the transaction. Create the new root in POOL. */
+static svn_fs_root_t *
+make_txn_root(svn_fs_t *fs,
+ const char *txn,
+ svn_revnum_t base_rev,
+ apr_uint32_t flags,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root = make_root(fs, pool);
+ root->is_txn_root = TRUE;
+ root->txn = apr_pstrdup(root->pool, txn);
+ root->txn_flags = flags;
+ root->rev = base_rev;
+
+ return root;
+}
OpenPOWER on IntegriCloud