summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_fs_base/dag.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_base/dag.c')
-rw-r--r--subversion/libsvn_fs_base/dag.c1758
1 files changed, 1758 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_base/dag.c b/subversion/libsvn_fs_base/dag.c
new file mode 100644
index 0000000..510ccbb
--- /dev/null
+++ b/subversion/libsvn_fs_base/dag.c
@@ -0,0 +1,1758 @@
+/* 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_time.h"
+#include "svn_error.h"
+#include "svn_fs.h"
+#include "svn_hash.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+
+#include "dag.h"
+#include "err.h"
+#include "fs.h"
+#include "key-gen.h"
+#include "node-rev.h"
+#include "trail.h"
+#include "reps-strings.h"
+#include "revs-txns.h"
+#include "id.h"
+
+#include "util/fs_skels.h"
+
+#include "bdb/txn-table.h"
+#include "bdb/rev-table.h"
+#include "bdb/nodes-table.h"
+#include "bdb/copies-table.h"
+#include "bdb/reps-table.h"
+#include "bdb/strings-table.h"
+#include "bdb/checksum-reps-table.h"
+#include "bdb/changes-table.h"
+#include "bdb/node-origins-table.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_fs_util.h"
+#include "private/svn_fspath.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+
+/* Initializing a filesystem. */
+
+struct dag_node_t
+{
+ /*** NOTE: Keeping in-memory representations of disk data that can
+ be changed by other accessors is a nasty business. Such
+ representations are basically a cache with some pretty complex
+ invalidation rules. For example, the "node revision"
+ associated with a DAG node ID can look completely different to
+ a process that has modified that information as part of a
+ Berkeley DB transaction than it does to some other process.
+ That said, there are some aspects of a "node revision" which
+ never change, like its 'id' or 'kind'. Our best bet is to
+ limit ourselves to exposing outside of this interface only
+ those immutable aspects of a DAG node representation. ***/
+
+ /* The filesystem this dag node came from. */
+ svn_fs_t *fs;
+
+ /* The node revision ID for this dag node. */
+ svn_fs_id_t *id;
+
+ /* The node's type (file, dir, etc.) */
+ svn_node_kind_t kind;
+
+ /* the path at which this node was created. */
+ const char *created_path;
+};
+
+
+
+/* Trivial helper/accessor functions. */
+svn_node_kind_t svn_fs_base__dag_node_kind(dag_node_t *node)
+{
+ return node->kind;
+}
+
+
+const svn_fs_id_t *
+svn_fs_base__dag_get_id(dag_node_t *node)
+{
+ return node->id;
+}
+
+
+const char *
+svn_fs_base__dag_get_created_path(dag_node_t *node)
+{
+ return node->created_path;
+}
+
+
+svn_fs_t *
+svn_fs_base__dag_get_fs(dag_node_t *node)
+{
+ return node->fs;
+}
+
+
+svn_boolean_t svn_fs_base__dag_check_mutable(dag_node_t *node,
+ const char *txn_id)
+{
+ return (strcmp(svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)),
+ txn_id) == 0);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_node(dag_node_t **node,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *new_node;
+ node_revision_t *noderev;
+
+ /* Construct the node. */
+ new_node = apr_pcalloc(pool, sizeof(*new_node));
+ new_node->fs = fs;
+ new_node->id = svn_fs_base__id_copy(id, pool);
+
+ /* Grab the contents so we can cache some of the immutable parts of it. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool));
+
+ /* Initialize the KIND and CREATED_PATH attributes */
+ new_node->kind = noderev->kind;
+ new_node->created_path = noderev->created_path;
+
+ /* Return a fresh new node */
+ *node = new_node;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_revision(svn_revnum_t *rev,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* Use the txn ID from the NODE's id to look up the transaction and
+ get its revision number. */
+ return svn_fs_base__txn_get_revision
+ (rev, svn_fs_base__dag_get_fs(node),
+ svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)), trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t **id_p,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
+ trail, pool));
+ *id_p = noderev->predecessor_id;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_predecessor_count(int *count,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
+ trail, pool));
+ *count = noderev->predecessor_count;
+ return SVN_NO_ERROR;
+}
+
+
+/* Trail body for svn_fs_base__dag_init_fs. */
+static svn_error_t *
+txn_body_dag_init_fs(void *baton,
+ trail_t *trail)
+{
+ node_revision_t noderev;
+ revision_t revision;
+ svn_revnum_t rev = SVN_INVALID_REVNUM;
+ svn_fs_t *fs = trail->fs;
+ svn_string_t date;
+ const char *txn_id;
+ const char *copy_id;
+ svn_fs_id_t *root_id = svn_fs_base__id_create("0", "0", "0", trail->pool);
+
+ /* Create empty root directory with node revision 0.0.0. */
+ memset(&noderev, 0, sizeof(noderev));
+ noderev.kind = svn_node_dir;
+ noderev.created_path = "/";
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, root_id, &noderev,
+ trail, trail->pool));
+
+ /* Create a new transaction (better have an id of "0") */
+ SVN_ERR(svn_fs_bdb__create_txn(&txn_id, fs, root_id, trail, trail->pool));
+ if (strcmp(txn_id, "0"))
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: initial transaction id not '0' in filesystem '%s'"),
+ fs->path);
+
+ /* Create a default copy (better have an id of "0") */
+ SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, trail->pool));
+ if (strcmp(copy_id, "0"))
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: initial copy id not '0' in filesystem '%s'"), fs->path);
+ SVN_ERR(svn_fs_bdb__create_copy(fs, copy_id, NULL, NULL, root_id,
+ copy_kind_real, trail, trail->pool));
+
+ /* Link it into filesystem revision 0. */
+ revision.txn_id = txn_id;
+ SVN_ERR(svn_fs_bdb__put_rev(&rev, fs, &revision, trail, trail->pool));
+ if (rev != 0)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: initial revision number "
+ "is not '0' in filesystem '%s'"), fs->path);
+
+ /* Promote our transaction to a "committed" transaction. */
+ SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, rev,
+ trail, trail->pool));
+
+ /* Set a date on revision 0. */
+ date.data = svn_time_to_cstring(apr_time_now(), trail->pool);
+ date.len = strlen(date.data);
+ return svn_fs_base__set_rev_prop(fs, 0, SVN_PROP_REVISION_DATE, NULL, &date,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_init_fs(svn_fs_t *fs)
+{
+ return svn_fs_base__retry_txn(fs, txn_body_dag_init_fs, NULL,
+ TRUE, fs->pool);
+}
+
+
+
+/*** Directory node functions ***/
+
+/* Some of these are helpers for functions outside this section. */
+
+/* Given directory NODEREV in FS, set *ENTRIES_P to its entries list
+ hash, as part of TRAIL, or to NULL if NODEREV has no entries. The
+ entries list will be allocated in POOL, and the entries in that
+ list will not have interesting value in their 'kind' fields. If
+ NODEREV is not a directory, return the error SVN_ERR_FS_NOT_DIRECTORY. */
+static svn_error_t *
+get_dir_entries(apr_hash_t **entries_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_hash_t *entries = NULL;
+ apr_hash_index_t *hi;
+ svn_string_t entries_raw;
+ svn_skel_t *entries_skel;
+
+ /* Error if this is not a directory. */
+ if (noderev->kind != svn_node_dir)
+ return svn_error_create
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Attempted to get entries of a non-directory node"));
+
+ /* If there's a DATA-KEY, there might be entries to fetch. */
+ if (noderev->data_key)
+ {
+ /* Now we have a rep, follow through to get the entries. */
+ SVN_ERR(svn_fs_base__rep_contents(&entries_raw, fs, noderev->data_key,
+ trail, pool));
+ entries_skel = svn_skel__parse(entries_raw.data, entries_raw.len, pool);
+
+ /* Were there entries? Make a hash from them. */
+ if (entries_skel)
+ SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
+ pool));
+ }
+
+ /* No hash? No problem. */
+ *entries_p = NULL;
+ if (! entries)
+ return SVN_NO_ERROR;
+
+ /* Else, convert the hash from a name->id mapping to a name->dirent one. */
+ *entries_p = apr_hash_make(pool);
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *val;
+ svn_fs_dirent_t *dirent = apr_palloc(pool, sizeof(*dirent));
+
+ /* KEY will be the entry name in ancestor, VAL the id. */
+ apr_hash_this(hi, &key, &klen, &val);
+ dirent->name = key;
+ dirent->id = val;
+ dirent->kind = svn_node_unknown;
+ apr_hash_set(*entries_p, key, klen, dirent);
+ }
+
+ /* Return our findings. */
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *ID_P to the node-id for entry NAME in PARENT, as part of
+ TRAIL. If no such entry, set *ID_P to NULL but do not error. The
+ entry is allocated in POOL or in the same pool as PARENT;
+ the caller should copy if it cares. */
+static svn_error_t *
+dir_entry_id_from_node(const svn_fs_id_t **id_p,
+ dag_node_t *parent,
+ const char *name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_hash_t *entries;
+ svn_fs_dirent_t *dirent;
+
+ SVN_ERR(svn_fs_base__dag_dir_entries(&entries, parent, trail, pool));
+ if (entries)
+ dirent = svn_hash_gets(entries, name);
+ else
+ dirent = NULL;
+
+ *id_p = dirent ? dirent->id : NULL;
+ return SVN_NO_ERROR;
+}
+
+
+/* Add or set in PARENT a directory entry NAME pointing to ID.
+ Allocations are done in TRAIL.
+
+ 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_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *parent_noderev;
+ const char *rep_key, *mutable_rep_key;
+ apr_hash_t *entries = NULL;
+ svn_stream_t *wstream;
+ apr_size_t len;
+ svn_string_t raw_entries;
+ svn_stringbuf_t *raw_entries_buf;
+ svn_skel_t *entries_skel;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(parent);
+
+ /* Get the parent's node-revision. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
+ trail, pool));
+ rep_key = parent_noderev->data_key;
+ SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
+ fs, txn_id, trail, pool));
+
+ /* If the parent node already pointed at a mutable representation,
+ we don't need to do anything. But if it didn't, either because
+ the parent didn't refer to any rep yet or because it referred to
+ an immutable one, we must make the parent refer to the mutable
+ rep we just created. */
+ if (! svn_fs_base__same_keys(rep_key, mutable_rep_key))
+ {
+ parent_noderev->data_key = mutable_rep_key;
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
+ trail, pool));
+ }
+
+ /* If the new representation inherited nothing, start a new entries
+ list for it. Else, go read its existing entries list. */
+ if (rep_key)
+ {
+ SVN_ERR(svn_fs_base__rep_contents(&raw_entries, fs, rep_key,
+ trail, pool));
+ entries_skel = svn_skel__parse(raw_entries.data, raw_entries.len, pool);
+ if (entries_skel)
+ SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
+ pool));
+ }
+
+ /* If we still have no ENTRIES hash, make one here. */
+ if (! entries)
+ entries = apr_hash_make(pool);
+
+ /* Now, add our new entry to the entries list. */
+ svn_hash_sets(entries, name, id);
+
+ /* Finally, replace the old entries list with the new one. */
+ SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries,
+ pool));
+ raw_entries_buf = svn_skel__unparse(entries_skel, pool);
+ SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs,
+ mutable_rep_key, txn_id,
+ TRUE, trail, pool));
+ len = raw_entries_buf->len;
+ SVN_ERR(svn_stream_write(wstream, raw_entries_buf->data, &len));
+ return svn_stream_close(wstream);
+}
+
+
+/* Make a new entry named NAME in PARENT, as part of TRAIL. 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 POOL. PARENT must be mutable, and must not have an entry
+ named NAME. */
+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,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *new_node_id;
+ node_revision_t new_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_base__dag_check_mutable(parent, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to clone child of non-mutable node"));
+
+ /* Check that parent does not already have an entry named NAME. */
+ SVN_ERR(dir_entry_id_from_node(&new_node_id, parent, name, trail, pool));
+ if (new_node_id)
+ return svn_error_createf
+ (SVN_ERR_FS_ALREADY_EXISTS, NULL,
+ _("Attempted to create entry that already exists"));
+
+ /* 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, pool);
+ SVN_ERR(svn_fs_base__create_node
+ (&new_node_id, svn_fs_base__dag_get_fs(parent), &new_noderev,
+ svn_fs_base__id_copy_id(svn_fs_base__dag_get_id(parent)),
+ txn_id, trail, pool));
+
+ /* Create a new dag_node_t for our new node */
+ SVN_ERR(svn_fs_base__dag_get_node(child_p,
+ svn_fs_base__dag_get_fs(parent),
+ new_node_id, trail, 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, svn_fs_base__dag_get_id(*child_p),
+ txn_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_dir_entries(apr_hash_t **entries,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
+ trail, pool));
+ return get_dir_entries(entries, node->fs, noderev, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_set_entry(dag_node_t *node,
+ const char *entry_name,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *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_base__dag_check_mutable(node, txn_id))
+ return svn_error_create
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to set entry in immutable node"));
+
+ return set_entry(node, entry_name, id, txn_id, trail, pool);
+}
+
+
+
+/*** Proplists. ***/
+
+svn_error_t *
+svn_fs_base__dag_get_proplist(apr_hash_t **proplist_p,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ apr_hash_t *proplist = NULL;
+ svn_string_t raw_proplist;
+ svn_skel_t *proplist_skel;
+
+ /* Go get a fresh NODE-REVISION for this node. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
+ trail, pool));
+
+ /* Get property key (returning early if there isn't one) . */
+ if (! noderev->prop_key)
+ {
+ *proplist_p = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Get the string associated with the property rep, parsing it as a
+ skel, and then attempt to parse *that* into a property hash. */
+ SVN_ERR(svn_fs_base__rep_contents(&raw_proplist,
+ svn_fs_base__dag_get_fs(node),
+ noderev->prop_key, trail, pool));
+ proplist_skel = svn_skel__parse(raw_proplist.data, raw_proplist.len, pool);
+ if (proplist_skel)
+ SVN_ERR(svn_skel__parse_proplist(&proplist, proplist_skel, pool));
+
+ *proplist_p = proplist;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_set_proplist(dag_node_t *node,
+ const apr_hash_t *proplist,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ const char *rep_key, *mutable_rep_key;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
+ svn_stream_t *wstream;
+ apr_size_t len;
+ svn_skel_t *proplist_skel;
+ svn_stringbuf_t *raw_proplist_buf;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* Sanity check: this node better be mutable! */
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ {
+ svn_string_t *idstr = svn_fs_base__id_unparse(node->id, 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(svn_fs_bdb__get_node_revision(&noderev, fs, node->id,
+ trail, pool));
+ rep_key = noderev->prop_key;
+
+ /* Flatten the proplist into a string. */
+ SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, proplist, pool));
+ raw_proplist_buf = svn_skel__unparse(proplist_skel, pool);
+
+ /* If this repository supports representation sharing, and the
+ resulting property list is exactly the same as another string in
+ the database, just use the previously existing string and get
+ outta here. */
+ if (bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
+ {
+ svn_error_t *err;
+ const char *dup_rep_key;
+ svn_checksum_t *checksum;
+
+ SVN_ERR(svn_checksum(&checksum, svn_checksum_sha1, raw_proplist_buf->data,
+ raw_proplist_buf->len, pool));
+
+ err = svn_fs_bdb__get_checksum_rep(&dup_rep_key, fs, checksum,
+ trail, pool);
+ if (! err)
+ {
+ if (noderev->prop_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key,
+ txn_id, trail, pool));
+ noderev->prop_key = dup_rep_key;
+ return svn_fs_bdb__put_node_revision(fs, node->id, noderev,
+ trail, pool);
+ }
+ else if (err)
+ {
+ if (err->apr_err != SVN_ERR_FS_NO_SUCH_CHECKSUM_REP)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ }
+
+ /* Get a mutable version of this rep (updating the node revision if
+ this isn't a NOOP) */
+ SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
+ fs, txn_id, trail, pool));
+ if (! svn_fs_base__same_keys(mutable_rep_key, rep_key))
+ {
+ noderev->prop_key = mutable_rep_key;
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, node->id, noderev,
+ trail, pool));
+ }
+
+ /* Replace the old property list with the new one. */
+ SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs,
+ mutable_rep_key, txn_id,
+ TRUE, trail, pool));
+ len = raw_proplist_buf->len;
+ SVN_ERR(svn_stream_write(wstream, raw_proplist_buf->data, &len));
+ SVN_ERR(svn_stream_close(wstream));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Roots. ***/
+
+svn_error_t *
+svn_fs_base__dag_revision_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *root_id;
+
+ SVN_ERR(svn_fs_base__rev_get_root(&root_id, fs, rev, trail, pool));
+ return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_txn_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *root_id, *ignored;
+
+ SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &ignored, fs, txn_id,
+ trail, pool));
+ return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_txn_base_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *base_root_id, *ignored;
+
+ SVN_ERR(svn_fs_base__get_txn_ids(&ignored, &base_root_id, fs, txn_id,
+ trail, pool));
+ return svn_fs_base__dag_get_node(node_p, fs, base_root_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_clone_child(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *cur_entry; /* parent's current entry named NAME */
+ const svn_fs_id_t *new_node_id; /* node id we'll put into NEW_NODE */
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(parent);
+
+ /* First check that the parent is mutable. */
+ if (! svn_fs_base__dag_check_mutable(parent, txn_id))
+ 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_base__dag_open(&cur_entry, parent, name, trail, pool));
+
+ /* Check for mutability in the node we found. If it's mutable, we
+ don't need to clone it. */
+ if (svn_fs_base__dag_check_mutable(cur_entry, txn_id))
+ {
+ /* This has already been cloned */
+ new_node_id = cur_entry->id;
+ }
+ else
+ {
+ node_revision_t *noderev;
+
+ /* Go get a fresh NODE-REVISION for current child node. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, cur_entry->id,
+ trail, pool));
+
+ /* Do the clone thingy here. */
+ noderev->predecessor_id = cur_entry->id;
+ if (noderev->predecessor_count != -1)
+ noderev->predecessor_count++;
+ noderev->created_path = svn_fspath__join(parent_path, name, pool);
+ SVN_ERR(svn_fs_base__create_successor(&new_node_id, fs, cur_entry->id,
+ noderev, copy_id, txn_id,
+ trail, pool));
+
+ /* 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, txn_id, trail, pool));
+ }
+
+ /* Initialize the youngster. */
+ return svn_fs_base__dag_get_node(child_p, fs, new_node_id, trail, pool);
+}
+
+
+
+svn_error_t *
+svn_fs_base__dag_clone_root(dag_node_t **root_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *base_root_id, *root_id;
+ node_revision_t *noderev;
+
+ /* Get the node ID's of the root directories of the transaction and
+ its base revision. */
+ SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &base_root_id, fs, txn_id,
+ trail, pool));
+
+ /* Oh, give me a clone...
+ (If they're the same, we haven't cloned the transaction's root
+ directory yet.) */
+ if (svn_fs_base__id_eq(root_id, base_root_id))
+ {
+ const char *base_copy_id = svn_fs_base__id_copy_id(base_root_id);
+
+ /* Of my own flesh and bone...
+ (Get the NODE-REVISION for the base node, and then write
+ it back out as the clone.) */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, base_root_id,
+ trail, pool));
+
+ /* With its Y-chromosome changed to X...
+ (Store it with an updated predecessor count.) */
+ /* ### TODO: Does it even makes sense to have a different copy id for
+ the root node? That is, does this function need a copy_id
+ passed in? */
+ noderev->predecessor_id = svn_fs_base__id_copy(base_root_id, pool);
+ if (noderev->predecessor_count != -1)
+ noderev->predecessor_count++;
+ SVN_ERR(svn_fs_base__create_successor(&root_id, fs, base_root_id,
+ noderev, base_copy_id,
+ txn_id, trail, pool));
+
+ /* ... And when it is grown
+ * Then my own little clone
+ * Will be of the opposite sex!
+ */
+ SVN_ERR(svn_fs_base__set_txn_root(fs, txn_id, root_id, trail, pool));
+ }
+
+ /*
+ * (Sung to the tune of "Home, Home on the Range", with thanks to
+ * Randall Garrett and Isaac Asimov.)
+ */
+
+ /* One way or another, root_id now identifies a cloned root node. */
+ return svn_fs_base__dag_get_node(root_p, fs, root_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_delete(dag_node_t *parent,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *parent_noderev;
+ const char *rep_key, *mutable_rep_key;
+ apr_hash_t *entries = NULL;
+ svn_skel_t *entries_skel;
+ svn_fs_t *fs = parent->fs;
+ svn_string_t str;
+ svn_fs_id_t *id = NULL;
+ dag_node_t *node;
+
+ /* 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_base__dag_check_mutable(parent, txn_id))
+ 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(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
+ trail, pool));
+
+ /* Get the key for the parent's entries list (data) representation. */
+ rep_key = parent_noderev->data_key;
+
+ /* No REP_KEY means no representation, and no representation means
+ no data, and no data means no entries...there's nothing here to
+ delete! */
+ if (! rep_key)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
+ _("Delete failed: directory has no entry '%s'"), name);
+
+ /* Ensure we have a key to a mutable representation of the entries
+ list. We'll have to update the NODE-REVISION if it points to an
+ immutable version. */
+ SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
+ fs, txn_id, trail, pool));
+ if (! svn_fs_base__same_keys(mutable_rep_key, rep_key))
+ {
+ parent_noderev->data_key = mutable_rep_key;
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
+ trail, pool));
+ }
+
+ /* Read the representation, then use it to get the string that holds
+ the entries list. Parse that list into a skel, and parse *that*
+ into a hash. */
+
+ SVN_ERR(svn_fs_base__rep_contents(&str, fs, rep_key, trail, pool));
+ entries_skel = svn_skel__parse(str.data, str.len, pool);
+ if (entries_skel)
+ SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel, pool));
+
+ /* Find NAME in the ENTRIES skel. */
+ if (entries)
+ id = svn_hash_gets(entries, name);
+
+ /* 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 (! id)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
+ _("Delete failed: directory has no entry '%s'"), name);
+
+ /* Use the ID of this ENTRY to get the entry's node. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, svn_fs_base__dag_get_fs(parent),
+ id, trail, pool));
+
+ /* If mutable, remove it and any mutable children from db. */
+ SVN_ERR(svn_fs_base__dag_delete_if_mutable(parent->fs, id, txn_id,
+ trail, pool));
+
+ /* Remove this entry from its parent's entries list. */
+ svn_hash_sets(entries, name, NULL);
+
+ /* Replace the old entries list with the new one. */
+ {
+ svn_stream_t *ws;
+ svn_stringbuf_t *unparsed_entries;
+ apr_size_t len;
+
+ SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries, pool));
+ unparsed_entries = svn_skel__unparse(entries_skel, pool);
+ SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
+ txn_id, TRUE, trail,
+ pool));
+ len = unparsed_entries->len;
+ SVN_ERR(svn_stream_write(ws, unparsed_entries->data, &len));
+ SVN_ERR(svn_stream_close(ws));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_remove_node(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+ node_revision_t *noderev;
+
+ /* Fetch the node. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
+
+ /* If immutable, do nothing and return immediately. */
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted removal of immutable node"));
+
+ /* Get a fresh node-revision. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool));
+
+ /* Delete any mutable property representation. */
+ if (noderev->prop_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key,
+ txn_id, trail, pool));
+
+ /* Delete any mutable data representation. */
+ if (noderev->data_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->data_key,
+ txn_id, trail, pool));
+
+ /* Delete any mutable edit representation (files only). */
+ if (noderev->edit_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
+ txn_id, trail, pool));
+
+ /* Delete the node revision itself. */
+ return svn_fs_base__delete_node_revision(fs, id,
+ noderev->predecessor_id == NULL,
+ trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_delete_if_mutable(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+
+ /* Get the node. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
+
+ /* If immutable, do nothing and return immediately. */
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return SVN_NO_ERROR;
+
+ /* Else it's mutable. Recurse on directories... */
+ if (node->kind == svn_node_dir)
+ {
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+
+ /* Loop over hash entries */
+ SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, pool));
+ if (entries)
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ void *val;
+ svn_fs_dirent_t *dirent;
+
+ apr_hash_this(hi, NULL, NULL, &val);
+ dirent = val;
+ SVN_ERR(svn_fs_base__dag_delete_if_mutable(fs, dirent->id,
+ txn_id, trail,
+ subpool));
+ }
+ }
+ }
+
+ /* ... then delete the node itself, any mutable representations and
+ strings it points to, and possibly its node-origins record. */
+ return svn_fs_base__dag_remove_node(fs, id, txn_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_make_file(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* Call our little helper function */
+ return make_entry(child_p, parent, parent_path, name, FALSE,
+ txn_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_make_dir(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* Call our little helper function */
+ return make_entry(child_p, parent, parent_path, name, TRUE,
+ txn_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_contents(svn_stream_t **contents,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_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 textual contents of a *non*-file node"));
+
+ /* Go get a fresh node-revision for FILE. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
+ trail, pool));
+
+ /* Our job is to _return_ a stream on the file's contents, so the
+ stream has to be trail-independent. Here, we pass NULL to tell
+ the stream that we're not providing it a trail that lives across
+ reads. This means the stream will do each read in a one-off,
+ temporary trail. */
+ return svn_fs_base__rep_contents_read_stream(contents, file->fs,
+ noderev->data_key,
+ FALSE, trail, pool);
+
+ /* Note that we're not registering any `close' func, because there's
+ nothing to cleanup outside of our trail. When the trail is
+ freed, the stream/baton will be too. */
+}
+
+
+svn_error_t *
+svn_fs_base__dag_file_length(svn_filesize_t *length,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_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(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
+ trail, pool));
+ if (noderev->data_key)
+ SVN_ERR(svn_fs_base__rep_contents_size(length, file->fs,
+ noderev->data_key, trail, pool));
+ else
+ *length = 0;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_file_checksum(svn_checksum_t **checksum,
+ svn_checksum_kind_t checksum_kind,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_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(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
+ trail, pool));
+ if (! noderev->data_key)
+ {
+ *checksum = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ if (checksum_kind == svn_checksum_md5)
+ return svn_fs_base__rep_contents_checksums(checksum, NULL, file->fs,
+ noderev->data_key,
+ trail, pool);
+ else if (checksum_kind == svn_checksum_sha1)
+ return svn_fs_base__rep_contents_checksums(NULL, checksum, file->fs,
+ noderev->data_key,
+ trail, pool);
+ else
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_edit_stream(svn_stream_t **contents,
+ dag_node_t *file,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = file->fs; /* just for nicer indentation */
+ node_revision_t *noderev;
+ const char *mutable_rep_key;
+ 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_base__dag_check_mutable(file, txn_id))
+ 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(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
+ trail, pool));
+
+ /* If this node already has an EDIT-DATA-KEY, destroy the data
+ associated with that key. */
+ if (noderev->edit_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
+ txn_id, trail, pool));
+
+ /* Now, let's ensure that we have a new EDIT-DATA-KEY available for
+ use. */
+ SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, NULL, fs,
+ txn_id, trail, pool));
+
+ /* We made a new rep, so update the node revision. */
+ noderev->edit_key = mutable_rep_key;
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev,
+ trail, pool));
+
+ /* Return a writable stream with which to set new contents. */
+ SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
+ txn_id, FALSE, trail,
+ pool));
+ *contents = ws;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_fs_base__dag_finalize_edits(dag_node_t *file,
+ const svn_checksum_t *checksum,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = file->fs; /* just for nicer indentation */
+ node_revision_t *noderev;
+ const char *old_data_key, *new_data_key, *useless_data_key = NULL;
+ const char *data_key_uniquifier = NULL;
+ svn_checksum_t *md5_checksum, *sha1_checksum;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* 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_base__dag_check_mutable(file, txn_id))
+ 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(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
+ trail, pool));
+
+ /* If this node has no EDIT-DATA-KEY, this is a no-op. */
+ if (! noderev->edit_key)
+ return SVN_NO_ERROR;
+
+ /* Get our representation's checksums. */
+ SVN_ERR(svn_fs_base__rep_contents_checksums(&md5_checksum, &sha1_checksum,
+ fs, noderev->edit_key,
+ trail, pool));
+
+ /* If our caller provided a checksum of the right kind to compare, do so. */
+ if (checksum)
+ {
+ svn_checksum_t *test_checksum;
+
+ if (checksum->kind == svn_checksum_md5)
+ test_checksum = md5_checksum;
+ else if (checksum->kind == svn_checksum_sha1)
+ test_checksum = sha1_checksum;
+ else
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
+
+ if (! svn_checksum_match(checksum, test_checksum))
+ return svn_checksum_mismatch_err(checksum, test_checksum, pool,
+ _("Checksum mismatch on representation '%s'"),
+ noderev->edit_key);
+ }
+
+ /* Now, we want to delete the old representation and replace it with
+ the new. Of course, we don't actually delete anything until
+ everything is being properly referred to by the node-revision
+ skel.
+
+ Now, if the result of all this editing is that we've created a
+ representation that describes content already represented
+ immutably in our database, we don't even need to keep these edits.
+ We can simply point our data_key at that pre-existing
+ representation and throw away our work! In this situation,
+ though, we'll need a unique ID to help other code distinguish
+ between "the contents weren't touched" and "the contents were
+ touched but still look the same" (to state it oversimply). */
+ old_data_key = noderev->data_key;
+ if (sha1_checksum && bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
+ {
+ svn_error_t *err = svn_fs_bdb__get_checksum_rep(&new_data_key, fs,
+ sha1_checksum,
+ trail, pool);
+ if (! err)
+ {
+ useless_data_key = noderev->edit_key;
+ err = svn_fs_bdb__reserve_rep_reuse_id(&data_key_uniquifier,
+ trail->fs, trail, pool);
+ }
+ else if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_CHECKSUM_REP))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ new_data_key = noderev->edit_key;
+ }
+ SVN_ERR(err);
+ }
+ else
+ {
+ new_data_key = noderev->edit_key;
+ }
+
+ noderev->data_key = new_data_key;
+ noderev->data_key_uniquifier = data_key_uniquifier;
+ noderev->edit_key = NULL;
+
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, trail, pool));
+
+ /* Only *now* can we safely destroy the old representation (if it
+ even existed in the first place). */
+ if (old_data_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, old_data_key, txn_id,
+ trail, pool));
+
+ /* If we've got a discardable rep (probably because we ended up
+ re-using a preexisting one), throw out the discardable rep. */
+ if (useless_data_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, useless_data_key,
+ txn_id, trail, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+dag_node_t *
+svn_fs_base__dag_dup(dag_node_t *node,
+ apr_pool_t *pool)
+{
+ /* Allocate our new node. */
+ dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node));
+
+ new_node->fs = node->fs;
+ new_node->id = svn_fs_base__id_copy(node->id, pool);
+ new_node->kind = node->kind;
+ new_node->created_path = apr_pstrdup(pool, node->created_path);
+ return new_node;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_open(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *node_id;
+
+ /* Ensure that NAME exists in PARENT's entry list. */
+ SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, trail, pool));
+ if (! node_id)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Attempted to open non-existent child node '%s'"), 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 open node with an illegal name '%s'"), name);
+
+ /* Now get the node that was requested. */
+ return svn_fs_base__dag_get_node(child_p, svn_fs_base__dag_get_fs(parent),
+ node_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__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,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *id;
+
+ if (preserve_history)
+ {
+ node_revision_t *noderev;
+ const char *copy_id;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(from_node);
+ const svn_fs_id_t *src_id = svn_fs_base__dag_get_id(from_node);
+ const char *from_txn_id = NULL;
+
+ /* Make a copy of the original node revision. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, from_node->id,
+ trail, pool));
+
+ /* Reserve a copy ID for this new copy. */
+ SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, pool));
+
+ /* Create a successor with its predecessor pointing at the copy
+ source. */
+ noderev->predecessor_id = svn_fs_base__id_copy(src_id, pool);
+ if (noderev->predecessor_count != -1)
+ noderev->predecessor_count++;
+ noderev->created_path = svn_fspath__join
+ (svn_fs_base__dag_get_created_path(to_node), entry, pool);
+ SVN_ERR(svn_fs_base__create_successor(&id, fs, src_id, noderev,
+ copy_id, txn_id, trail, pool));
+
+ /* Translate FROM_REV into a transaction ID. */
+ SVN_ERR(svn_fs_base__rev_get_txn_id(&from_txn_id, fs, from_rev,
+ trail, pool));
+
+ /* Now that we've done the copy, we need to add the information
+ about the copy to the `copies' table, using the COPY_ID we
+ reserved above. */
+ SVN_ERR(svn_fs_bdb__create_copy
+ (fs, copy_id,
+ svn_fs__canonicalize_abspath(from_path, pool),
+ from_txn_id, id, copy_kind_real, trail, pool));
+
+ /* Finally, add the COPY_ID to the transaction's list of copies
+ so that, if this transaction is aborted, the `copies' table
+ entry we added above will be cleaned up. */
+ SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id, trail, pool));
+ }
+ else /* don't preserve history */
+ {
+ id = svn_fs_base__dag_get_id(from_node);
+ }
+
+ /* Set the entry in to_node to the new id. */
+ return svn_fs_base__dag_set_entry(to_node, entry, id, txn_id,
+ trail, pool);
+}
+
+
+
+/*** Deltification ***/
+
+/* Maybe change the representation identified by TARGET_REP_KEY to be
+ a delta against the representation identified by SOURCE_REP_KEY.
+ Some reasons why we wouldn't include:
+
+ - TARGET_REP_KEY and SOURCE_REP_KEY are the same key.
+
+ - TARGET_REP_KEY's representation isn't mutable in TXN_ID (if
+ TXN_ID is non-NULL).
+
+ - The delta provides less space savings that a fulltext (this is
+ a detail handled by lower logic layers, not this function).
+
+ Do this work in TRAIL, using POOL for necessary allocations.
+*/
+static svn_error_t *
+maybe_deltify_mutable_rep(const char *target_rep_key,
+ const char *source_rep_key,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ if (! (target_rep_key && source_rep_key
+ && (strcmp(target_rep_key, source_rep_key) != 0)))
+ return SVN_NO_ERROR;
+
+ if (txn_id)
+ {
+ representation_t *target_rep;
+ SVN_ERR(svn_fs_bdb__read_rep(&target_rep, trail->fs, target_rep_key,
+ trail, pool));
+ if (strcmp(target_rep->txn_id, txn_id) != 0)
+ return SVN_NO_ERROR;
+ }
+
+ return svn_fs_base__rep_deltify(trail->fs, target_rep_key, source_rep_key,
+ trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_deltify(dag_node_t *target,
+ dag_node_t *source,
+ svn_boolean_t props_only,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *source_nr, *target_nr;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(target);
+
+ /* Get node revisions for the two nodes. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&target_nr, fs, target->id,
+ trail, pool));
+ SVN_ERR(svn_fs_bdb__get_node_revision(&source_nr, fs, source->id,
+ trail, pool));
+
+ /* If TARGET and SOURCE both have properties, and are not sharing a
+ property key, deltify TARGET's properties. */
+ SVN_ERR(maybe_deltify_mutable_rep(target_nr->prop_key, source_nr->prop_key,
+ txn_id, trail, pool));
+
+ /* If we are not only attending to properties, and if TARGET and
+ SOURCE both have data, and are not sharing a data key, deltify
+ TARGET's data. */
+ if (! props_only)
+ SVN_ERR(maybe_deltify_mutable_rep(target_nr->data_key, source_nr->data_key,
+ txn_id, trail, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Maybe store a `checksum-reps' index record for the representation whose
+ key is REP. (If there's already a rep for this checksum, we don't
+ bother overwriting it.) */
+static svn_error_t *
+maybe_store_checksum_rep(const char *rep,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_fs_t *fs = trail->fs;
+ svn_checksum_t *sha1_checksum;
+
+ /* We want the SHA1 checksum, if any. */
+ SVN_ERR(svn_fs_base__rep_contents_checksums(NULL, &sha1_checksum,
+ fs, rep, trail, pool));
+ if (sha1_checksum)
+ {
+ err = svn_fs_bdb__set_checksum_rep(fs, sha1_checksum, rep, trail, pool);
+ if (err && (err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ }
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_fs_base__dag_index_checksums(dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *node_rev;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, trail->fs, node->id,
+ trail, pool));
+ if ((node_rev->kind == svn_node_file) && node_rev->data_key)
+ SVN_ERR(maybe_store_checksum_rep(node_rev->data_key, trail, pool));
+ if (node_rev->prop_key)
+ SVN_ERR(maybe_store_checksum_rep(node_rev->prop_key, trail, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Committing ***/
+
+svn_error_t *
+svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ revision_t revision;
+ svn_string_t date;
+ apr_hash_t *txnprops;
+ svn_fs_t *fs = txn->fs;
+ const char *txn_id = txn->id;
+
+ /* Remove any temporary transaction properties initially created by
+ begin_txn(). */
+ SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, txn_id, trail));
+
+ /* Add new revision entry to `revisions' table. */
+ revision.txn_id = txn_id;
+ *new_rev = SVN_INVALID_REVNUM;
+ SVN_ERR(svn_fs_bdb__put_rev(new_rev, fs, &revision, trail, pool));
+
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
+ SVN_ERR(svn_fs_base__set_txn_prop
+ (fs, txn_id, SVN_FS__PROP_TXN_CHECK_OOD, NULL, trail, pool));
+
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
+ SVN_ERR(svn_fs_base__set_txn_prop
+ (fs, txn_id, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, trail, pool));
+
+ /* Promote the unfinished transaction to a committed one. */
+ SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, *new_rev,
+ trail, pool));
+
+ /* Set a date on the commit. We wait until now to fetch the date,
+ so it's definitely newer than any previous revision's date. */
+ date.data = svn_time_to_cstring(apr_time_now(), pool);
+ date.len = strlen(date.data);
+ return svn_fs_base__set_rev_prop(fs, *new_rev, SVN_PROP_REVISION_DATE,
+ NULL, &date, trail, pool);
+}
+
+
+/*** Comparison. ***/
+
+svn_error_t *
+svn_fs_base__things_different(svn_boolean_t *props_changed,
+ svn_boolean_t *contents_changed,
+ dag_node_t *node1,
+ dag_node_t *node2,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev1, *noderev2;
+
+ /* If we have no place to store our results, don't bother doing
+ anything. */
+ if (! props_changed && ! contents_changed)
+ return SVN_NO_ERROR;
+
+ /* The node revision skels for these two nodes. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev1, node1->fs, node1->id,
+ trail, pool));
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev2, node2->fs, node2->id,
+ trail, pool));
+
+ /* Compare property keys. */
+ if (props_changed != NULL)
+ *props_changed = (! svn_fs_base__same_keys(noderev1->prop_key,
+ noderev2->prop_key));
+
+ /* Compare contents keys and their (optional) uniquifiers. */
+ if (contents_changed != NULL)
+ *contents_changed =
+ (! (svn_fs_base__same_keys(noderev1->data_key,
+ noderev2->data_key)
+ /* Technically, these uniquifiers aren't used and "keys",
+ but keys are base-36 stringified numbers, so we'll take
+ this liberty. */
+ && (svn_fs_base__same_keys(noderev1->data_key_uniquifier,
+ noderev2->data_key_uniquifier))));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Mergeinfo tracking stuff ***/
+
+svn_error_t *
+svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo,
+ apr_int64_t *count,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *node_rev;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
+ const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
+ if (has_mergeinfo)
+ *has_mergeinfo = node_rev->has_mergeinfo;
+ if (count)
+ *count = node_rev->mergeinfo_count;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node,
+ svn_boolean_t has_mergeinfo,
+ svn_boolean_t *had_mergeinfo,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *node_rev;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
+ const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
+
+ SVN_ERR(svn_fs_base__test_required_feature_format
+ (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
+
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted merge tracking info change on "
+ "immutable node"));
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
+ *had_mergeinfo = node_rev->has_mergeinfo;
+
+ /* Are we changing the node? */
+ if ((! has_mergeinfo) != (! *had_mergeinfo))
+ {
+ /* Note the new has-mergeinfo state. */
+ node_rev->has_mergeinfo = has_mergeinfo;
+
+ /* Increment or decrement the mergeinfo count as necessary. */
+ if (has_mergeinfo)
+ node_rev->mergeinfo_count++;
+ else
+ node_rev->mergeinfo_count--;
+
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node,
+ apr_int64_t count_delta,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *node_rev;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
+ const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
+
+ SVN_ERR(svn_fs_base__test_required_feature_format
+ (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
+
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted mergeinfo count change on "
+ "immutable node"));
+
+ if (count_delta == 0)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
+ node_rev->mergeinfo_count = node_rev->mergeinfo_count + count_delta;
+ if ((node_rev->mergeinfo_count < 0)
+ || ((node->kind == svn_node_file) && (node_rev->mergeinfo_count > 1)))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ apr_psprintf(pool,
+ _("Invalid value (%%%s) for node "
+ "revision mergeinfo count"),
+ APR_INT64_T_FMT),
+ node_rev->mergeinfo_count);
+
+ return svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool);
+}
OpenPOWER on IntegriCloud