summaryrefslogtreecommitdiff
path: root/subversion/libsvn_fs_x/dag.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_x/dag.c')
-rw-r--r--subversion/libsvn_fs_x/dag.c1368
1 files changed, 1368 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_x/dag.c b/subversion/libsvn_fs_x/dag.c
new file mode 100644
index 0000000..2f5bcb2
--- /dev/null
+++ b/subversion/libsvn_fs_x/dag.c
@@ -0,0 +1,1368 @@
+/* dag.c : DAG-like interface filesystem, private to libsvn_fs
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#include <string.h>
+
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_fs.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+
+#include "dag.h"
+#include "fs.h"
+#include "fs_x.h"
+#include "fs_id.h"
+#include "cached_data.h"
+#include "transaction.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "private/svn_fspath.h"
+#include "svn_private_config.h"
+#include "private/svn_temp_serializer.h"
+#include "temp_serializer.h"
+
+
+/* Initializing a filesystem. */
+
+struct dag_node_t
+{
+ /* The filesystem this dag node came from. */
+ svn_fs_t *fs;
+
+ /* The node revision ID for this dag node. */
+ svn_fs_x__id_t id;
+
+ /* In the special case that this node is the root of a transaction
+ that has not yet been modified, the revision of this node is the
+ respective txn's base rev. Otherwise, this is SVN_INVALID_REVNUM
+ for txn nodes and the respective crev for committed nodes.
+ (Used in svn_fs_node_created_rev.) */
+ svn_revnum_t revision;
+
+ /* The node's type (file, dir, etc.) */
+ svn_node_kind_t kind;
+
+ /* The node's NODE-REVISION, or NULL if we haven't read it in yet.
+ This is allocated in this node's POOL.
+
+ If you're willing to respect all the rules above, you can munge
+ this yourself, but you're probably better off just calling
+ `get_node_revision' and `set_node_revision', which take care of
+ things for you. */
+ svn_fs_x__noderev_t *node_revision;
+
+ /* The pool to allocate NODE_REVISION in. */
+ apr_pool_t *node_pool;
+
+ /* the path at which this node was created. */
+ const char *created_path;
+
+ /* Directory entry lookup hint to speed up consecutive calls to
+ svn_fs_x__rep_contents_dir_entry(). Only used for directory nodes.
+ Any value is legal but should default to APR_SIZE_MAX. */
+ apr_size_t hint;
+};
+
+
+
+/* Trivial helper/accessor functions. */
+svn_node_kind_t
+svn_fs_x__dag_node_kind(dag_node_t *node)
+{
+ return node->kind;
+}
+
+const svn_fs_x__id_t *
+svn_fs_x__dag_get_id(const dag_node_t *node)
+{
+ return &node->id;
+}
+
+
+const char *
+svn_fs_x__dag_get_created_path(dag_node_t *node)
+{
+ return node->created_path;
+}
+
+
+svn_fs_t *
+svn_fs_x__dag_get_fs(dag_node_t *node)
+{
+ return node->fs;
+}
+
+void
+svn_fs_x__dag_set_fs(dag_node_t *node,
+ svn_fs_t *fs)
+{
+ node->fs = fs;
+}
+
+
+/* Dup NODEREV and all associated data into RESULT_POOL.
+ Leaves the id and is_fresh_txn_root fields as zero bytes. */
+static svn_fs_x__noderev_t *
+copy_node_revision(svn_fs_x__noderev_t *noderev,
+ apr_pool_t *result_pool)
+{
+ svn_fs_x__noderev_t *nr = apr_pmemdup(result_pool, noderev,
+ sizeof(*noderev));
+
+ if (noderev->copyfrom_path)
+ nr->copyfrom_path = apr_pstrdup(result_pool, noderev->copyfrom_path);
+
+ nr->copyroot_path = apr_pstrdup(result_pool, noderev->copyroot_path);
+ nr->data_rep = svn_fs_x__rep_copy(noderev->data_rep, result_pool);
+ nr->prop_rep = svn_fs_x__rep_copy(noderev->prop_rep, result_pool);
+
+ if (noderev->created_path)
+ nr->created_path = apr_pstrdup(result_pool, noderev->created_path);
+
+ return nr;
+}
+
+
+/* Set *NODEREV_P to the cached node-revision for NODE.
+ If the node-revision was not already cached in NODE, read it in,
+ allocating the cache in NODE->NODE_POOL.
+
+ If you plan to change the contents of NODE, be careful! We're
+ handing you a pointer directly to our cached node-revision, not
+ your own copy. If you change it as part of some operation, but
+ then some Berkeley DB function deadlocks or gets an error, you'll
+ need to back out your changes, or else the cache will reflect
+ changes that never got committed. It's probably best not to change
+ the structure at all. */
+static svn_error_t *
+get_node_revision(svn_fs_x__noderev_t **noderev_p,
+ dag_node_t *node)
+{
+ /* If we've already got a copy, there's no need to read it in. */
+ if (! node->node_revision)
+ {
+ svn_fs_x__noderev_t *noderev;
+ apr_pool_t *scratch_pool = svn_pool_create(node->node_pool);
+
+ SVN_ERR(svn_fs_x__get_node_revision(&noderev, node->fs, &node->id,
+ node->node_pool, scratch_pool));
+ node->node_revision = noderev;
+ svn_pool_destroy(scratch_pool);
+ }
+
+ /* Now NODE->node_revision is set. */
+ *noderev_p = node->node_revision;
+ return SVN_NO_ERROR;
+}
+
+/* Return the node revision ID of NODE. The value returned is shared
+ with NODE, and will be deallocated when NODE is. */
+svn_error_t *
+svn_fs_x__dag_get_node_id(svn_fs_x__id_t *node_id,
+ dag_node_t *node)
+{
+ svn_fs_x__noderev_t *noderev;
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ *node_id = noderev->node_id;
+ return SVN_NO_ERROR;
+}
+
+/* Return the node revision ID of NODE. The value returned is shared
+ with NODE, and will be deallocated when NODE is. */
+svn_error_t *
+svn_fs_x__dag_get_copy_id(svn_fs_x__id_t *copy_id,
+ dag_node_t *node)
+{
+ svn_fs_x__noderev_t *noderev;
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ *copy_id = noderev->copy_id;
+ return SVN_NO_ERROR;
+}
+
+/* Return the node ID of NODE. The value returned is shared with NODE,
+ and will be deallocated when NODE is. */
+svn_error_t *
+svn_fs_x__dag_related_node(svn_boolean_t *same,
+ dag_node_t *lhs,
+ dag_node_t *rhs)
+{
+ svn_fs_x__id_t lhs_node, rhs_node;
+
+ SVN_ERR(svn_fs_x__dag_get_node_id(&lhs_node, lhs));
+ SVN_ERR(svn_fs_x__dag_get_node_id(&rhs_node, rhs));
+ *same = svn_fs_x__id_eq(&lhs_node, &rhs_node);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__dag_same_line_of_history(svn_boolean_t *same,
+ dag_node_t *lhs,
+ dag_node_t *rhs)
+{
+ svn_fs_x__noderev_t *lhs_noderev, *rhs_noderev;
+
+ SVN_ERR(get_node_revision(&lhs_noderev, lhs));
+ SVN_ERR(get_node_revision(&rhs_noderev, rhs));
+
+ *same = svn_fs_x__id_eq(&lhs_noderev->node_id, &rhs_noderev->node_id)
+ && svn_fs_x__id_eq(&lhs_noderev->copy_id, &rhs_noderev->copy_id);
+
+ return SVN_NO_ERROR;
+}
+
+svn_boolean_t
+svn_fs_x__dag_check_mutable(const dag_node_t *node)
+{
+ return svn_fs_x__is_txn(svn_fs_x__dag_get_id(node)->change_set);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_get_node(dag_node_t **node,
+ svn_fs_t *fs,
+ const svn_fs_x__id_t *id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ dag_node_t *new_node;
+ svn_fs_x__noderev_t *noderev;
+
+ /* Construct the node. */
+ new_node = apr_pcalloc(result_pool, sizeof(*new_node));
+ new_node->fs = fs;
+ new_node->id = *id;
+ new_node->hint = APR_SIZE_MAX;
+
+ /* Grab the contents so we can inspect the node's kind and created path. */
+ SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, id,
+ result_pool, scratch_pool));
+ new_node->node_pool = result_pool;
+ new_node->node_revision = noderev;
+
+ /* Initialize the KIND and CREATED_PATH attributes */
+ new_node->kind = noderev->kind;
+ new_node->created_path = noderev->created_path;
+
+ /* Support our quirky svn_fs_node_created_rev API.
+ Untouched txn roots report the base rev as theirs. */
+ new_node->revision
+ = ( svn_fs_x__is_fresh_txn_root(noderev)
+ ? svn_fs_x__get_revnum(noderev->predecessor_id.change_set)
+ : svn_fs_x__get_revnum(id->change_set));
+
+ /* Return a fresh new node */
+ *node = new_node;
+ return SVN_NO_ERROR;
+}
+
+
+svn_revnum_t
+svn_fs_x__dag_get_revision(const dag_node_t *node)
+{
+ return node->revision;
+}
+
+
+svn_error_t *
+svn_fs_x__dag_get_predecessor_id(svn_fs_x__id_t *id_p,
+ dag_node_t *node)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ SVN_ERR(get_node_revision(&noderev, node));
+ *id_p = noderev->predecessor_id;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_x__dag_get_predecessor_count(int *count,
+ dag_node_t *node)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ SVN_ERR(get_node_revision(&noderev, node));
+ *count = noderev->predecessor_count;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__dag_get_mergeinfo_count(apr_int64_t *count,
+ dag_node_t *node)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ SVN_ERR(get_node_revision(&noderev, node));
+ *count = noderev->mergeinfo_count;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__dag_has_mergeinfo(svn_boolean_t *has_mergeinfo,
+ dag_node_t *node)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ SVN_ERR(get_node_revision(&noderev, node));
+ *has_mergeinfo = noderev->has_mergeinfo;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__dag_has_descendants_with_mergeinfo(svn_boolean_t *do_they,
+ dag_node_t *node)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ if (node->kind != svn_node_dir)
+ {
+ *do_they = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(get_node_revision(&noderev, node));
+ if (noderev->mergeinfo_count > 1)
+ *do_they = TRUE;
+ else if (noderev->mergeinfo_count == 1 && !noderev->has_mergeinfo)
+ *do_they = TRUE;
+ else
+ *do_they = FALSE;
+ return SVN_NO_ERROR;
+}
+
+
+/*** Directory node functions ***/
+
+/* Some of these are helpers for functions outside this section. */
+
+/* Set *ID_P to the noderev-id for entry NAME in PARENT. If no such
+ entry, set *ID_P to NULL but do not error. */
+static svn_error_t *
+dir_entry_id_from_node(svn_fs_x__id_t *id_p,
+ dag_node_t *parent,
+ const char *name,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__dirent_t *dirent;
+ svn_fs_x__noderev_t *noderev;
+
+ SVN_ERR(get_node_revision(&noderev, parent));
+ if (noderev->kind != svn_node_dir)
+ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Can't get entries of non-directory"));
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ "Attempted to open node with an illegal name '%s'", name);
+
+ /* Get a dirent hash for this directory. */
+ SVN_ERR(svn_fs_x__rep_contents_dir_entry(&dirent, parent->fs, noderev,
+ name, &parent->hint,
+ scratch_pool, scratch_pool));
+ if (dirent)
+ *id_p = dirent->id;
+ else
+ svn_fs_x__id_reset(id_p);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Add or set in PARENT a directory entry NAME pointing to ID.
+ Temporary allocations are done in SCRATCH_POOL.
+
+ Assumptions:
+ - PARENT is a mutable directory.
+ - ID does not refer to an ancestor of parent
+ - NAME is a single path component
+*/
+static svn_error_t *
+set_entry(dag_node_t *parent,
+ const char *name,
+ const svn_fs_x__id_t *id,
+ svn_node_kind_t kind,
+ svn_fs_x__txn_id_t txn_id,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__noderev_t *parent_noderev;
+
+ /* Get the parent's node-revision. */
+ SVN_ERR(get_node_revision(&parent_noderev, parent));
+
+ /* Set the new entry. */
+ return svn_fs_x__set_entry(parent->fs, txn_id, parent_noderev, name, id,
+ kind, parent->node_pool, scratch_pool);
+}
+
+
+/* Make a new entry named NAME in PARENT. If IS_DIR is true, then the
+ node revision the new entry points to will be a directory, else it
+ will be a file. The new node will be allocated in RESULT_POOL. PARENT
+ must be mutable, and must not have an entry named NAME.
+
+ Use SCRATCH_POOL for all temporary allocations.
+ */
+static svn_error_t *
+make_entry(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ svn_boolean_t is_dir,
+ svn_fs_x__txn_id_t txn_id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__noderev_t new_noderev, *parent_noderev;
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ _("Attempted to create a node with an illegal name '%s'"), name);
+
+ /* Make sure that parent is a directory */
+ if (parent->kind != svn_node_dir)
+ return svn_error_create
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Attempted to create entry in non-directory parent"));
+
+ /* Check that the parent is mutable. */
+ if (! svn_fs_x__dag_check_mutable(parent))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to clone child of non-mutable node"));
+
+ /* Create the new node's NODE-REVISION */
+ memset(&new_noderev, 0, sizeof(new_noderev));
+ new_noderev.kind = is_dir ? svn_node_dir : svn_node_file;
+ new_noderev.created_path = svn_fspath__join(parent_path, name, result_pool);
+
+ SVN_ERR(get_node_revision(&parent_noderev, parent));
+ new_noderev.copyroot_path = apr_pstrdup(result_pool,
+ parent_noderev->copyroot_path);
+ new_noderev.copyroot_rev = parent_noderev->copyroot_rev;
+ new_noderev.copyfrom_rev = SVN_INVALID_REVNUM;
+ new_noderev.copyfrom_path = NULL;
+ svn_fs_x__id_reset(&new_noderev.predecessor_id);
+
+ SVN_ERR(svn_fs_x__create_node
+ (svn_fs_x__dag_get_fs(parent), &new_noderev,
+ &parent_noderev->copy_id, txn_id, scratch_pool));
+
+ /* Create a new dag_node_t for our new node */
+ SVN_ERR(svn_fs_x__dag_get_node(child_p, svn_fs_x__dag_get_fs(parent),
+ &new_noderev.noderev_id, result_pool,
+ scratch_pool));
+
+ /* We can safely call set_entry because we already know that
+ PARENT is mutable, and we just created CHILD, so we know it has
+ no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */
+ return set_entry(parent, name, &new_noderev.noderev_id,
+ new_noderev.kind, txn_id, scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_dir_entries(apr_array_header_t **entries,
+ dag_node_t *node,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ if (noderev->kind != svn_node_dir)
+ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Can't get entries of non-directory"));
+
+ return svn_fs_x__rep_contents_dir(entries, node->fs, noderev, result_pool,
+ scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_set_entry(dag_node_t *node,
+ const char *entry_name,
+ const svn_fs_x__id_t *id,
+ svn_node_kind_t kind,
+ svn_fs_x__txn_id_t txn_id,
+ apr_pool_t *scratch_pool)
+{
+ /* Check it's a directory. */
+ if (node->kind != svn_node_dir)
+ return svn_error_create
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Attempted to set entry in non-directory node"));
+
+ /* Check it's mutable. */
+ if (! svn_fs_x__dag_check_mutable(node))
+ return svn_error_create
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to set entry in immutable node"));
+
+ return set_entry(node, entry_name, id, kind, txn_id, scratch_pool);
+}
+
+
+
+/*** Proplists. ***/
+
+svn_error_t *
+svn_fs_x__dag_get_proplist(apr_hash_t **proplist_p,
+ dag_node_t *node,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__noderev_t *noderev;
+ apr_hash_t *proplist = NULL;
+
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ SVN_ERR(svn_fs_x__get_proplist(&proplist, node->fs, noderev, result_pool,
+ scratch_pool));
+
+ *proplist_p = proplist;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_x__dag_set_proplist(dag_node_t *node,
+ apr_hash_t *proplist,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ /* Sanity check: this node better be mutable! */
+ if (! svn_fs_x__dag_check_mutable(node))
+ {
+ svn_string_t *idstr = svn_fs_x__id_unparse(&node->id, scratch_pool);
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ "Can't set proplist on *immutable* node-revision %s",
+ idstr->data);
+ }
+
+ /* Go get a fresh NODE-REVISION for this node. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ /* Set the new proplist. */
+ return svn_fs_x__set_proplist(node->fs, noderev, proplist, scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_increment_mergeinfo_count(dag_node_t *node,
+ apr_int64_t increment,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ /* Sanity check: this node better be mutable! */
+ if (! svn_fs_x__dag_check_mutable(node))
+ {
+ svn_string_t *idstr = svn_fs_x__id_unparse(&node->id, scratch_pool);
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ "Can't increment mergeinfo count on *immutable* node-revision %s",
+ idstr->data);
+ }
+
+ if (increment == 0)
+ return SVN_NO_ERROR;
+
+ /* Go get a fresh NODE-REVISION for this node. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ noderev->mergeinfo_count += increment;
+ if (noderev->mergeinfo_count < 0)
+ {
+ svn_string_t *idstr = svn_fs_x__id_unparse(&node->id, scratch_pool);
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ apr_psprintf(scratch_pool,
+ _("Can't increment mergeinfo count on node-revision %%s "
+ "to negative value %%%s"),
+ APR_INT64_T_FMT),
+ idstr->data, noderev->mergeinfo_count);
+ }
+ if (noderev->mergeinfo_count > 1 && noderev->kind == svn_node_file)
+ {
+ svn_string_t *idstr = svn_fs_x__id_unparse(&node->id, scratch_pool);
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ apr_psprintf(scratch_pool,
+ _("Can't increment mergeinfo count on *file* "
+ "node-revision %%s to %%%s (> 1)"),
+ APR_INT64_T_FMT),
+ idstr->data, noderev->mergeinfo_count);
+ }
+
+ /* Flush it out. */
+ return svn_fs_x__put_node_revision(node->fs, noderev, scratch_pool);
+}
+
+svn_error_t *
+svn_fs_x__dag_set_has_mergeinfo(dag_node_t *node,
+ svn_boolean_t has_mergeinfo,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ /* Sanity check: this node better be mutable! */
+ if (! svn_fs_x__dag_check_mutable(node))
+ {
+ svn_string_t *idstr = svn_fs_x__id_unparse(&node->id, scratch_pool);
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ "Can't set mergeinfo flag on *immutable* node-revision %s",
+ idstr->data);
+ }
+
+ /* Go get a fresh NODE-REVISION for this node. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ noderev->has_mergeinfo = has_mergeinfo;
+
+ /* Flush it out. */
+ return svn_fs_x__put_node_revision(node->fs, noderev, scratch_pool);
+}
+
+
+/*** Roots. ***/
+
+svn_error_t *
+svn_fs_x__dag_revision_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__id_t root_id;
+
+ svn_fs_x__init_rev_root(&root_id, rev);
+ return svn_fs_x__dag_get_node(node_p, fs, &root_id, result_pool,
+ scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_txn_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ svn_fs_x__txn_id_t txn_id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__id_t root_id;
+
+ svn_fs_x__init_txn_root(&root_id, txn_id);
+ return svn_fs_x__dag_get_node(node_p, fs, &root_id, result_pool,
+ scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_clone_child(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const svn_fs_x__id_t *copy_id,
+ svn_fs_x__txn_id_t txn_id,
+ svn_boolean_t is_parent_copyroot,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ dag_node_t *cur_entry; /* parent's current entry named NAME */
+ const svn_fs_x__id_t *new_node_id; /* node id we'll put into NEW_NODE */
+ svn_fs_t *fs = svn_fs_x__dag_get_fs(parent);
+
+ /* First check that the parent is mutable. */
+ if (! svn_fs_x__dag_check_mutable(parent))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ "Attempted to clone child of non-mutable node");
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ "Attempted to make a child clone with an illegal name '%s'", name);
+
+ /* Find the node named NAME in PARENT's entries list if it exists. */
+ SVN_ERR(svn_fs_x__dag_open(&cur_entry, parent, name, scratch_pool,
+ scratch_pool));
+ if (! cur_entry)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FOUND, NULL,
+ "Attempted to open non-existent child node '%s'", name);
+
+ /* Check for mutability in the node we found. If it's mutable, we
+ don't need to clone it. */
+ if (svn_fs_x__dag_check_mutable(cur_entry))
+ {
+ /* This has already been cloned */
+ new_node_id = svn_fs_x__dag_get_id(cur_entry);
+ }
+ else
+ {
+ svn_fs_x__noderev_t *noderev, *parent_noderev;
+
+ /* Go get a fresh NODE-REVISION for current child node. */
+ SVN_ERR(get_node_revision(&noderev, cur_entry));
+
+ if (is_parent_copyroot)
+ {
+ SVN_ERR(get_node_revision(&parent_noderev, parent));
+ noderev->copyroot_rev = parent_noderev->copyroot_rev;
+ noderev->copyroot_path = apr_pstrdup(scratch_pool,
+ parent_noderev->copyroot_path);
+ }
+
+ noderev->copyfrom_path = NULL;
+ noderev->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ noderev->predecessor_id = noderev->noderev_id;
+ noderev->predecessor_count++;
+ noderev->created_path = svn_fspath__join(parent_path, name,
+ scratch_pool);
+
+ if (copy_id == NULL)
+ copy_id = &noderev->copy_id;
+
+ SVN_ERR(svn_fs_x__create_successor(fs, noderev, copy_id, txn_id,
+ scratch_pool));
+ new_node_id = &noderev->noderev_id;
+
+ /* Replace the ID in the parent's ENTRY list with the ID which
+ refers to the mutable clone of this child. */
+ SVN_ERR(set_entry(parent, name, new_node_id, noderev->kind, txn_id,
+ scratch_pool));
+ }
+
+ /* Initialize the youngster. */
+ return svn_fs_x__dag_get_node(child_p, fs, new_node_id, result_pool,
+ scratch_pool);
+}
+
+
+/* Delete all mutable node revisions reachable from node ID, including
+ ID itself, from FS's `nodes' table. Also delete any mutable
+ representations and strings associated with that node revision.
+ ID may refer to a file or directory, which may be mutable or immutable.
+
+ Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+delete_if_mutable(svn_fs_t *fs,
+ const svn_fs_x__id_t *id,
+ apr_pool_t *scratch_pool)
+{
+ dag_node_t *node;
+
+ /* Get the node. */
+ SVN_ERR(svn_fs_x__dag_get_node(&node, fs, id, scratch_pool, scratch_pool));
+
+ /* If immutable, do nothing and return immediately. */
+ if (! svn_fs_x__dag_check_mutable(node))
+ return SVN_NO_ERROR;
+
+ /* Else it's mutable. Recurse on directories... */
+ if (node->kind == svn_node_dir)
+ {
+ apr_array_header_t *entries;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* Loop over directory entries */
+ SVN_ERR(svn_fs_x__dag_dir_entries(&entries, node, scratch_pool,
+ iterpool));
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ const svn_fs_x__id_t *noderev_id
+ = &APR_ARRAY_IDX(entries, i, svn_fs_x__dirent_t *)->id;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(delete_if_mutable(fs, noderev_id, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ /* ... then delete the node itself, after deleting any mutable
+ representations and strings it points to. */
+ return svn_fs_x__delete_node_revision(fs, id, scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_delete(dag_node_t *parent,
+ const char *name,
+ svn_fs_x__txn_id_t txn_id,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__noderev_t *parent_noderev;
+ svn_fs_t *fs = parent->fs;
+ svn_fs_x__dirent_t *dirent;
+ apr_pool_t *subpool;
+
+ /* Make sure parent is a directory. */
+ if (parent->kind != svn_node_dir)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ "Attempted to delete entry '%s' from *non*-directory node", name);
+
+ /* Make sure parent is mutable. */
+ if (! svn_fs_x__dag_check_mutable(parent))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ "Attempted to delete entry '%s' from immutable directory node", name);
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ "Attempted to delete a node with an illegal name '%s'", name);
+
+ /* Get a fresh NODE-REVISION for the parent node. */
+ SVN_ERR(get_node_revision(&parent_noderev, parent));
+
+ subpool = svn_pool_create(scratch_pool);
+
+ /* Search this directory for a dirent with that NAME. */
+ SVN_ERR(svn_fs_x__rep_contents_dir_entry(&dirent, fs, parent_noderev,
+ name, &parent->hint,
+ subpool, subpool));
+
+ /* If we never found ID in ENTRIES (perhaps because there are no
+ ENTRIES, perhaps because ID just isn't in the existing ENTRIES
+ ... it doesn't matter), return an error. */
+ if (! dirent)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
+ "Delete failed--directory has no entry '%s'", name);
+
+ /* If mutable, remove it and any mutable children from db. */
+ SVN_ERR(delete_if_mutable(parent->fs, &dirent->id, scratch_pool));
+ svn_pool_destroy(subpool);
+
+ /* Remove this entry from its parent's entries list. */
+ return svn_fs_x__set_entry(parent->fs, txn_id, parent_noderev, name,
+ NULL, svn_node_unknown, parent->node_pool,
+ scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_make_file(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ svn_fs_x__txn_id_t txn_id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* Call our little helper function */
+ return make_entry(child_p, parent, parent_path, name, FALSE, txn_id,
+ result_pool, scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_make_dir(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ svn_fs_x__txn_id_t txn_id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* Call our little helper function */
+ return make_entry(child_p, parent, parent_path, name, TRUE, txn_id,
+ result_pool, scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_get_contents(svn_stream_t **contents_p,
+ dag_node_t *file,
+ apr_pool_t *result_pool)
+{
+ svn_fs_x__noderev_t *noderev;
+ svn_stream_t *contents;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ "Attempted to get textual contents of a *non*-file node");
+
+ /* Go get a fresh node-revision for FILE. */
+ SVN_ERR(get_node_revision(&noderev, file));
+
+ /* Get a stream to the contents. */
+ SVN_ERR(svn_fs_x__get_contents(&contents, file->fs,
+ noderev->data_rep, TRUE, result_pool));
+
+ *contents_p = contents;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_x__dag_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ dag_node_t *source,
+ dag_node_t *target,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__noderev_t *src_noderev;
+ svn_fs_x__noderev_t *tgt_noderev;
+
+ /* Make sure our nodes are files. */
+ if ((source && source->kind != svn_node_file)
+ || target->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ "Attempted to get textual contents of a *non*-file node");
+
+ /* Go get fresh node-revisions for the nodes. */
+ if (source)
+ SVN_ERR(get_node_revision(&src_noderev, source));
+ else
+ src_noderev = NULL;
+ SVN_ERR(get_node_revision(&tgt_noderev, target));
+
+ /* Get the delta stream. */
+ return svn_fs_x__get_file_delta_stream(stream_p, target->fs,
+ src_noderev, tgt_noderev,
+ result_pool, scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_try_process_file_contents(svn_boolean_t *success,
+ dag_node_t *node,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ /* Go get fresh node-revisions for the nodes. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ return svn_fs_x__try_process_file_contents(success, node->fs,
+ noderev,
+ processor, baton, scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_file_length(svn_filesize_t *length,
+ dag_node_t *file)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ "Attempted to get length of a *non*-file node");
+
+ /* Go get a fresh node-revision for FILE, and . */
+ SVN_ERR(get_node_revision(&noderev, file));
+
+ return svn_fs_x__file_length(length, noderev);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_file_checksum(svn_checksum_t **checksum,
+ dag_node_t *file,
+ svn_checksum_kind_t kind,
+ apr_pool_t *result_pool)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ "Attempted to get checksum of a *non*-file node");
+
+ SVN_ERR(get_node_revision(&noderev, file));
+
+ return svn_fs_x__file_checksum(checksum, noderev, kind, result_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_get_edit_stream(svn_stream_t **contents,
+ dag_node_t *file,
+ apr_pool_t *result_pool)
+{
+ svn_fs_x__noderev_t *noderev;
+ svn_stream_t *ws;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ "Attempted to set textual contents of a *non*-file node");
+
+ /* Make sure our node is mutable. */
+ if (! svn_fs_x__dag_check_mutable(file))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ "Attempted to set textual contents of an immutable node");
+
+ /* Get the node revision. */
+ SVN_ERR(get_node_revision(&noderev, file));
+
+ SVN_ERR(svn_fs_x__set_contents(&ws, file->fs, noderev, result_pool));
+
+ *contents = ws;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_fs_x__dag_finalize_edits(dag_node_t *file,
+ const svn_checksum_t *checksum,
+ apr_pool_t *scratch_pool)
+{
+ if (checksum)
+ {
+ svn_checksum_t *file_checksum;
+
+ SVN_ERR(svn_fs_x__dag_file_checksum(&file_checksum, file,
+ checksum->kind, scratch_pool));
+ if (!svn_checksum_match(checksum, file_checksum))
+ return svn_checksum_mismatch_err(checksum, file_checksum,
+ scratch_pool,
+ _("Checksum mismatch for '%s'"),
+ file->created_path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+dag_node_t *
+svn_fs_x__dag_dup(const dag_node_t *node,
+ apr_pool_t *result_pool)
+{
+ /* Allocate our new node. */
+ dag_node_t *new_node = apr_pmemdup(result_pool, node, sizeof(*new_node));
+
+ /* Only copy cached svn_fs_x__noderev_t for immutable nodes. */
+ if (node->node_revision && !svn_fs_x__dag_check_mutable(node))
+ {
+ new_node->node_revision = copy_node_revision(node->node_revision,
+ result_pool);
+ new_node->created_path = new_node->node_revision->created_path;
+ }
+ else
+ {
+ new_node->node_revision = NULL;
+ new_node->created_path = apr_pstrdup(result_pool, node->created_path);
+ }
+
+ new_node->node_pool = result_pool;
+
+ return new_node;
+}
+
+dag_node_t *
+svn_fs_x__dag_copy_into_pool(dag_node_t *node,
+ apr_pool_t *result_pool)
+{
+ return (node->node_pool == result_pool
+ ? node
+ : svn_fs_x__dag_dup(node, result_pool));
+}
+
+svn_error_t *
+svn_fs_x__dag_serialize(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ dag_node_t *node = in;
+ svn_stringbuf_t *serialized;
+
+ /* create an serialization context and serialize the dag node as root */
+ svn_temp_serializer__context_t *context =
+ svn_temp_serializer__init(node,
+ sizeof(*node),
+ 1024 - SVN_TEMP_SERIALIZER__OVERHEAD,
+ pool);
+
+ /* for mutable nodes, we will _never_ cache the noderev */
+ if (node->node_revision && !svn_fs_x__dag_check_mutable(node))
+ {
+ svn_fs_x__noderev_serialize(context, &node->node_revision);
+ }
+ else
+ {
+ svn_temp_serializer__set_null(context,
+ (const void * const *)&node->node_revision);
+ svn_temp_serializer__add_string(context, &node->created_path);
+ }
+
+ /* The deserializer will use its own pool. */
+ svn_temp_serializer__set_null(context,
+ (const void * const *)&node->node_pool);
+
+ /* return serialized data */
+ serialized = svn_temp_serializer__get(context);
+ *data = serialized->data;
+ *data_len = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__dag_deserialize(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ dag_node_t *node = (dag_node_t *)data;
+ if (data_len == 0)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Empty noderev in cache"));
+
+ /* Copy the _full_ buffer as it also contains the sub-structures. */
+ node->fs = NULL;
+
+ /* fixup all references to sub-structures */
+ svn_fs_x__noderev_deserialize(node, &node->node_revision, pool);
+ node->node_pool = pool;
+
+ if (node->node_revision)
+ node->created_path = node->node_revision->created_path;
+ else
+ svn_temp_deserializer__resolve(node, (void**)&node->created_path);
+
+ /* return result */
+ *out = node;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__dag_open(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__id_t node_id;
+
+ /* Ensure that NAME exists in PARENT's entry list. */
+ SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, scratch_pool));
+ if (! svn_fs_x__id_used(&node_id))
+ {
+ *child_p = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Now get the node that was requested. */
+ return svn_fs_x__dag_get_node(child_p, svn_fs_x__dag_get_fs(parent),
+ &node_id, result_pool, scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__dag_copy(dag_node_t *to_node,
+ const char *entry,
+ dag_node_t *from_node,
+ svn_boolean_t preserve_history,
+ svn_revnum_t from_rev,
+ const char *from_path,
+ svn_fs_x__txn_id_t txn_id,
+ apr_pool_t *scratch_pool)
+{
+ const svn_fs_x__id_t *id;
+
+ if (preserve_history)
+ {
+ svn_fs_x__noderev_t *from_noderev, *to_noderev;
+ svn_fs_x__id_t copy_id;
+ svn_fs_t *fs = svn_fs_x__dag_get_fs(from_node);
+
+ /* Make a copy of the original node revision. */
+ SVN_ERR(get_node_revision(&from_noderev, from_node));
+ to_noderev = copy_node_revision(from_noderev, scratch_pool);
+
+ /* Reserve a copy ID for this new copy. */
+ SVN_ERR(svn_fs_x__reserve_copy_id(&copy_id, fs, txn_id, scratch_pool));
+
+ /* Create a successor with its predecessor pointing at the copy
+ source. */
+ to_noderev->predecessor_id = to_noderev->noderev_id;
+ to_noderev->predecessor_count++;
+ to_noderev->created_path =
+ svn_fspath__join(svn_fs_x__dag_get_created_path(to_node), entry,
+ scratch_pool);
+ to_noderev->copyfrom_path = apr_pstrdup(scratch_pool, from_path);
+ to_noderev->copyfrom_rev = from_rev;
+
+ /* Set the copyroot equal to our own id. */
+ to_noderev->copyroot_path = NULL;
+
+ SVN_ERR(svn_fs_x__create_successor(fs, to_noderev,
+ &copy_id, txn_id, scratch_pool));
+ id = &to_noderev->noderev_id;
+ }
+ else /* don't preserve history */
+ {
+ id = svn_fs_x__dag_get_id(from_node);
+ }
+
+ /* Set the entry in to_node to the new id. */
+ return svn_fs_x__dag_set_entry(to_node, entry, id, from_node->kind,
+ txn_id, scratch_pool);
+}
+
+
+
+/*** Comparison. ***/
+
+svn_error_t *
+svn_fs_x__dag_things_different(svn_boolean_t *props_changed,
+ svn_boolean_t *contents_changed,
+ dag_node_t *node1,
+ dag_node_t *node2,
+ svn_boolean_t strict,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__noderev_t *noderev1, *noderev2;
+ svn_fs_t *fs;
+ svn_boolean_t same;
+
+ /* If we have no place to store our results, don't bother doing
+ anything. */
+ if (! props_changed && ! contents_changed)
+ return SVN_NO_ERROR;
+
+ fs = svn_fs_x__dag_get_fs(node1);
+
+ /* The node revision skels for these two nodes. */
+ SVN_ERR(get_node_revision(&noderev1, node1));
+ SVN_ERR(get_node_revision(&noderev2, node2));
+
+ /* Compare property keys. */
+ if (props_changed != NULL)
+ {
+ SVN_ERR(svn_fs_x__prop_rep_equal(&same, fs, noderev1, noderev2,
+ strict, scratch_pool));
+ *props_changed = !same;
+ }
+
+ /* Compare contents keys. */
+ if (contents_changed != NULL)
+ *contents_changed = !svn_fs_x__file_text_rep_equal(noderev1->data_rep,
+ noderev2->data_rep);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__dag_get_copyroot(svn_revnum_t *rev,
+ const char **path,
+ dag_node_t *node)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ /* Go get a fresh node-revision for NODE. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ *rev = noderev->copyroot_rev;
+ *path = noderev->copyroot_path;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__dag_get_copyfrom_rev(svn_revnum_t *rev,
+ dag_node_t *node)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ /* Go get a fresh node-revision for NODE. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ *rev = noderev->copyfrom_rev;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__dag_get_copyfrom_path(const char **path,
+ dag_node_t *node)
+{
+ svn_fs_x__noderev_t *noderev;
+
+ /* Go get a fresh node-revision for NODE. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ *path = noderev->copyfrom_path;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__dag_update_ancestry(dag_node_t *target,
+ dag_node_t *source,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__noderev_t *source_noderev, *target_noderev;
+
+ if (! svn_fs_x__dag_check_mutable(target))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to update ancestry of non-mutable node"));
+
+ SVN_ERR(get_node_revision(&source_noderev, source));
+ SVN_ERR(get_node_revision(&target_noderev, target));
+
+ target_noderev->predecessor_id = source_noderev->noderev_id;
+ target_noderev->predecessor_count = source_noderev->predecessor_count;
+ target_noderev->predecessor_count++;
+
+ return svn_fs_x__put_node_revision(target->fs, target_noderev,
+ scratch_pool);
+}