diff options
Diffstat (limited to 'subversion/libsvn_fs_base/dag.c')
-rw-r--r-- | subversion/libsvn_fs_base/dag.c | 1758 |
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(©_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(©_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); +} |