aboutsummaryrefslogtreecommitdiff
path: root/subversion/libsvn_delta/branch.c
diff options
context:
space:
mode:
authorPeter Wemm <peter@FreeBSD.org>2018-05-08 03:44:38 +0000
committerPeter Wemm <peter@FreeBSD.org>2018-05-08 03:44:38 +0000
commit3faf8d6bffc5d0fb2525ba37bb504c53366caf9d (patch)
tree7e47911263e75034b767fe34b2f8d3d17e91f66d /subversion/libsvn_delta/branch.c
parenta55fb3c0d5eca7d887798125d5b95942b1f01d4b (diff)
Diffstat (limited to 'subversion/libsvn_delta/branch.c')
-rw-r--r--subversion/libsvn_delta/branch.c1699
1 files changed, 1699 insertions, 0 deletions
diff --git a/subversion/libsvn_delta/branch.c b/subversion/libsvn_delta/branch.c
new file mode 100644
index 000000000000..95355d44650d
--- /dev/null
+++ b/subversion/libsvn_delta/branch.c
@@ -0,0 +1,1699 @@
+/*
+ * branch.c : Element-Based Branching and Move Tracking.
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_iter.h"
+#include "svn_pools.h"
+
+#include "private/svn_element.h"
+#include "private/svn_branch.h"
+#include "private/svn_branch_impl.h"
+#include "private/svn_sorts_private.h"
+
+#include "svn_private_config.h"
+
+
+/* Is EID allocated (no matter whether an element with this id exists)? */
+#define EID_IS_ALLOCATED(branch, eid) \
+ ((eid) >= (branch)->txn->priv->first_eid \
+ && (eid) < (branch)->txn->priv->next_eid)
+
+#define IS_BRANCH_ROOT_EID(branch, eid) \
+ ((eid) == (branch)->priv->element_tree->root_eid)
+
+/* Is BRANCH1 the same branch as BRANCH2? Compare by full branch-ids; don't
+ require identical branch objects. */
+#define BRANCH_IS_SAME_BRANCH(branch1, branch2, scratch_pool) \
+ (strcmp(svn_branch__get_id(branch1, scratch_pool), \
+ svn_branch__get_id(branch2, scratch_pool)) == 0)
+
+struct svn_branch__txn_priv_t
+{
+ /* All branches. */
+ apr_array_header_t *branches;
+
+ /* The range of element ids assigned. */
+ /* EIDs local to the txn are negative, assigned by decrementing FIRST_EID
+ * (skipping -1). */
+ int first_eid, next_eid;
+
+};
+
+struct svn_branch__state_priv_t
+{
+ /* EID -> svn_element__content_t mapping. */
+ svn_element__tree_t *element_tree;
+
+ /* Merge history for this branch state. */
+ svn_branch__history_t *history;
+
+ svn_boolean_t is_flat;
+
+};
+
+static svn_branch__state_t *
+branch_state_create(const char *bid,
+ int root_eid,
+ svn_branch__txn_t *txn,
+ apr_pool_t *result_pool);
+
+static svn_error_t *
+branch_instantiate_elements(svn_branch__state_t *to_branch,
+ const svn_element__tree_t *elements,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+svn_branch__map_add_subtree(svn_branch__state_t *to_branch,
+ int to_eid,
+ svn_branch__eid_t new_parent_eid,
+ const char *new_name,
+ svn_element__tree_t *new_subtree,
+ apr_pool_t *scratch_pool);
+
+/* */
+static apr_pool_t *
+branch_state_pool_get(svn_branch__state_t *branch)
+{
+ return apr_hash_pool_get(branch->priv->element_tree->e_map);
+}
+
+/* ### Layering: we didn't want to look at the whole repos in here, but
+ copying seems to require it. */
+svn_error_t *
+svn_branch__repos_get_branch_by_id(svn_branch__state_t **branch_p,
+ const svn_branch__repos_t *repos,
+ svn_revnum_t revnum,
+ const char *branch_id,
+ apr_pool_t *scratch_pool);
+
+/* */
+static svn_error_t *
+branch_in_rev_or_txn(svn_branch__state_t **src_branch,
+ const svn_branch__rev_bid_eid_t *src_el_rev,
+ svn_branch__txn_t *txn,
+ apr_pool_t *result_pool)
+{
+ if (SVN_IS_VALID_REVNUM(src_el_rev->rev))
+ {
+ SVN_ERR(svn_branch__repos_get_branch_by_id(src_branch,
+ txn->repos,
+ src_el_rev->rev,
+ src_el_rev->bid,
+ result_pool));
+ }
+ else
+ {
+ *src_branch
+ = svn_branch__txn_get_branch_by_id(txn, src_el_rev->bid, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static apr_array_header_t *
+branch_txn_get_branches(const svn_branch__txn_t *txn,
+ apr_pool_t *result_pool)
+{
+ return apr_array_copy(result_pool, txn->priv->branches);
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+branch_txn_delete_branch(svn_branch__txn_t *txn,
+ const char *bid,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+
+ for (i = 0; i < txn->priv->branches->nelts; i++)
+ {
+ svn_branch__state_t *b = APR_ARRAY_IDX(txn->priv->branches, i, void *);
+
+ if (strcmp(b->bid, bid) == 0)
+ {
+ svn_sort__array_delete(txn->priv->branches, i, 1);
+ break;
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+branch_txn_get_num_new_eids(const svn_branch__txn_t *txn,
+ int *num_new_eids_p,
+ apr_pool_t *scratch_pool)
+{
+ if (num_new_eids_p)
+ *num_new_eids_p = -1 - txn->priv->first_eid;
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+branch_txn_new_eid(svn_branch__txn_t *txn,
+ svn_branch__eid_t *eid_p,
+ apr_pool_t *scratch_pool)
+{
+ int eid = (txn->priv->first_eid < 0) ? txn->priv->first_eid - 1 : -2;
+
+ txn->priv->first_eid = eid;
+ if (eid_p)
+ *eid_p = eid;
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+branch_txn_open_branch(svn_branch__txn_t *txn,
+ svn_branch__state_t **new_branch_p,
+ const char *branch_id,
+ int root_eid,
+ svn_branch__rev_bid_eid_t *tree_ref,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__state_t *new_branch;
+
+ /* if the branch already exists, just return it, else create it */
+ new_branch
+ = svn_branch__txn_get_branch_by_id(txn, branch_id, scratch_pool);
+ if (new_branch)
+ {
+ SVN_ERR_ASSERT(root_eid == svn_branch__root_eid(new_branch));
+ }
+ else
+ {
+ SVN_ERR_ASSERT_NO_RETURN(root_eid != -1);
+
+ new_branch = branch_state_create(branch_id, root_eid, txn,
+ txn->priv->branches->pool);
+ APR_ARRAY_PUSH(txn->priv->branches, void *) = new_branch;
+ }
+
+ if (tree_ref)
+ {
+ svn_branch__state_t *from_branch;
+ svn_element__tree_t *tree;
+
+ SVN_ERR(branch_in_rev_or_txn(&from_branch, tree_ref, txn, scratch_pool));
+ /* Source branch must exist */
+ if (! from_branch)
+ {
+ return svn_error_createf(SVN_BRANCH__ERR, NULL,
+ _("Cannot branch from r%ld %s e%d: "
+ "branch does not exist"),
+ tree_ref->rev, tree_ref->bid, tree_ref->eid);
+ }
+
+ SVN_ERR_ASSERT(from_branch->priv->is_flat);
+
+ SVN_ERR(svn_branch__state_get_elements(from_branch, &tree,
+ scratch_pool));
+ tree = svn_element__tree_get_subtree_at_eid(tree, tree_ref->eid,
+ scratch_pool);
+ /* Source element must exist */
+ if (! tree)
+ {
+ return svn_error_createf(SVN_BRANCH__ERR, NULL,
+ _("Cannot branch from r%ld %s e%d: "
+ "element does not exist"),
+ tree_ref->rev, tree_ref->bid, tree_ref->eid);
+ }
+
+ /* Populate the tree from the 'from' source */
+ SVN_ERR(branch_instantiate_elements(new_branch, tree, scratch_pool));
+ }
+
+ if (new_branch_p)
+ *new_branch_p = new_branch;
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+branch_txn_sequence_point(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+
+ /* purge elements in each branch */
+ for (i = 0; i < txn->priv->branches->nelts; i++)
+ {
+ svn_branch__state_t *b
+ = APR_ARRAY_IDX(txn->priv->branches, i, void *);
+
+ SVN_ERR(svn_branch__state_purge(b, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+branch_txn_complete(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+branch_txn_abort(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+/*
+ * ========================================================================
+ * Branch Txn Object
+ * ========================================================================
+ */
+
+apr_array_header_t *
+svn_branch__txn_get_branches(const svn_branch__txn_t *txn,
+ apr_pool_t *result_pool)
+{
+ apr_array_header_t *branches
+ = txn->vtable->get_branches(txn,
+ result_pool);
+ return branches;
+}
+
+svn_error_t *
+svn_branch__txn_delete_branch(svn_branch__txn_t *txn,
+ const char *bid,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(txn->vtable->delete_branch(txn,
+ bid,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__txn_get_num_new_eids(const svn_branch__txn_t *txn,
+ int *num_new_eids_p,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(txn->vtable->get_num_new_eids(txn,
+ num_new_eids_p,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__txn_new_eid(svn_branch__txn_t *txn,
+ int *new_eid_p,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(txn->vtable->new_eid(txn,
+ new_eid_p,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__txn_open_branch(svn_branch__txn_t *txn,
+ svn_branch__state_t **new_branch_p,
+ const char *branch_id,
+ int root_eid,
+ svn_branch__rev_bid_eid_t *tree_ref,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(txn->vtable->open_branch(txn,
+ new_branch_p,
+ branch_id,
+ root_eid, tree_ref, result_pool,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__txn_finalize_eids(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(txn->vtable->finalize_eids(txn,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__txn_serialize(svn_branch__txn_t *txn,
+ svn_stream_t *stream,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(txn->vtable->serialize(txn,
+ stream,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__txn_sequence_point(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(txn->vtable->sequence_point(txn,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__txn_complete(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(txn->vtable->complete(txn,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__txn_abort(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(txn->vtable->abort(txn,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_branch__txn_t *
+svn_branch__txn_create(const svn_branch__txn_vtable_t *vtable,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool)
+{
+ svn_branch__txn_t *txn = apr_pcalloc(result_pool, sizeof(*txn));
+
+ txn->vtable = apr_pmemdup(result_pool, vtable, sizeof(*vtable));
+
+ txn->vtable->vpriv.cancel_func = cancel_func;
+ txn->vtable->vpriv.cancel_baton = cancel_baton;
+
+#ifdef ENABLE_ORDERING_CHECK
+ txn->vtable->vpriv.within_callback = FALSE;
+ txn->vtable->vpriv.finished = FALSE;
+ txn->vtable->vpriv.state_pool = result_pool;
+#endif
+
+ return txn;
+}
+
+/*
+ * ========================================================================
+ */
+
+/* */
+static const char *
+branch_finalize_bid(const char *bid,
+ int mapping_offset,
+ apr_pool_t *result_pool)
+{
+ const char *outer_bid;
+ int outer_eid;
+
+ svn_branch__id_unnest(&outer_bid, &outer_eid, bid, result_pool);
+
+ if (outer_bid)
+ {
+ outer_bid = branch_finalize_bid(outer_bid, mapping_offset, result_pool);
+ }
+
+ if (outer_eid < -1)
+ {
+ outer_eid = mapping_offset - outer_eid;
+ }
+
+ return svn_branch__id_nest(outer_bid, outer_eid, result_pool);
+}
+
+/* Change txn-local EIDs (negative integers) in BRANCH to revision EIDs, by
+ * assigning a new revision-EID (positive integer) for each one.
+ */
+static svn_error_t *
+branch_finalize_eids(svn_branch__state_t *branch,
+ int mapping_offset,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ branch->bid = branch_finalize_bid(branch->bid, mapping_offset,
+ branch_state_pool_get(branch));
+ if (branch->priv->element_tree->root_eid < -1)
+ {
+ branch->priv->element_tree->root_eid
+ = mapping_offset - branch->priv->element_tree->root_eid;
+ }
+
+ for (hi = apr_hash_first(scratch_pool, branch->priv->element_tree->e_map);
+ hi; hi = apr_hash_next(hi))
+ {
+ int old_eid = svn_eid__hash_this_key(hi);
+ svn_element__content_t *element = apr_hash_this_val(hi);
+
+ if (old_eid < -1)
+ {
+ int new_eid = mapping_offset - old_eid;
+
+ svn_element__tree_set(branch->priv->element_tree, old_eid, NULL);
+ svn_element__tree_set(branch->priv->element_tree, new_eid, element);
+ }
+ if (element->parent_eid < -1)
+ {
+ element->parent_eid = mapping_offset - element->parent_eid;
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+branch_txn_finalize_eids(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ int n_txn_eids = (-1) - txn->priv->first_eid;
+ int mapping_offset;
+ apr_array_header_t *branches = branch_txn_get_branches(txn, scratch_pool);
+ int i;
+
+ if (txn->priv->first_eid == 0)
+ return SVN_NO_ERROR;
+
+ /* mapping from txn-local (negative) EID to committed (positive) EID is:
+ txn_local_eid == -2 => committed_eid := (txn.next_eid + 0)
+ txn_local_eid == -3 => committed_eid := (txn.next_eid + 1) ... */
+ mapping_offset = txn->priv->next_eid - 2;
+
+ for (i = 0; i < branches->nelts; i++)
+ {
+ svn_branch__state_t *b = APR_ARRAY_IDX(branches, i, void *);
+
+ SVN_ERR(branch_finalize_eids(b, mapping_offset, scratch_pool));
+ }
+
+ txn->priv->next_eid += n_txn_eids;
+ txn->priv->first_eid = 0;
+ return SVN_NO_ERROR;
+}
+
+/*
+ * ========================================================================
+ */
+
+static svn_error_t *
+branch_txn_serialize(svn_branch__txn_t *txn,
+ svn_stream_t *stream,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *branches = branch_txn_get_branches(txn, scratch_pool);
+ int i;
+
+ SVN_ERR(svn_stream_printf(stream, scratch_pool,
+ "r%ld: eids %d %d "
+ "branches %d\n",
+ txn->rev,
+ txn->priv->first_eid, txn->priv->next_eid,
+ branches->nelts));
+
+ for (i = 0; i < branches->nelts; i++)
+ {
+ svn_branch__state_t *branch = APR_ARRAY_IDX(branches, i, void *);
+
+ SVN_ERR(svn_branch__state_serialize(stream, branch, scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/*
+ * ========================================================================
+ */
+
+svn_branch__state_t *
+svn_branch__txn_get_branch_by_id(const svn_branch__txn_t *txn,
+ const char *branch_id,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *branches = svn_branch__txn_get_branches(txn, scratch_pool);
+ int i;
+ svn_branch__state_t *branch = NULL;
+
+ for (i = 0; i < branches->nelts; i++)
+ {
+ svn_branch__state_t *b = APR_ARRAY_IDX(branches, i, void *);
+
+ if (strcmp(svn_branch__get_id(b, scratch_pool), branch_id) == 0)
+ {
+ branch = b;
+ break;
+ }
+ }
+ return branch;
+}
+
+/*
+ * ========================================================================
+ */
+
+/* Create a new branch txn object.
+ *
+ * It will have no branches.
+ */
+static svn_branch__txn_t *
+branch_txn_create(svn_branch__repos_t *repos,
+ svn_revnum_t rev,
+ svn_revnum_t base_rev,
+ apr_pool_t *result_pool)
+{
+ static const svn_branch__txn_vtable_t vtable = {
+ {0},
+ branch_txn_get_branches,
+ branch_txn_delete_branch,
+ branch_txn_get_num_new_eids,
+ branch_txn_new_eid,
+ branch_txn_open_branch,
+ branch_txn_finalize_eids,
+ branch_txn_serialize,
+ branch_txn_sequence_point,
+ branch_txn_complete,
+ branch_txn_abort,
+ };
+ svn_branch__txn_t *txn
+ = svn_branch__txn_create(&vtable, NULL, NULL, result_pool);
+
+ txn->priv = apr_pcalloc(result_pool, sizeof(*txn->priv));
+ txn->repos = repos;
+ txn->rev = rev;
+ txn->base_rev = base_rev;
+ txn->priv->branches = apr_array_make(result_pool, 0, sizeof(void *));
+ return txn;
+}
+
+/*
+ * ========================================================================
+ */
+
+static void
+branch_validate_element(const svn_branch__state_t *branch,
+ int eid,
+ const svn_element__content_t *element);
+
+/* Assert BRANCH satisfies all its invariants.
+ */
+static void
+assert_branch_state_invariants(const svn_branch__state_t *branch,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ assert(branch->bid);
+ assert(branch->txn);
+ assert(branch->priv->element_tree);
+ assert(branch->priv->element_tree->e_map);
+
+ /* Validate elements in the map */
+ for (hi = apr_hash_first(scratch_pool, branch->priv->element_tree->e_map);
+ hi; hi = apr_hash_next(hi))
+ {
+ branch_validate_element(branch, svn_eid__hash_this_key(hi),
+ apr_hash_this_val(hi));
+ }
+}
+
+/* An #svn_branch__state_t method. */
+static svn_error_t *
+branch_state_copy_one(svn_branch__state_t *branch,
+ const svn_branch__rev_bid_eid_t *src_el_rev,
+ svn_branch__eid_t eid,
+ svn_branch__eid_t new_parent_eid,
+ const char *new_name,
+ const svn_element__payload_t *new_payload,
+ apr_pool_t *scratch_pool)
+{
+ /* New payload shall be the same as the source if NEW_PAYLOAD is null. */
+ /* ### if (! new_payload)
+ {
+ new_payload = branch_map_get(branch, eid)->payload;
+ }
+ */
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy a subtree.
+ *
+ * Adjust TO_BRANCH and its subbranches (recursively), to reflect a copy
+ * of a subtree from FROM_EL_REV to TO_PARENT_EID:TO_NAME.
+ *
+ * FROM_EL_REV must be an existing element. (It may be a branch root.)
+ *
+ * ### TODO:
+ * If FROM_EL_REV is the root of a subbranch and/or contains nested
+ * subbranches, also copy them ...
+ * ### What shall we do with a subbranch? Make plain copies of its raw
+ * elements; make a subbranch by branching the source subbranch?
+ *
+ * TO_PARENT_EID must be a directory element in TO_BRANCH, and TO_NAME a
+ * non-existing path in it.
+ */
+static svn_error_t *
+copy_subtree(const svn_branch__el_rev_id_t *from_el_rev,
+ svn_branch__state_t *to_branch,
+ svn_branch__eid_t to_parent_eid,
+ const char *to_name,
+ apr_pool_t *scratch_pool)
+{
+ svn_element__tree_t *new_subtree;
+
+ SVN_ERR_ASSERT(from_el_rev->branch->priv->is_flat);
+
+ SVN_ERR(svn_branch__state_get_elements(from_el_rev->branch, &new_subtree,
+ scratch_pool));
+ new_subtree = svn_element__tree_get_subtree_at_eid(new_subtree,
+ from_el_rev->eid,
+ scratch_pool);
+
+ /* copy the subtree, assigning new EIDs */
+ SVN_ERR(svn_branch__map_add_subtree(to_branch, -1 /*to_eid*/,
+ to_parent_eid, to_name,
+ new_subtree,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__state_t method. */
+static svn_error_t *
+branch_state_copy_tree(svn_branch__state_t *to_branch,
+ const svn_branch__rev_bid_eid_t *src_el_rev,
+ svn_branch__eid_t new_parent_eid,
+ const char *new_name,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__txn_t *txn = to_branch->txn;
+ svn_branch__state_t *src_branch;
+ svn_branch__el_rev_id_t *from_el_rev;
+
+ SVN_ERR(branch_in_rev_or_txn(&src_branch, src_el_rev, txn, scratch_pool));
+ from_el_rev = svn_branch__el_rev_id_create(src_branch, src_el_rev->eid,
+ src_el_rev->rev, scratch_pool);
+ SVN_ERR(copy_subtree(from_el_rev,
+ to_branch, new_parent_eid, new_name,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_branch__get_id(const svn_branch__state_t *branch,
+ apr_pool_t *result_pool)
+{
+ return branch->bid;
+}
+
+int
+svn_branch__root_eid(const svn_branch__state_t *branch)
+{
+ svn_element__tree_t *elements;
+
+ svn_error_clear(svn_branch__state_get_elements(branch, &elements,
+ NULL/*scratch_pool*/));
+ return elements->root_eid;
+}
+
+svn_branch__el_rev_id_t *
+svn_branch__el_rev_id_create(svn_branch__state_t *branch,
+ int eid,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool)
+{
+ svn_branch__el_rev_id_t *id = apr_palloc(result_pool, sizeof(*id));
+
+ id->branch = branch;
+ id->eid = eid;
+ id->rev = rev;
+ return id;
+}
+
+svn_branch__el_rev_id_t *
+svn_branch__el_rev_id_dup(const svn_branch__el_rev_id_t *old_id,
+ apr_pool_t *result_pool)
+{
+ if (! old_id)
+ return NULL;
+
+ return svn_branch__el_rev_id_create(old_id->branch,
+ old_id->eid,
+ old_id->rev,
+ result_pool);
+}
+
+svn_branch__rev_bid_eid_t *
+svn_branch__rev_bid_eid_create(svn_revnum_t rev,
+ const char *branch_id,
+ int eid,
+ apr_pool_t *result_pool)
+{
+ svn_branch__rev_bid_eid_t *id = apr_palloc(result_pool, sizeof(*id));
+
+ id->bid = apr_pstrdup(result_pool, branch_id);
+ id->eid = eid;
+ id->rev = rev;
+ return id;
+}
+
+svn_branch__rev_bid_eid_t *
+svn_branch__rev_bid_eid_dup(const svn_branch__rev_bid_eid_t *old_id,
+ apr_pool_t *result_pool)
+{
+ svn_branch__rev_bid_eid_t *id;
+
+ if (! old_id)
+ return NULL;
+
+ id = apr_pmemdup(result_pool, old_id, sizeof(*id));
+ id->bid = apr_pstrdup(result_pool, old_id->bid);
+ return id;
+}
+
+svn_branch__rev_bid_t *
+svn_branch__rev_bid_create(svn_revnum_t rev,
+ const char *branch_id,
+ apr_pool_t *result_pool)
+{
+ svn_branch__rev_bid_t *id = apr_palloc(result_pool, sizeof(*id));
+
+ id->bid = apr_pstrdup(result_pool, branch_id);
+ id->rev = rev;
+ return id;
+}
+
+svn_branch__rev_bid_t *
+svn_branch__rev_bid_dup(const svn_branch__rev_bid_t *old_id,
+ apr_pool_t *result_pool)
+{
+ svn_branch__rev_bid_t *id;
+
+ if (! old_id)
+ return NULL;
+
+ id = apr_pmemdup(result_pool, old_id, sizeof(*id));
+ id->bid = apr_pstrdup(result_pool, old_id->bid);
+ return id;
+}
+
+svn_boolean_t
+svn_branch__rev_bid_equal(const svn_branch__rev_bid_t *id1,
+ const svn_branch__rev_bid_t *id2)
+{
+ return (id1->rev == id2->rev
+ && strcmp(id1->bid, id2->bid) == 0);
+}
+
+svn_branch__history_t *
+svn_branch__history_create_empty(apr_pool_t *result_pool)
+{
+ svn_branch__history_t *history
+ = svn_branch__history_create(NULL, result_pool);
+
+ return history;
+}
+
+svn_branch__history_t *
+svn_branch__history_create(apr_hash_t *parents,
+ apr_pool_t *result_pool)
+{
+ svn_branch__history_t *history
+ = apr_pcalloc(result_pool, sizeof(*history));
+
+ history->parents = apr_hash_make(result_pool);
+ if (parents)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(result_pool, parents);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *bid = apr_hash_this_key(hi);
+ svn_branch__rev_bid_t *val = apr_hash_this_val(hi);
+
+ svn_hash_sets(history->parents,
+ apr_pstrdup(result_pool, bid),
+ svn_branch__rev_bid_dup(val, result_pool));
+ }
+ }
+ return history;
+}
+
+svn_branch__history_t *
+svn_branch__history_dup(const svn_branch__history_t *old,
+ apr_pool_t *result_pool)
+{
+ svn_branch__history_t *history = NULL;
+
+ if (old)
+ {
+ history
+ = svn_branch__history_create(old->parents, result_pool);
+ }
+ return history;
+}
+
+
+/*
+ * ========================================================================
+ * Branch mappings
+ * ========================================================================
+ */
+
+/* Validate that ELEMENT is suitable for a mapping of BRANCH:EID.
+ * ELEMENT->payload may be null.
+ */
+static void
+branch_validate_element(const svn_branch__state_t *branch,
+ int eid,
+ const svn_element__content_t *element)
+{
+ SVN_ERR_ASSERT_NO_RETURN(element);
+
+ /* Parent EID must be valid and different from this element's EID, or -1
+ iff this is the branch root element. */
+ SVN_ERR_ASSERT_NO_RETURN(
+ IS_BRANCH_ROOT_EID(branch, eid)
+ ? (element->parent_eid == -1)
+ : (element->parent_eid != eid
+ && EID_IS_ALLOCATED(branch, element->parent_eid)));
+
+ /* Element name must be given, and empty iff EID is the branch root. */
+ SVN_ERR_ASSERT_NO_RETURN(
+ element->name
+ && IS_BRANCH_ROOT_EID(branch, eid) == (*element->name == '\0'));
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_element__payload_invariants(element->payload));
+ if (element->payload->is_subbranch_root)
+ {
+ /* a subbranch root element must not be the branch root element */
+ SVN_ERR_ASSERT_NO_RETURN(! IS_BRANCH_ROOT_EID(branch, eid));
+ }
+}
+
+static svn_error_t *
+branch_state_get_elements(const svn_branch__state_t *branch,
+ svn_element__tree_t **element_tree_p,
+ apr_pool_t *result_pool)
+{
+ *element_tree_p = branch->priv->element_tree;
+ return SVN_NO_ERROR;
+}
+
+static svn_element__content_t *
+branch_get_element(const svn_branch__state_t *branch,
+ int eid)
+{
+ svn_element__content_t *element;
+
+ element = svn_element__tree_get(branch->priv->element_tree, eid);
+
+ if (element)
+ branch_validate_element(branch, eid, element);
+ return element;
+}
+
+static svn_error_t *
+branch_state_get_element(const svn_branch__state_t *branch,
+ svn_element__content_t **element_p,
+ int eid,
+ apr_pool_t *result_pool)
+{
+ *element_p = branch_get_element(branch, eid);
+ return SVN_NO_ERROR;
+}
+
+/* In BRANCH, set element EID to ELEMENT.
+ *
+ * If ELEMENT is null, delete element EID.
+ *
+ * Assume ELEMENT is already allocated with sufficient lifetime.
+ */
+static void
+branch_map_set(svn_branch__state_t *branch,
+ int eid,
+ const svn_element__content_t *element)
+{
+ apr_pool_t *map_pool = apr_hash_pool_get(branch->priv->element_tree->e_map);
+
+ SVN_ERR_ASSERT_NO_RETURN(EID_IS_ALLOCATED(branch, eid));
+ if (element)
+ branch_validate_element(branch, eid, element);
+
+ svn_element__tree_set(branch->priv->element_tree, eid, element);
+ branch->priv->is_flat = FALSE;
+ assert_branch_state_invariants(branch, map_pool);
+}
+
+/* An #svn_branch__state_t method. */
+static svn_error_t *
+branch_state_set_element(svn_branch__state_t *branch,
+ svn_branch__eid_t eid,
+ const svn_element__content_t *element,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *map_pool = apr_hash_pool_get(branch->priv->element_tree->e_map);
+
+ /* EID must be a valid element id */
+ SVN_ERR_ASSERT(EID_IS_ALLOCATED(branch, eid));
+
+ if (element)
+ {
+ element = svn_element__content_dup(element, map_pool);
+
+ /* NEW_PAYLOAD must be specified, either in full or by reference */
+ SVN_ERR_ASSERT(element->payload);
+
+ if ((element->parent_eid == -1) != IS_BRANCH_ROOT_EID(branch, eid)
+ || (*element->name == '\0') != IS_BRANCH_ROOT_EID(branch, eid))
+ {
+ return svn_error_createf(SVN_BRANCH__ERR, NULL,
+ _("Cannot set e%d to (parent=e%d, name='%s'): "
+ "branch root is e%d"),
+ eid, element->parent_eid, element->name,
+ branch->priv->element_tree->root_eid);
+ }
+ }
+
+ /* Insert the new version */
+ branch_map_set(branch, eid, element);
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__state_t method. */
+static svn_error_t *
+branch_state_purge(svn_branch__state_t *branch,
+ apr_pool_t *scratch_pool)
+{
+ svn_element__tree_purge_orphans(branch->priv->element_tree->e_map,
+ branch->priv->element_tree->root_eid,
+ scratch_pool);
+ branch->priv->is_flat = TRUE;
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__state_t method. */
+static svn_error_t *
+branch_state_get_history(svn_branch__state_t *branch,
+ svn_branch__history_t **history_p,
+ apr_pool_t *result_pool)
+{
+ if (history_p)
+ {
+ *history_p
+ = svn_branch__history_dup(branch->priv->history, result_pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__state_t method. */
+static svn_error_t *
+branch_state_set_history(svn_branch__state_t *branch,
+ const svn_branch__history_t *history,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *branch_pool = branch_state_pool_get(branch);
+
+ branch->priv->history
+ = svn_branch__history_dup(history, branch_pool);
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_branch__get_path_by_eid(const svn_branch__state_t *branch,
+ int eid,
+ apr_pool_t *result_pool)
+{
+ svn_element__tree_t *elements;
+
+ SVN_ERR_ASSERT_NO_RETURN(EID_IS_ALLOCATED(branch, eid));
+ /*SVN_ERR_ASSERT_NO_RETURN(branch->priv->is_flat);*/
+
+ svn_error_clear(svn_branch__state_get_elements(branch, &elements, result_pool));
+ return svn_element__tree_get_path_by_eid(elements, eid, result_pool);
+}
+
+int
+svn_branch__get_eid_by_path(const svn_branch__state_t *branch,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ svn_element__tree_t *elements;
+ apr_hash_index_t *hi;
+
+ /*SVN_ERR_ASSERT_NO_RETURN(branch->priv->is_flat);*/
+
+ /* ### This is a crude, linear search */
+ svn_error_clear(svn_branch__state_get_elements(branch, &elements, scratch_pool));
+ for (hi = apr_hash_first(scratch_pool, elements->e_map);
+ hi; hi = apr_hash_next(hi))
+ {
+ int eid = svn_eid__hash_this_key(hi);
+ const char *this_path = svn_element__tree_get_path_by_eid(elements, eid,
+ scratch_pool);
+
+ if (! this_path)
+ {
+ /* Mapping is not complete; this element is in effect not present. */
+ continue;
+ }
+ if (strcmp(path, this_path) == 0)
+ {
+ return eid;
+ }
+ }
+
+ return -1;
+}
+
+/* Create a copy of NEW_SUBTREE in TO_BRANCH.
+ *
+ * For each non-root element in NEW_SUBTREE, create a new element with
+ * a new EID, no matter what EID is used to represent it in NEW_SUBTREE.
+ *
+ * For the new subtree root element, if TO_EID is -1, generate a new EID,
+ * otherwise alter (if it exists) or instantiate the element TO_EID.
+ *
+ * Set the new subtree root element's parent to NEW_PARENT_EID and name to
+ * NEW_NAME.
+ */
+static svn_error_t *
+svn_branch__map_add_subtree(svn_branch__state_t *to_branch,
+ int to_eid,
+ svn_branch__eid_t new_parent_eid,
+ const char *new_name,
+ svn_element__tree_t *new_subtree,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ svn_element__content_t *new_root_content;
+
+ /* Get a new EID for the root element, if not given. */
+ if (to_eid == -1)
+ {
+ SVN_ERR(svn_branch__txn_new_eid(to_branch->txn, &to_eid,
+ scratch_pool));
+ }
+
+ /* Create the new subtree root element */
+ new_root_content = svn_element__tree_get(new_subtree, new_subtree->root_eid);
+ new_root_content = svn_element__content_create(new_parent_eid, new_name,
+ new_root_content->payload,
+ scratch_pool);
+ SVN_ERR(branch_state_set_element(to_branch, to_eid, new_root_content,
+ scratch_pool));
+
+ /* Process its immediate children */
+ for (hi = apr_hash_first(scratch_pool, new_subtree->e_map);
+ hi; hi = apr_hash_next(hi))
+ {
+ int this_from_eid = svn_eid__hash_this_key(hi);
+ svn_element__content_t *from_element = apr_hash_this_val(hi);
+
+ if (from_element->parent_eid == new_subtree->root_eid)
+ {
+ svn_element__tree_t *this_subtree;
+
+ /* Recurse. (We don't try to check whether it's a directory node,
+ as we might not have the node kind in the map.) */
+ this_subtree
+ = svn_element__tree_create(new_subtree->e_map, this_from_eid,
+ scratch_pool);
+ SVN_ERR(svn_branch__map_add_subtree(to_branch, -1 /*to_eid*/,
+ to_eid, from_element->name,
+ this_subtree, scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Instantiate elements in a branch.
+ *
+ * In TO_BRANCH, instantiate (or alter, if existing) each element of
+ * ELEMENTS, each with its given tree structure (parent, name) and payload.
+ */
+static svn_error_t *
+branch_instantiate_elements(svn_branch__state_t *to_branch,
+ const svn_element__tree_t *elements,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, elements->e_map);
+ hi; hi = apr_hash_next(hi))
+ {
+ int this_eid = svn_eid__hash_this_key(hi);
+ svn_element__content_t *this_element = apr_hash_this_val(hi);
+
+ branch_map_set(to_branch, this_eid,
+ svn_element__content_dup(
+ this_element,
+ apr_hash_pool_get(to_branch->priv->element_tree->e_map)));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * ========================================================================
+ * Branch State Object
+ * ========================================================================
+ */
+
+svn_error_t *
+svn_branch__state_get_elements(const svn_branch__state_t *branch,
+ svn_element__tree_t **element_tree_p,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(branch->vtable->get_elements(branch,
+ element_tree_p,
+ result_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__state_get_element(const svn_branch__state_t *branch,
+ svn_element__content_t **element_p,
+ int eid,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(branch->vtable->get_element(branch,
+ element_p, eid, result_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__state_set_element(svn_branch__state_t *branch,
+ int eid,
+ const svn_element__content_t *element,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(branch->vtable->set_element(branch,
+ eid, element,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__state_alter_one(svn_branch__state_t *branch,
+ svn_branch__eid_t eid,
+ svn_branch__eid_t new_parent_eid,
+ const char *new_name,
+ const svn_element__payload_t *new_payload,
+ apr_pool_t *scratch_pool)
+{
+ svn_element__content_t *element
+ = svn_element__content_create(new_parent_eid, new_name, new_payload,
+ scratch_pool);
+
+ SVN_ERR(svn_branch__state_set_element(branch, eid, element, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__state_copy_tree(svn_branch__state_t *branch,
+ const svn_branch__rev_bid_eid_t *src_el_rev,
+ svn_branch__eid_t new_parent_eid,
+ const char *new_name,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(branch->vtable->copy_tree(branch,
+ src_el_rev, new_parent_eid, new_name,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__state_delete_one(svn_branch__state_t *branch,
+ svn_branch__eid_t eid,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_branch__state_set_element(branch, eid, NULL, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__state_purge(svn_branch__state_t *branch,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(branch->vtable->purge(branch,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__state_get_history(svn_branch__state_t *branch,
+ svn_branch__history_t **history_p,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(branch->vtable->get_history(branch,
+ history_p,
+ result_pool));
+ SVN_ERR_ASSERT(*history_p);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__state_set_history(svn_branch__state_t *branch,
+ const svn_branch__history_t *history,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(history);
+ SVN_ERR(branch->vtable->set_history(branch,
+ history,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_branch__state_t *
+svn_branch__state_create(const svn_branch__state_vtable_t *vtable,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool)
+{
+ svn_branch__state_t *b = apr_pcalloc(result_pool, sizeof(*b));
+
+ b->vtable = apr_pmemdup(result_pool, vtable, sizeof(*vtable));
+
+ b->vtable->vpriv.cancel_func = cancel_func;
+ b->vtable->vpriv.cancel_baton = cancel_baton;
+
+#ifdef ENABLE_ORDERING_CHECK
+ b->vtable->vpriv.within_callback = FALSE;
+ b->vtable->vpriv.finished = FALSE;
+ b->vtable->vpriv.state_pool = result_pool;
+#endif
+
+ return b;
+}
+
+/* Create a new branch state object.
+ *
+ * It will have no elements (not even a root element).
+ */
+static svn_branch__state_t *
+branch_state_create(const char *bid,
+ int root_eid,
+ svn_branch__txn_t *txn,
+ apr_pool_t *result_pool)
+{
+ static const svn_branch__state_vtable_t vtable = {
+ {0},
+ branch_state_get_elements,
+ branch_state_get_element,
+ branch_state_set_element,
+ branch_state_copy_one,
+ branch_state_copy_tree,
+ branch_state_purge,
+ branch_state_get_history,
+ branch_state_set_history,
+ };
+ svn_branch__state_t *b
+ = svn_branch__state_create(&vtable, NULL, NULL, result_pool);
+
+ b->priv = apr_pcalloc(result_pool, sizeof(*b->priv));
+ b->bid = apr_pstrdup(result_pool, bid);
+ b->txn = txn;
+ b->priv->element_tree = svn_element__tree_create(NULL, root_eid, result_pool);
+ assert_branch_state_invariants(b, result_pool);
+ b->priv->is_flat = TRUE;
+ b->priv->history = svn_branch__history_create_empty(result_pool);
+ return b;
+}
+
+/*
+ * ========================================================================
+ * Parsing and Serializing
+ * ========================================================================
+ */
+
+svn_string_t *
+svn_branch__get_default_r0_metadata(apr_pool_t *result_pool)
+{
+ static const char *default_repos_info
+ = "r0: eids 0 1 branches 1\n"
+ "B0 root-eid 0 num-eids 1\n"
+ "history: parents 0\n"
+ "e0: normal -1 .\n";
+
+ return svn_string_create(default_repos_info, result_pool);
+}
+
+/* */
+static svn_error_t *
+parse_branch_line(char *bid_p,
+ int *root_eid_p,
+ int *num_eids_p,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *line;
+ svn_boolean_t eof;
+ int n;
+
+ /* Read a line */
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+ SVN_ERR_ASSERT(!eof);
+
+ n = sscanf(line->data, "%s root-eid %d num-eids %d",
+ bid_p, root_eid_p, num_eids_p);
+ SVN_ERR_ASSERT(n == 3);
+
+ return SVN_NO_ERROR;
+}
+
+/* Parse the history metadata for BRANCH.
+ */
+static svn_error_t *
+history_parse(svn_branch__history_t **history_p,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__history_t *history
+ = svn_branch__history_create_empty(result_pool);
+ svn_stringbuf_t *line;
+ svn_boolean_t eof;
+ int n;
+ int num_parents;
+ int i;
+
+ /* Read a line */
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+ SVN_ERR_ASSERT(!eof);
+
+ n = sscanf(line->data, "history: parents %d",
+ &num_parents);
+ SVN_ERR_ASSERT(n == 1);
+
+ for (i = 0; i < num_parents; i++)
+ {
+ svn_revnum_t rev;
+ char bid[100];
+
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+ SVN_ERR_ASSERT(!eof);
+
+ n = sscanf(line->data, "parent: r%ld.%99s",
+ &rev, bid);
+ SVN_ERR_ASSERT(n == 2);
+
+ svn_hash_sets(history->parents,
+ apr_pstrdup(result_pool, bid),
+ svn_branch__rev_bid_create(rev, bid, result_pool));
+ }
+
+ if (history_p)
+ *history_p = history;
+ return SVN_NO_ERROR;
+}
+
+/* Parse the mapping for one element.
+ */
+static svn_error_t *
+parse_element_line(int *eid_p,
+ svn_boolean_t *is_subbranch_p,
+ int *parent_eid_p,
+ const char **name_p,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *line;
+ svn_boolean_t eof;
+ char kind[10];
+ int n;
+ int offset;
+
+ /* Read a line */
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+ SVN_ERR_ASSERT(!eof);
+
+ n = sscanf(line->data, "e%d: %9s %d%n",
+ eid_p,
+ kind, parent_eid_p, &offset);
+ SVN_ERR_ASSERT(n >= 3); /* C std is unclear on whether '%n' counts */
+ SVN_ERR_ASSERT(line->data[offset] == ' ');
+
+ *name_p = apr_pstrdup(result_pool, line->data + offset + 1);
+ *is_subbranch_p = (strcmp(kind, "subbranch") == 0);
+
+ if (strcmp(*name_p, "(null)") == 0)
+ *name_p = NULL;
+ else if (strcmp(*name_p, ".") == 0)
+ *name_p = "";
+
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_branch__id_nest(const char *outer_bid,
+ int outer_eid,
+ apr_pool_t *result_pool)
+{
+ if (!outer_bid)
+ return apr_psprintf(result_pool, "B%d", outer_eid);
+
+ return apr_psprintf(result_pool, "%s.%d", outer_bid, outer_eid);
+}
+
+void
+svn_branch__id_unnest(const char **outer_bid,
+ int *outer_eid,
+ const char *bid,
+ apr_pool_t *result_pool)
+{
+ char *last_dot = strrchr(bid, '.');
+
+ if (last_dot) /* BID looks like "B3.11" or "B3.11.22" etc. */
+ {
+ *outer_bid = apr_pstrndup(result_pool, bid, last_dot - bid);
+ *outer_eid = atoi(last_dot + 1);
+ }
+ else /* looks like "B0" or B22" (with no dot) */
+ {
+ *outer_bid = NULL;
+ *outer_eid = atoi(bid + 1);
+ }
+}
+
+/* Create a new branch *NEW_BRANCH, initialized
+ * with info parsed from STREAM, allocated in RESULT_POOL.
+ */
+static svn_error_t *
+svn_branch__state_parse(svn_branch__state_t **new_branch,
+ svn_branch__txn_t *txn,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char bid[1000];
+ int root_eid, num_eids;
+ svn_branch__state_t *branch_state;
+ int i;
+
+ SVN_ERR(parse_branch_line(bid, &root_eid, &num_eids,
+ stream, scratch_pool, scratch_pool));
+
+ branch_state = branch_state_create(bid, root_eid, txn,
+ result_pool);
+
+ /* Read in the merge history. */
+ SVN_ERR(history_parse(&branch_state->priv->history,
+ stream, result_pool, scratch_pool));
+
+ /* Read in the structure. Set the payload of each normal element to a
+ (branch-relative) reference. */
+ for (i = 0; i < num_eids; i++)
+ {
+ int eid, this_parent_eid;
+ const char *this_name;
+ svn_boolean_t is_subbranch;
+
+ SVN_ERR(parse_element_line(&eid,
+ &is_subbranch, &this_parent_eid, &this_name,
+ stream, scratch_pool, scratch_pool));
+
+ if (this_name)
+ {
+ svn_element__payload_t *payload;
+ svn_element__content_t *element;
+
+ if (! is_subbranch)
+ {
+ payload = svn_element__payload_create_ref(txn->rev, bid, eid,
+ result_pool);
+ }
+ else
+ {
+ payload
+ = svn_element__payload_create_subbranch(result_pool);
+ }
+ element = svn_element__content_create(this_parent_eid,
+ this_name, payload,
+ scratch_pool);
+ SVN_ERR(branch_state_set_element(branch_state, eid, element,
+ scratch_pool));
+ }
+ }
+
+ branch_state->priv->is_flat = TRUE;
+ *new_branch = branch_state;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__txn_parse(svn_branch__txn_t **txn_p,
+ svn_branch__repos_t *repos,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__txn_t *txn;
+ svn_revnum_t rev;
+ int first_eid, next_eid;
+ int num_branches;
+ svn_stringbuf_t *line;
+ svn_boolean_t eof;
+ int n;
+ int j;
+
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+ SVN_ERR_ASSERT(! eof);
+ n = sscanf(line->data, "r%ld: eids %d %d "
+ "branches %d",
+ &rev,
+ &first_eid, &next_eid,
+ &num_branches);
+ SVN_ERR_ASSERT(n == 4);
+
+ txn = branch_txn_create(repos, rev, rev - 1, result_pool);
+ txn->priv->first_eid = first_eid;
+ txn->priv->next_eid = next_eid;
+
+ /* parse the branches */
+ for (j = 0; j < num_branches; j++)
+ {
+ svn_branch__state_t *branch;
+
+ SVN_ERR(svn_branch__state_parse(&branch, txn, stream,
+ result_pool, scratch_pool));
+ APR_ARRAY_PUSH(txn->priv->branches, void *) = branch;
+ }
+
+ *txn_p = txn;
+ return SVN_NO_ERROR;
+}
+
+/* Serialize the history metadata for BRANCH.
+ */
+static svn_error_t *
+history_serialize(svn_stream_t *stream,
+ svn_branch__history_t *history,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *ancestors_sorted;
+ int i;
+
+ /* Write entries in sorted order for stability -- so that for example
+ we can test parse-then-serialize by expecting identical output. */
+ ancestors_sorted = svn_sort__hash(history->parents,
+ svn_sort_compare_items_lexically,
+ scratch_pool);
+ SVN_ERR(svn_stream_printf(stream, scratch_pool,
+ "history: parents %d\n",
+ ancestors_sorted->nelts));
+ for (i = 0; i < ancestors_sorted->nelts; i++)
+ {
+ svn_sort__item_t *item
+ = &APR_ARRAY_IDX(ancestors_sorted, i, svn_sort__item_t);
+ svn_branch__rev_bid_t *rev_bid = item->value;
+
+ SVN_ERR(svn_stream_printf(stream, scratch_pool,
+ "parent: r%ld.%s\n",
+ rev_bid->rev, rev_bid->bid));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Write to STREAM a parseable representation of BRANCH.
+ */
+svn_error_t *
+svn_branch__state_serialize(svn_stream_t *stream,
+ svn_branch__state_t *branch,
+ apr_pool_t *scratch_pool)
+{
+ svn_eid__hash_iter_t *ei;
+
+ SVN_ERR_ASSERT(branch->priv->is_flat);
+
+ SVN_ERR(svn_stream_printf(stream, scratch_pool,
+ "%s root-eid %d num-eids %d\n",
+ svn_branch__get_id(branch, scratch_pool),
+ branch->priv->element_tree->root_eid,
+ apr_hash_count(branch->priv->element_tree->e_map)));
+
+ SVN_ERR(history_serialize(stream, branch->priv->history,
+ scratch_pool));
+
+ for (SVN_EID__HASH_ITER_SORTED_BY_EID(ei, branch->priv->element_tree->e_map,
+ scratch_pool))
+ {
+ int eid = ei->eid;
+ svn_element__content_t *element = branch_get_element(branch, eid);
+ int parent_eid;
+ const char *name;
+
+ SVN_ERR_ASSERT(element);
+ parent_eid = element->parent_eid;
+ name = element->name[0] ? element->name : ".";
+ SVN_ERR(svn_stream_printf(stream, scratch_pool,
+ "e%d: %s %d %s\n",
+ eid,
+ element ? ((! element->payload->is_subbranch_root)
+ ? "normal" : "subbranch")
+ : "none",
+ parent_eid, name));
+ }
+ return SVN_NO_ERROR;
+}
+
+/*
+ * ========================================================================
+ */
+