aboutsummaryrefslogtreecommitdiff
path: root/subversion/libsvn_delta
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
parenta55fb3c0d5eca7d887798125d5b95942b1f01d4b (diff)
Import Subversion-1.10.0vendor/subversion/subversion-1.10.0
Notes
Notes: svn path=/vendor/subversion/dist/; revision=333347 svn path=/vendor/subversion/subversion-1.10.0/; revision=333348; tag=vendor/subversion/subversion-1.10.0
Diffstat (limited to 'subversion/libsvn_delta')
-rw-r--r--subversion/libsvn_delta/branch.c1699
-rw-r--r--subversion/libsvn_delta/branch_compat.c2070
-rw-r--r--subversion/libsvn_delta/branch_migrate.c366
-rw-r--r--subversion/libsvn_delta/branch_nested.c660
-rw-r--r--subversion/libsvn_delta/branch_repos.c132
-rw-r--r--subversion/libsvn_delta/cancel.c21
-rw-r--r--subversion/libsvn_delta/compat.c30
-rw-r--r--subversion/libsvn_delta/debug_editor.c2
-rw-r--r--subversion/libsvn_delta/debug_editor.h54
-rw-r--r--subversion/libsvn_delta/default_editor.c30
-rw-r--r--subversion/libsvn_delta/element.c471
-rw-r--r--subversion/libsvn_delta/svndiff.c527
-rw-r--r--subversion/libsvn_delta/text_delta.c33
-rw-r--r--subversion/libsvn_delta/xdelta.c2
14 files changed, 5833 insertions, 264 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;
+}
+
+/*
+ * ========================================================================
+ */
+
diff --git a/subversion/libsvn_delta/branch_compat.c b/subversion/libsvn_delta/branch_compat.c
new file mode 100644
index 000000000000..dac0e191cf24
--- /dev/null
+++ b/subversion/libsvn_delta/branch_compat.c
@@ -0,0 +1,2070 @@
+/*
+ * branch_compat.c : Branching compatibility layer.
+ *
+ * ====================================================================
+ * 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 "svn_types.h"
+#include "svn_error.h"
+#include "svn_delta.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_iter.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+
+#include "private/svn_branch_impl.h"
+#include "private/svn_branch_repos.h"
+#include "private/svn_branch_nested.h"
+#include "private/svn_delta_private.h"
+#include "private/svn_branch_compat.h"
+
+#include "svn_private_config.h"
+
+
+/* Verify EXPR is true; raise an error if not. */
+#define VERIFY(expr) SVN_ERR_ASSERT(expr)
+
+
+/*
+ * ===================================================================
+ * Minor data types
+ * ===================================================================
+ */
+
+/** A location in a committed revision.
+ *
+ * @a rev shall not be #SVN_INVALID_REVNUM unless the interface using this
+ * type specifically allows it and defines its meaning. */
+typedef struct svn_pathrev_t
+{
+ svn_revnum_t rev;
+ const char *relpath;
+} svn_pathrev_t;
+
+/* Return true iff PEG_PATH1 and PEG_PATH2 are both the same location.
+ */
+static svn_boolean_t
+pathrev_equal(const svn_pathrev_t *p1,
+ const svn_pathrev_t *p2)
+{
+ if (p1->rev != p2->rev)
+ return FALSE;
+ if (strcmp(p1->relpath, p2->relpath) != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+#if 0
+/* Return a human-readable string representation of LOC. */
+static const char *
+pathrev_str(const svn_pathrev_t *loc,
+ apr_pool_t *result_pool)
+{
+ if (! loc)
+ return "<nil>";
+ return apr_psprintf(result_pool, "%s@%ld",
+ loc->relpath, loc->rev);
+}
+
+/* Return a string representation of the (string) keys of HASH. */
+static const char *
+hash_keys_str(apr_hash_t *hash)
+{
+ const char *str = NULL;
+ apr_pool_t *pool;
+ apr_hash_index_t *hi;
+
+ if (! hash)
+ return "<nil>";
+
+ pool = apr_hash_pool_get(hash);
+ for (hi = apr_hash_first(pool, hash); hi; hi = apr_hash_next(hi))
+ {
+ const char *key = apr_hash_this_key(hi);
+
+ if (!str)
+ str = key;
+ else
+ str = apr_psprintf(pool, "%s, %s", str, key);
+ }
+ return apr_psprintf(pool, "{%s}", str);
+}
+#endif
+
+/**
+ * Merge two hash tables into one new hash table. The values of the overlay
+ * hash override the values of the base if both have the same key.
+ *
+ * Unlike apr_hash_overlay(), this doesn't care whether the input hashes use
+ * the same hash function, nor about the relationship between the three pools.
+ *
+ * @param p The pool to use for the new hash table
+ * @param overlay The table to add to the initial table
+ * @param base The table that represents the initial values of the new table
+ * @return A new hash table containing all of the data from the two passed in
+ * @remark Makes a shallow copy: keys and values are not copied
+ */
+static apr_hash_t *
+hash_overlay(apr_hash_t *overlay,
+ apr_hash_t *base)
+{
+ apr_pool_t *pool = apr_hash_pool_get(base);
+ apr_hash_t *result = apr_hash_copy(pool, base);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, overlay); hi; hi = apr_hash_next(hi))
+ {
+ svn_hash_sets(result, apr_hash_this_key(hi), apr_hash_this_val(hi));
+ }
+ return result;
+}
+
+
+/*
+ * ========================================================================
+ * Configuration Options
+ * ========================================================================
+ */
+
+/* Features that are not wanted for this commit editor shim but may be
+ * wanted in a similar but different shim such as for an update editor. */
+/* #define SHIM_WITH_ADD_ABSENT */
+/* #define SHIM_WITH_UNLOCK */
+
+/* Whether to support switching from relative to absolute paths in the
+ * Ev1 methods. */
+/* #define SHIM_WITH_ABS_PATHS */
+
+
+/*
+ * ========================================================================
+ * Shim Connector
+ * ========================================================================
+ *
+ * The shim connector enables a more exact round-trip conversion from an
+ * Ev1 drive to Ev3 and back to Ev1.
+ */
+struct svn_branch__compat_shim_connector_t
+{
+ /* Set to true if and when an Ev1 receiving shim receives an absolute
+ * path (prefixed with '/') from the delta edit, and causes the Ev1
+ * sending shim to send absolute paths.
+ * ### NOT IMPLEMENTED
+ */
+#ifdef SHIM_WITH_ABS_PATHS
+ svn_boolean_t *ev1_absolute_paths;
+#endif
+
+ /* The Ev1 set_target_revision and start_edit methods, respectively, will
+ * call the TARGET_REVISION_FUNC and START_EDIT_FUNC callbacks, if non-null.
+ * Otherwise, default calls will be used.
+ *
+ * (Possibly more useful for update editors than for commit editors?) */
+ svn_branch__compat_set_target_revision_func_t target_revision_func;
+
+ /* If not null, a callback that the Ev3 driver may call to
+ * provide the "base revision" of the root directory, even if it is not
+ * going to modify that directory. (If it does modify it, then it will
+ * pass in the appropriate base revision at that time.) If null
+ * or if the driver does not call it, then the Ev1
+ * open_root() method will be called with SVN_INVALID_REVNUM as the base
+ * revision parameter.
+ */
+ svn_delta__start_edit_func_t start_edit_func;
+
+#ifdef SHIM_WITH_UNLOCK
+ /* A callback which will be called when an unlocking action is received.
+ (For update editors?) */
+ svn_delta__unlock_func_t unlock_func;
+#endif
+
+ void *baton;
+};
+
+svn_error_t *
+svn_branch__compat_insert_shims(
+ const svn_delta_editor_t **new_deditor,
+ void **new_dedit_baton,
+ const svn_delta_editor_t *old_deditor,
+ void *old_dedit_baton,
+ const char *repos_root,
+ const char *base_relpath,
+ svn_branch__compat_fetch_func_t fetch_func,
+ void *fetch_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+#if 0
+ svn_branch__txn_t *edit_txn;
+ svn_branch__compat_shim_connector_t *shim_connector;
+
+#ifdef SVN_DEBUG
+ /*SVN_ERR(svn_delta__get_debug_editor(&old_deditor, &old_dedit_baton,
+ old_deditor, old_dedit_baton,
+ "[OUT] ", result_pool));*/
+#endif
+ SVN_ERR(svn_branch__compat_txn_from_delta_for_commit(
+ &edit_txn,
+ &shim_connector,
+ old_deditor, old_dedit_baton,
+ branching_txn,
+ repos_root,
+ fetch_func, fetch_baton,
+ NULL, NULL /*cancel*/,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_branch__compat_delta_from_txn_for_commit(
+ new_deditor, new_dedit_baton,
+ edit_txn,
+ repos_root, base_relpath,
+ fetch_func, fetch_baton,
+ shim_connector,
+ result_pool, scratch_pool));
+#ifdef SVN_DEBUG
+ /*SVN_ERR(svn_delta__get_debug_editor(new_deditor, new_dedit_baton,
+ *new_deditor, *new_dedit_baton,
+ "[IN] ", result_pool));*/
+#endif
+#else
+ *new_deditor = old_deditor;
+ *new_dedit_baton = old_dedit_baton;
+#endif
+ return SVN_NO_ERROR;
+}
+
+
+/*
+ * ========================================================================
+ * Buffering the Delta Editor Changes
+ * ========================================================================
+ */
+
+/* The kind of Ev1 restructuring operation on a particular path. For each
+ * visited path we use exactly one restructuring action. */
+enum restructure_action_t
+{
+ RESTRUCTURE_NONE = 0,
+ RESTRUCTURE_ADD, /* add the node, maybe replacing. maybe copy */
+#ifdef SHIM_WITH_ADD_ABSENT
+ RESTRUCTURE_ADD_ABSENT, /* add an absent node, possibly replacing */
+#endif
+ RESTRUCTURE_DELETE /* delete this node */
+};
+
+/* Records everything about how this node is to be changed, from an Ev1
+ * point of view. */
+typedef struct change_node_t
+{
+ /* what kind of (tree) restructure is occurring at this node? */
+ enum restructure_action_t action;
+
+ svn_node_kind_t kind; /* the NEW kind of this node */
+
+ /* We may need to specify the revision we are altering or the revision
+ to delete or replace. These are mutually exclusive, but are separate
+ for clarity. */
+ /* CHANGING_REV is the base revision of the change if ACTION is 'none',
+ else is SVN_INVALID_REVNUM. (If ACTION is 'add' and COPYFROM_PATH
+ is non-null, then COPYFROM_REV serves the equivalent purpose for the
+ copied node.) */
+ /* ### Can also be SVN_INVALID_REVNUM for a pre-existing file/dir,
+ meaning the base is the youngest revision. This is probably not
+ a good idea -- it is at least confusing -- and we should instead
+ resolve to a real revnum when Ev1 passes in SVN_INVALID_REVNUM
+ in such cases. */
+ svn_revnum_t changing_rev;
+ /* If ACTION is 'delete' or if ACTION is 'add' and it is a replacement,
+ DELETING is TRUE and DELETING_REV is the revision to delete. */
+ /* ### Can also be SVN_INVALID_REVNUM for a pre-existing file/dir,
+ meaning the base is the youngest revision. This is probably not
+ a good idea -- it is at least confusing -- and we should instead
+ resolve to a real revnum when Ev1 passes in SVN_INVALID_REVNUM
+ in such cases. */
+ svn_boolean_t deleting;
+ svn_revnum_t deleting_rev;
+
+ /* new/final set of props to apply; null => no *change*, not no props */
+ apr_hash_t *props;
+
+ /* new fulltext; null => no change */
+ svn_boolean_t contents_changed;
+ svn_stringbuf_t *contents_text;
+
+ /* If COPYFROM_PATH is not NULL, then copy PATH@REV to this node.
+ RESTRUCTURE must be RESTRUCTURE_ADD. */
+ const char *copyfrom_path;
+ svn_revnum_t copyfrom_rev;
+
+#ifdef SHIM_WITH_UNLOCK
+ /* Record whether an incoming propchange unlocked this node. */
+ svn_boolean_t unlock;
+#endif
+} change_node_t;
+
+#if 0
+/* Return a string representation of CHANGE. */
+static const char *
+change_node_str(change_node_t *change,
+ apr_pool_t *result_pool)
+{
+ const char *copyfrom = "<nil>";
+ const char *str;
+
+ if (change->copyfrom_path)
+ copyfrom = apr_psprintf(result_pool, "'%s'@%ld",
+ change->copyfrom_path, change->copyfrom_rev);
+ str = apr_psprintf(result_pool,
+ "action=%d, kind=%s, changing_rev=%ld, "
+ "deleting=%d, deleting_rev=%ld, ..., "
+ "copyfrom=%s",
+ change->action,
+ svn_node_kind_to_word(change->kind),
+ change->changing_rev,
+ change->deleting, change->deleting_rev,
+ copyfrom);
+ return str;
+}
+#endif
+
+/* Check whether RELPATH is known to exist, known to not exist, or unknown. */
+static svn_tristate_t
+check_existence(apr_hash_t *changes,
+ const char *relpath)
+{
+ apr_pool_t *changes_pool = apr_hash_pool_get(changes);
+ apr_pool_t *scratch_pool = changes_pool;
+ change_node_t *change = svn_hash_gets(changes, relpath);
+ svn_tristate_t exists = svn_tristate_unknown;
+
+ if (change && change->action != RESTRUCTURE_DELETE)
+ exists = svn_tristate_true;
+ else if (change && change->action == RESTRUCTURE_DELETE)
+ exists = svn_tristate_false;
+ else
+ {
+ const char *parent_path = relpath;
+
+ /* Find the nearest parent change. If that's a delete or a simple
+ (non-recursive) add, this path cannot exist, else we don't know. */
+ while ((parent_path = svn_relpath_dirname(parent_path, scratch_pool)),
+ *parent_path)
+ {
+ change = svn_hash_gets(changes, parent_path);
+ if (change)
+ {
+ if ((change->action == RESTRUCTURE_ADD && !change->copyfrom_path)
+ || change->action == RESTRUCTURE_DELETE)
+ exists = svn_tristate_false;
+ break;
+ }
+ }
+ }
+
+ return exists;
+}
+
+/* Insert a new Ev1-style change for RELPATH, or return an existing one.
+ *
+ * Verify Ev3 rules. Primary differences from Ev1 rules are ...
+ *
+ * If ACTION is 'delete', elide any previous explicit deletes inside
+ * that subtree. (Other changes inside that subtree are not allowed.) We
+ * do not store multiple change records per path even with nested moves
+ * -- the most complex change is delete + copy which all fits in one
+ * record with action='add'.
+ */
+static svn_error_t *
+insert_change(change_node_t **change_p, apr_hash_t *changes,
+ const char *relpath,
+ enum restructure_action_t action)
+{
+ apr_pool_t *changes_pool = apr_hash_pool_get(changes);
+ change_node_t *change = svn_hash_gets(changes, relpath);
+
+ /* Check whether this op is allowed. */
+ switch (action)
+ {
+ case RESTRUCTURE_NONE:
+ if (change)
+ {
+ /* A no-restructure change is allowed after add, but not allowed
+ * (in Ev3) after another no-restructure change, nor a delete. */
+ VERIFY(change->action == RESTRUCTURE_ADD);
+ }
+ break;
+
+ case RESTRUCTURE_ADD:
+ if (change)
+ {
+ /* Add or copy is allowed after delete (and replaces the delete),
+ * but not allowed after an add or a no-restructure change. */
+ VERIFY(change->action == RESTRUCTURE_DELETE);
+ change->action = action;
+ }
+ break;
+
+#ifdef SHIM_WITH_ADD_ABSENT
+ case RESTRUCTURE_ADD_ABSENT:
+ /* ### */
+ break;
+#endif
+
+ case RESTRUCTURE_DELETE:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ if (change)
+ {
+ if (action != RESTRUCTURE_NONE)
+ {
+ change->action = action;
+ }
+ }
+ else
+ {
+ /* Create a new change. Callers will set the other fields as needed. */
+ change = apr_pcalloc(changes_pool, sizeof(*change));
+ change->action = action;
+ change->changing_rev = SVN_INVALID_REVNUM;
+
+ svn_hash_sets(changes, apr_pstrdup(changes_pool, relpath), change);
+ }
+
+ *change_p = change;
+ return SVN_NO_ERROR;
+}
+
+/* Modify CHANGES so as to delete the subtree at RELPATH.
+ *
+ * Insert a new Ev1-style change record for RELPATH (or perhaps remove
+ * the existing record if this would have the same effect), and remove
+ * any change records for sub-paths under RELPATH.
+ *
+ * Follow Ev3 rules, although without knowing whether this delete is
+ * part of a move. Ev3 (incremental) "rm" operation says each node to
+ * be removed "MAY be a child of a copy but otherwise SHOULD NOT have
+ * been created or modified in this edit". "mv" operation says ...
+ */
+static svn_error_t *
+delete_subtree(apr_hash_t *changes,
+ const char *relpath,
+ svn_revnum_t deleting_rev)
+{
+ apr_pool_t *changes_pool = apr_hash_pool_get(changes);
+ apr_pool_t *scratch_pool = changes_pool;
+ change_node_t *change = svn_hash_gets(changes, relpath);
+
+ if (change)
+ {
+ /* If this previous change was a non-replacing addition, there
+ is no longer any change to be made at this path. If it was
+ a replacement or a modification, it now becomes a delete.
+ (If it was a delete, this attempt to delete is an error.) */
+ VERIFY(change->action != RESTRUCTURE_DELETE);
+ if (change->action == RESTRUCTURE_ADD && !change->deleting)
+ {
+ svn_hash_sets(changes, relpath, NULL);
+ change = NULL;
+ }
+ else
+ {
+ change->action = RESTRUCTURE_DELETE;
+ }
+ }
+ else
+ {
+ /* There was no change recorded at this path. Record a delete. */
+ change = apr_pcalloc(changes_pool, sizeof(*change));
+ change->action = RESTRUCTURE_DELETE;
+ change->changing_rev = SVN_INVALID_REVNUM;
+ change->deleting = TRUE;
+ change->deleting_rev = deleting_rev;
+
+ svn_hash_sets(changes, apr_pstrdup(changes_pool, relpath), change);
+ }
+
+ /* Elide all child ops. */
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, changes);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *this_relpath = apr_hash_this_key(hi);
+ const char *r = svn_relpath_skip_ancestor(relpath, this_relpath);
+
+ if (r && r[0])
+ {
+ svn_hash_sets(changes, this_relpath, NULL);
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/*
+ * ===================================================================
+ * Commit Editor converter to join a v1 driver to a v3 consumer
+ * ===================================================================
+ *
+ * ...
+ */
+
+svn_error_t *
+svn_branch__compat_delta_from_txn_for_commit(
+ const svn_delta_editor_t **deditor,
+ void **dedit_baton,
+ svn_branch__txn_t *edit_txn,
+ const char *repos_root_url,
+ const char *base_relpath,
+ svn_branch__compat_fetch_func_t fetch_func,
+ void *fetch_baton,
+ const svn_branch__compat_shim_connector_t *shim_connector,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* ### ... */
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__compat_delta_from_txn_for_update(
+ const svn_delta_editor_t **deditor,
+ void **dedit_baton,
+ svn_branch__compat_update_editor3_t *update_editor,
+ const char *repos_root_url,
+ const char *base_repos_relpath,
+ svn_branch__compat_fetch_func_t fetch_func,
+ void *fetch_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__compat_shim_connector_t *shim_connector
+ = apr_pcalloc(result_pool, sizeof(*shim_connector));
+
+ shim_connector->target_revision_func = update_editor->set_target_revision_func;
+ shim_connector->baton = update_editor->set_target_revision_baton;
+#ifdef SHIM_WITH_ABS_PATHS
+ shim_connector->ev1_absolute_paths /*...*/;
+#endif
+
+ SVN_ERR(svn_branch__compat_delta_from_txn_for_commit(
+ deditor, dedit_baton,
+ update_editor->edit_txn,
+ repos_root_url, base_repos_relpath,
+ fetch_func, fetch_baton,
+ shim_connector,
+ result_pool, scratch_pool));
+ /*SVN_ERR(svn_delta__get_debug_editor(deditor, dedit_baton,
+ *deditor, *dedit_baton,
+ "[UP>1] ", result_pool));*/
+
+ return SVN_NO_ERROR;
+}
+
+
+/*
+ * ===================================================================
+ * Commit Editor converter to join a v3 driver to a v1 consumer
+ * ===================================================================
+ *
+ * This editor buffers all the changes before driving the Ev1 at the end,
+ * since it needs to do a single depth-first traversal of the path space
+ * and this cannot be started until all moves are known.
+ *
+ * Moves are converted to copy-and-delete, with the copy being from
+ * the source peg rev. (### Should it request copy-from revision "-1"?)
+ *
+ * It works like this:
+ *
+ * +------+--------+
+ * | path | change |
+ * Ev3 --> +------+--------+ --> Ev1
+ * | ... | ... |
+ * | ... | ... |
+ *
+ * 1. Ev3 changes are accumulated in a per-path table, EB->changes.
+ *
+ * 2. On Ev3 close-edit, walk through the table in a depth-first order,
+ * sending the equivalent Ev1 action for each change.
+ *
+ * TODO
+ *
+ * ### For changes inside a copied subtree, the calls to the "open dir"
+ * and "open file" Ev1 methods may be passing the wrong revision
+ * number: see comment in apply_change().
+ *
+ * ### Have we got our rel-paths in order? Ev1, Ev3 and callbacks may
+ * all expect different paths. Are they relative to repos root or to
+ * some base path? Leading slash (unimplemented 'send_abs_paths'
+ * feature), etc.
+ *
+ * ### May be tidier for OPEN_ROOT_FUNC callback (see open_root_ev3())
+ * not to actually open the root in advance, but instead just to
+ * remember the base revision that the driver wants us to specify
+ * when we do open it.
+ */
+
+
+
+/*
+ * ========================================================================
+ * Driving the Delta Editor
+ * ========================================================================
+ */
+
+/* Information needed for driving the delta editor. */
+struct svn_branch__txn_priv_t
+{
+ /* The Ev1 "delta editor" */
+ const svn_delta_editor_t *deditor;
+ void *dedit_baton;
+
+ /* Callbacks */
+ svn_branch__compat_fetch_func_t fetch_func;
+ void *fetch_baton;
+
+ /* The Ev1 root directory baton if we have opened the root, else null. */
+ void *ev1_root_dir_baton;
+
+#ifdef SHIM_WITH_ABS_PATHS
+ const svn_boolean_t *make_abs_paths;
+#endif
+
+ /* Repository root URL
+ ### Some code allows this to be null -- but is that valid? */
+ const char *repos_root_url;
+
+ /* Ev1 changes recorded so far: REPOS_RELPATH -> change_node_ev3_t */
+ apr_hash_t *changes;
+
+ /* The branching state on which the per-element API is working */
+ svn_branch__txn_t *txn;
+
+ apr_pool_t *edit_pool;
+};
+
+/* Get all the (Ev1) paths that have changes.
+ */
+static const apr_array_header_t *
+get_unsorted_paths(apr_hash_t *changes,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *paths = apr_array_make(scratch_pool, 0, sizeof(void *));
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, changes); hi; hi = apr_hash_next(hi))
+ {
+ const char *this_path = apr_hash_this_key(hi);
+ APR_ARRAY_PUSH(paths, const char *) = this_path;
+ }
+
+ return paths;
+}
+
+#if 0 /* needed only for shim connector */
+/* */
+static svn_error_t *
+set_target_revision_ev3(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__txn_priv_t *eb = edit_baton;
+
+ SVN_ERR(eb->deditor->set_target_revision(eb->dedit_baton, target_revision,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+#endif
+
+/* */
+static svn_error_t *
+open_root_ev3(void *baton,
+ svn_revnum_t base_revision)
+{
+ svn_branch__txn_priv_t *eb = baton;
+
+ SVN_ERR(eb->deditor->open_root(eb->dedit_baton, base_revision,
+ eb->edit_pool, &eb->ev1_root_dir_baton));
+ return SVN_NO_ERROR;
+}
+
+/* If RELPATH is a child of a copy, return the path of the copy root,
+ * else return NULL.
+ */
+static const char *
+find_enclosing_copy(apr_hash_t *changes,
+ const char *relpath,
+ apr_pool_t *result_pool)
+{
+ while (*relpath)
+ {
+ const change_node_t *change = svn_hash_gets(changes, relpath);
+
+ if (change)
+ {
+ if (change->copyfrom_path)
+ return relpath;
+ if (change->action != RESTRUCTURE_NONE)
+ return NULL;
+ }
+ relpath = svn_relpath_dirname(relpath, result_pool);
+ }
+
+ return NULL;
+}
+
+/* Set *BASE_PROPS to the 'base' properties, against which any changes
+ * will be described, for the changed path described in CHANGES at
+ * REPOS_RELPATH.
+ *
+ * For a copied path, including a copy child path, fetch from the copy
+ * source path. For a plain add, return an empty set. For a delete,
+ * return NULL.
+ */
+static svn_error_t *
+fetch_base_props(apr_hash_t **base_props,
+ apr_hash_t *changes,
+ const char *repos_relpath,
+ svn_branch__compat_fetch_func_t fetch_func,
+ void *fetch_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const change_node_t *change = svn_hash_gets(changes, repos_relpath);
+ const char *source_path = NULL;
+ svn_revnum_t source_rev;
+
+ if (change->action == RESTRUCTURE_DELETE)
+ {
+ *base_props = NULL;
+ }
+
+ else if (change->action == RESTRUCTURE_ADD && ! change->copyfrom_path)
+ {
+ *base_props = apr_hash_make(result_pool);
+ }
+ else if (change->copyfrom_path)
+ {
+ source_path = change->copyfrom_path;
+ source_rev = change->copyfrom_rev;
+ }
+ else /* RESTRUCTURE_NONE */
+ {
+ /* It's an edit, but possibly to a copy child. Discover if it's a
+ copy child, & find the copy-from source. */
+
+ const char *copy_path
+ = find_enclosing_copy(changes, repos_relpath, scratch_pool);
+
+ if (copy_path)
+ {
+ const change_node_t *enclosing_copy
+ = svn_hash_gets(changes, copy_path);
+ const char *remainder
+ = svn_relpath_skip_ancestor(copy_path, repos_relpath);
+
+ source_path = svn_relpath_join(enclosing_copy->copyfrom_path,
+ remainder, scratch_pool);
+ source_rev = enclosing_copy->copyfrom_rev;
+ }
+ else
+ {
+ /* It's a plain edit (not a copy child path). */
+ source_path = repos_relpath;
+ source_rev = change->changing_rev;
+ }
+ }
+
+ if (source_path)
+ {
+ SVN_ERR(fetch_func(NULL, base_props, NULL, NULL,
+ fetch_baton, source_path, source_rev,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Send property changes to Ev1 for the CHANGE at REPOS_RELPATH.
+ *
+ * Ev1 requires exactly one prop-change call for each prop whose value
+ * has changed. Therefore we *have* to fetch the original props from the
+ * repository, provide them as OLD_PROPS, and calculate the changes.
+ */
+static svn_error_t *
+drive_ev1_props(const char *repos_relpath,
+ const change_node_t *change,
+ apr_hash_t *old_props,
+ const svn_delta_editor_t *deditor,
+ void *node_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *propdiffs;
+ int i;
+
+ SVN_ERR_ASSERT(change->action != RESTRUCTURE_DELETE);
+
+ /* If there are no property changes, then just exit. */
+ if (change->props == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_prop_diffs(&propdiffs, change->props, old_props, scratch_pool));
+
+ /* Apply property changes. These should be changes against the empty set
+ for a new node, or changes against the source node for a copied node. */
+ for (i = 0; i < propdiffs->nelts; i++)
+ {
+ const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
+
+ svn_pool_clear(iterpool);
+
+ if (change->kind == svn_node_dir)
+ SVN_ERR(deditor->change_dir_prop(node_baton,
+ prop->name, prop->value,
+ iterpool));
+ else
+ SVN_ERR(deditor->change_file_prop(node_baton,
+ prop->name, prop->value,
+ iterpool));
+ }
+
+#ifdef SHIM_WITH_UNLOCK
+ /* Handle the funky unlock protocol. Note: only possibly on files. */
+ if (change->unlock)
+ {
+ SVN_ERR_ASSERT(change->kind == svn_node_file);
+ SVN_ERR(deditor->change_file_prop(node_baton,
+ SVN_PROP_ENTRY_LOCK_TOKEN, NULL,
+ iterpool));
+ }
+#endif
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Drive the Ev1 editor with the change recorded in EB->changes for the
+ * path EV1_RELPATH.
+ *
+ * Conforms to svn_delta_path_driver_cb_func_t.
+ */
+static svn_error_t *
+apply_change(void **dir_baton,
+ void *parent_baton,
+ void *callback_baton,
+ const char *ev1_relpath,
+ apr_pool_t *result_pool)
+{
+ apr_pool_t *scratch_pool = result_pool;
+ const svn_branch__txn_priv_t *eb = callback_baton;
+ const change_node_t *change = svn_hash_gets(eb->changes, ev1_relpath);
+ void *file_baton = NULL;
+ apr_hash_t *base_props;
+
+ /* The callback should only be called for paths in CHANGES. */
+ SVN_ERR_ASSERT(change != NULL);
+
+ /* Typically, we are not creating new directory batons. */
+ *dir_baton = NULL;
+
+ SVN_ERR(fetch_base_props(&base_props, eb->changes, ev1_relpath,
+ eb->fetch_func, eb->fetch_baton,
+ scratch_pool, scratch_pool));
+
+ /* Are we editing the root of the tree? */
+ if (parent_baton == NULL)
+ {
+ /* The root dir was already opened. */
+ *dir_baton = eb->ev1_root_dir_baton;
+
+ /* Only property edits are allowed on the root. */
+ SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE);
+ SVN_ERR(drive_ev1_props(ev1_relpath, change, base_props,
+ eb->deditor, *dir_baton, scratch_pool));
+
+ /* No further action possible for the root. */
+ return SVN_NO_ERROR;
+ }
+
+ if (change->action == RESTRUCTURE_DELETE)
+ {
+ SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting_rev,
+ parent_baton, scratch_pool));
+
+ /* No futher action possible for this node. */
+ return SVN_NO_ERROR;
+ }
+
+ /* If we're not deleting this node, then we should know its kind. */
+ SVN_ERR_ASSERT(change->kind != svn_node_unknown);
+
+#ifdef SHIM_WITH_ADD_ABSENT
+ if (change->action == RESTRUCTURE_ADD_ABSENT)
+ {
+ if (change->kind == svn_node_dir)
+ SVN_ERR(eb->deditor->absent_directory(ev1_relpath, parent_baton,
+ scratch_pool));
+ else if (change->kind == svn_node_file)
+ SVN_ERR(eb->deditor->absent_file(ev1_relpath, parent_baton,
+ scratch_pool));
+ else
+ SVN_ERR_MALFUNCTION();
+
+ /* No further action possible for this node. */
+ return SVN_NO_ERROR;
+ }
+#endif
+ /* RESTRUCTURE_NONE or RESTRUCTURE_ADD */
+
+ if (change->action == RESTRUCTURE_ADD)
+ {
+ const char *copyfrom_url = NULL;
+ svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* Do we have an old node to delete first? If so, delete it. */
+ if (change->deleting)
+ SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting_rev,
+ parent_baton, scratch_pool));
+
+ /* If it's a copy, determine the copy source location. */
+ if (change->copyfrom_path)
+ {
+ /* ### What's this about URL vs. fspath? REPOS_ROOT_URL isn't
+ optional, is it, at least in a commit editor? */
+ if (eb->repos_root_url)
+ copyfrom_url = svn_path_url_add_component2(eb->repos_root_url,
+ change->copyfrom_path,
+ scratch_pool);
+ else
+ {
+ copyfrom_url = change->copyfrom_path;
+
+ /* Make this an FS path by prepending "/" */
+ if (copyfrom_url[0] != '/')
+ copyfrom_url = apr_pstrcat(scratch_pool, "/",
+ copyfrom_url, SVN_VA_NULL);
+ }
+
+ copyfrom_rev = change->copyfrom_rev;
+ }
+
+ if (change->kind == svn_node_dir)
+ SVN_ERR(eb->deditor->add_directory(ev1_relpath, parent_baton,
+ copyfrom_url, copyfrom_rev,
+ result_pool, dir_baton));
+ else if (change->kind == svn_node_file)
+ SVN_ERR(eb->deditor->add_file(ev1_relpath, parent_baton,
+ copyfrom_url, copyfrom_rev,
+ result_pool, &file_baton));
+ else
+ SVN_ERR_MALFUNCTION();
+ }
+ else /* RESTRUCTURE_NONE */
+ {
+ /* ### The code that inserts a "plain edit" change record sets
+ 'changing_rev' to the peg rev of the pegged part of the path,
+ even when the full path refers to a child of a copy. Should we
+ instead be using the copy source rev here, in that case? (Like
+ when we fetch the base properties.) */
+
+ if (change->kind == svn_node_dir)
+ SVN_ERR(eb->deditor->open_directory(ev1_relpath, parent_baton,
+ change->changing_rev,
+ result_pool, dir_baton));
+ else if (change->kind == svn_node_file)
+ SVN_ERR(eb->deditor->open_file(ev1_relpath, parent_baton,
+ change->changing_rev,
+ result_pool, &file_baton));
+ else
+ SVN_ERR_MALFUNCTION();
+ }
+
+ /* Apply any properties in CHANGE to the node. */
+ if (change->kind == svn_node_dir)
+ SVN_ERR(drive_ev1_props(ev1_relpath, change, base_props,
+ eb->deditor, *dir_baton, scratch_pool));
+ else
+ SVN_ERR(drive_ev1_props(ev1_relpath, change, base_props,
+ eb->deditor, file_baton, scratch_pool));
+
+ /* Send the text content delta, if new text content is provided. */
+ if (change->contents_text)
+ {
+ svn_stream_t *read_stream;
+ svn_txdelta_window_handler_t handler;
+ void *handler_baton;
+
+ read_stream = svn_stream_from_stringbuf(change->contents_text,
+ scratch_pool);
+ /* ### would be nice to have a BASE_CHECKSUM, but hey: this is the
+ ### shim code... */
+ SVN_ERR(eb->deditor->apply_textdelta(file_baton, NULL, scratch_pool,
+ &handler, &handler_baton));
+ /* ### it would be nice to send a true txdelta here, but whatever. */
+ SVN_ERR(svn_txdelta_send_stream(read_stream, handler, handler_baton,
+ NULL, scratch_pool));
+ SVN_ERR(svn_stream_close(read_stream));
+ }
+
+ if (file_baton)
+ {
+ SVN_ERR(eb->deditor->close_file(file_baton, NULL, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * ========================================================================
+ * Old-repository storage paths for branch elements
+ * ========================================================================
+ *
+ * To support top-level branches, we map each top-level branch to its own
+ * directory in the old repository, with each nested branch in a subdirectory:
+ *
+ * B0 => ^/top0/...
+ * ^/top0/.../trunk/... <= B0.12
+ * B1 => ^/top1/...
+ *
+ * It may be better to put each branch in its own directory:
+ *
+ * B0 => ^/B0/...
+ * B0.12 => ^/B0.12/...
+ * B1 => ^/B1/...
+ *
+ * (A branch root is not necessarily a directory, it could be a file.)
+ */
+
+/* Get the old-repository path for the storage of the root element of BRANCH.
+ *
+ * Currently, this is the same as the nested-branching hierarchical path
+ * (and thus assumes there is only one top-level branch).
+ */
+static const char *
+branch_get_storage_root_rrpath(const svn_branch__state_t *branch,
+ apr_pool_t *result_pool)
+{
+ int top_branch_num = atoi(branch->bid + 1);
+ const char *top_path = apr_psprintf(result_pool, "top%d", top_branch_num);
+ const char *nested_path = svn_branch__get_root_rrpath(branch, result_pool);
+
+ return svn_relpath_join(top_path, nested_path, result_pool);
+}
+
+/* Get the old-repository path for the storage of element EID of BRANCH.
+ *
+ * If the element EID doesn't exist in BRANCH, return NULL.
+ */
+static const char *
+branch_get_storage_rrpath_by_eid(const svn_branch__state_t *branch,
+ int eid,
+ apr_pool_t *result_pool)
+{
+ const char *path = svn_branch__get_path_by_eid(branch, eid, result_pool);
+ const char *rrpath = NULL;
+
+ if (path)
+ {
+ rrpath = svn_relpath_join(branch_get_storage_root_rrpath(branch,
+ result_pool),
+ path, result_pool);
+ }
+ return rrpath;
+}
+
+/* Return, in *STORAGE_PATHREV_P, the storage-rrpath-rev for BRANCH_REF.
+ */
+static svn_error_t *
+storage_pathrev_from_branch_ref(svn_pathrev_t *storage_pathrev_p,
+ svn_element__branch_ref_t branch_ref,
+ svn_branch__repos_t *repos,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__el_rev_id_t *el_rev;
+
+ SVN_ERR(svn_branch__repos_find_el_rev_by_id(&el_rev,
+ repos,
+ branch_ref.rev,
+ branch_ref.branch_id,
+ branch_ref.eid,
+ scratch_pool, scratch_pool));
+
+ storage_pathrev_p->rev = el_rev->rev;
+ storage_pathrev_p->relpath
+ = branch_get_storage_rrpath_by_eid(el_rev->branch, el_rev->eid,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * ========================================================================
+ * Editor for Commit (independent per-element changes; element-id addressing)
+ * ========================================================================
+ */
+
+/* */
+#define PAYLOAD_IS_ONLY_BY_REFERENCE(payload) \
+ ((payload)->kind == svn_node_unknown)
+
+/* Fetch a payload as *PAYLOAD_P from its storage-pathrev PATH_REV.
+ * Fetch names of immediate children of PATH_REV as *CHILDREN_NAMES.
+ * Either of the outputs may be null if not wanted.
+ */
+static svn_error_t *
+payload_fetch(svn_element__payload_t **payload_p,
+ apr_hash_t **children_names,
+ svn_branch__txn_priv_t *eb,
+ const svn_pathrev_t *path_rev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_element__payload_t *payload
+ = apr_pcalloc(result_pool, sizeof (*payload));
+
+ SVN_ERR(eb->fetch_func(&payload->kind,
+ &payload->props,
+ &payload->text,
+ children_names,
+ eb->fetch_baton,
+ path_rev->relpath, path_rev->rev,
+ result_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(svn_element__payload_invariants(payload));
+ SVN_ERR_ASSERT(payload->kind == svn_node_dir
+ || payload->kind == svn_node_file);
+ if (payload_p)
+ *payload_p = payload;
+ return SVN_NO_ERROR;
+}
+
+/* Return the storage-pathrev of PAYLOAD as *STORAGE_PATHREV_P.
+ *
+ * Find the storage-pathrev from PAYLOAD->branch_ref.
+ */
+static svn_error_t *
+payload_get_storage_pathrev(svn_pathrev_t *storage_pathrev_p,
+ svn_element__payload_t *payload,
+ svn_branch__repos_t *repos,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR_ASSERT(payload->branch_ref.branch_id /* && ... */);
+
+ SVN_ERR(storage_pathrev_from_branch_ref(storage_pathrev_p,
+ payload->branch_ref, repos,
+ result_pool, result_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__compat_fetch(svn_element__payload_t **payload_p,
+ svn_branch__txn_t *txn,
+ svn_element__branch_ref_t branch_ref,
+ svn_branch__compat_fetch_func_t fetch_func,
+ void *fetch_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__txn_priv_t eb;
+ svn_pathrev_t storage_pathrev;
+
+ /* Simulate the existence of /top0 in r0. */
+ if (branch_ref.rev == 0 && branch_ref.eid == 0)
+ {
+ *payload_p = svn_element__payload_create_dir(apr_hash_make(result_pool),
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ eb.txn = txn;
+ eb.fetch_func = fetch_func;
+ eb.fetch_baton = fetch_baton;
+
+ SVN_ERR(storage_pathrev_from_branch_ref(&storage_pathrev,
+ branch_ref, txn->repos,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(payload_fetch(payload_p, NULL,
+ &eb, &storage_pathrev, result_pool, scratch_pool));
+ (*payload_p)->branch_ref = branch_ref;
+ return SVN_NO_ERROR;
+}
+
+/* Fill in the actual payload, from its reference, if not already done.
+ */
+static svn_error_t *
+payload_resolve(svn_element__payload_t *payload,
+ svn_branch__txn_priv_t *eb,
+ apr_pool_t *scratch_pool)
+{
+ svn_pathrev_t storage;
+
+ SVN_ERR_ASSERT(svn_element__payload_invariants(payload));
+
+ if (! PAYLOAD_IS_ONLY_BY_REFERENCE(payload))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(payload_get_storage_pathrev(&storage, payload,
+ eb->txn->repos,
+ scratch_pool));
+
+ SVN_ERR(eb->fetch_func(&payload->kind,
+ &payload->props,
+ &payload->text,
+ NULL,
+ eb->fetch_baton,
+ storage.relpath, storage.rev,
+ payload->pool, scratch_pool));
+
+ SVN_ERR_ASSERT(svn_element__payload_invariants(payload));
+ SVN_ERR_ASSERT(! PAYLOAD_IS_ONLY_BY_REFERENCE(payload));
+ return SVN_NO_ERROR;
+}
+
+/* Update *PATHS, a hash of (storage_rrpath -> svn_branch__el_rev_id_t),
+ * creating or filling in entries for all elements in BRANCH.
+ */
+static svn_error_t *
+convert_branch_to_paths(apr_hash_t *paths,
+ svn_branch__state_t *branch,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ svn_element__tree_t *elements;
+
+ /* assert(branch is at a sequence point); */
+
+ SVN_ERR(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);
+ svn_element__content_t *element = apr_hash_this_val(hi);
+ const char *rrpath
+ = branch_get_storage_rrpath_by_eid(branch, eid, result_pool);
+ svn_branch__el_rev_id_t *ba;
+
+ /* A subbranch-root element carries no payload; the corresponding
+ inner branch root element will provide the payload for this path. */
+ if (element->payload->is_subbranch_root)
+ continue;
+
+ /* No other element should exist at this path, given that we avoid
+ storing anything for a subbranch-root element. */
+ SVN_ERR_ASSERT(! svn_hash_gets(paths, rrpath));
+
+ /* Fill in the details. */
+ ba = svn_branch__el_rev_id_create(branch, eid, branch->txn->rev,
+ result_pool);
+ svn_hash_sets(paths, rrpath, ba);
+ /*SVN_DBG(("branch-to-path[%d]: b%s e%d -> %s",
+ i, svn_branch__get_id(branch, scratch_pool), eid, rrpath));*/
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Produce a mapping from paths to element ids, covering all elements in
+ * BRANCH and all its sub-branches, recursively.
+ *
+ * Update *PATHS_UNION, a hash of (storage_rrpath -> svn_branch__el_rev_id_t),
+ * creating or filling in entries for all elements in all branches at and
+ * under BRANCH, recursively.
+ */
+static svn_error_t *
+convert_branch_to_paths_r(apr_hash_t *paths_union,
+ svn_branch__state_t *branch,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *subbranches;
+ int i;
+
+ /*SVN_DBG(("[%d] branch={b%s e%d at '%s'}", idx,
+ svn_branch__get_id(branch, scratch_pool), branch->root_eid,
+ svn_branch__get_root_rrpath(branch, scratch_pool)));*/
+ SVN_ERR(convert_branch_to_paths(paths_union, branch,
+ result_pool, scratch_pool));
+
+ SVN_ERR(svn_branch__get_immediate_subbranches(branch, &subbranches,
+ scratch_pool, scratch_pool));
+ /* Rercurse into sub-branches */
+ for (i = 0; i < subbranches->nelts; i++)
+ {
+ svn_branch__state_t *b = APR_ARRAY_IDX(subbranches, i, void *);
+
+ SVN_ERR(convert_branch_to_paths_r(paths_union, b, result_pool,
+ scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE iff INITIAL_PAYLOAD and FINAL_PAYLOAD are both non-null
+ * and have the same properties.
+ */
+static svn_boolean_t
+props_equal(svn_element__payload_t *initial_payload,
+ svn_element__payload_t *final_payload,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *prop_diffs;
+
+ if (!initial_payload || !final_payload)
+ return FALSE;
+
+ svn_error_clear(svn_prop_diffs(&prop_diffs,
+ initial_payload->props,
+ final_payload->props,
+ scratch_pool));
+ return (prop_diffs->nelts == 0);
+}
+
+/* Return TRUE iff INITIAL_PAYLOAD and FINAL_PAYLOAD are both file payload
+ * and have the same text.
+ */
+static svn_boolean_t
+text_equal(svn_element__payload_t *initial_payload,
+ svn_element__payload_t *final_payload)
+{
+ if (!initial_payload || !final_payload
+ || initial_payload->kind != svn_node_file
+ || final_payload->kind != svn_node_file)
+ {
+ return FALSE;
+ }
+
+ return svn_stringbuf_compare(initial_payload->text,
+ final_payload->text);
+}
+
+/* Return the copy-from location to be used if this is to be a copy;
+ * otherwise return NULL.
+ *
+ * ### Currently this is indicated by payload-by-reference, which is
+ * an inadequate indication.
+ */
+static svn_error_t *
+get_copy_from(svn_pathrev_t *copyfrom_pathrev_p,
+ svn_element__payload_t *final_payload,
+ svn_branch__txn_priv_t *eb,
+ apr_pool_t *result_pool)
+{
+ if (final_payload->branch_ref.branch_id)
+ {
+ SVN_ERR(payload_get_storage_pathrev(copyfrom_pathrev_p, final_payload,
+ eb->txn->repos,
+ result_pool));
+ }
+ else
+ {
+ copyfrom_pathrev_p->relpath = NULL;
+ copyfrom_pathrev_p->rev = SVN_INVALID_REVNUM;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return a hash whose keys are the names of the immediate children of
+ * RRPATH in PATHS.
+ */
+static apr_hash_t *
+get_immediate_children_names(apr_hash_t *paths,
+ const char *parent_rrpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *children = apr_hash_make(result_pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, paths); hi; hi = apr_hash_next(hi))
+ {
+ const char *this_rrpath = apr_hash_this_key(hi);
+
+ if (this_rrpath[0]
+ && strcmp(parent_rrpath, svn_relpath_dirname(this_rrpath,
+ scratch_pool)) == 0)
+ {
+ svn_hash_sets(children,
+ svn_relpath_basename(this_rrpath, result_pool), "");
+ }
+ }
+
+ return children;
+}
+
+/* Generate Ev1 instructions to edit from a current state to a final state
+ * at RRPATH, recursing for child paths of RRPATH.
+ *
+ * The current state at RRPATH might not be the initial state because,
+ * although neither RRPATH nor any sub-paths have been explicitly visited
+ * before, the current state at RRPATH and its sub-paths might be the
+ * result of a copy.
+ *
+ * PRED_LOC is the predecessor location of the node currently at RRPATH in
+ * the Ev1 transaction, or NULL if there is no node currently at RRPATH.
+ * If the node is copied, including a child of a copy, this is its copy-from
+ * location, otherwise this is its location in the txn base revision.
+ * (The current node cannot be a plain added node on entry to this function,
+ * as the function must be called only once for each path and there is no
+ * recursive add operation.) PRED_LOC identifies the node content that the
+ * that the Ev1 edit needs to delete, replace, update or leave unchanged.
+ *
+ * Process a single hierarchy of nested branches, rooted in the top-level
+ * branch TOP_BRANCH_NUM.
+ */
+static svn_error_t *
+drive_changes_r(const char *rrpath,
+ svn_pathrev_t *pred_loc,
+ apr_hash_t *paths_final,
+ const char *top_branch_id,
+ svn_branch__txn_priv_t *eb,
+ apr_pool_t *scratch_pool)
+{
+ /* The el-rev-id of the element that will finally exist at RRPATH. */
+ svn_branch__el_rev_id_t *final_el_rev = svn_hash_gets(paths_final, rrpath);
+ svn_element__payload_t *final_payload;
+ svn_pathrev_t final_copy_from;
+ svn_boolean_t succession;
+
+ /*SVN_DBG(("rrpath '%s' current=%s, final=e%d)",
+ rrpath,
+ pred_loc ? pathrev_str(*pred_loc, scratch_pool) : "<nil>",
+ final_el_rev ? final_el_rev->eid : -1));*/
+
+ SVN_ERR_ASSERT(!pred_loc
+ || (pred_loc->relpath && SVN_IS_VALID_REVNUM(pred_loc->rev)));
+
+ if (final_el_rev)
+ {
+ svn_element__content_t *final_element;
+
+ SVN_ERR(svn_branch__state_get_element(final_el_rev->branch, &final_element,
+ final_el_rev->eid, scratch_pool));
+ /* A non-null FINAL address means an element exists there. */
+ SVN_ERR_ASSERT(final_element);
+
+ final_payload = final_element->payload;
+
+ /* Decide whether the state at this path should be a copy (incl. a
+ copy-child) */
+ SVN_ERR(get_copy_from(&final_copy_from, final_payload, eb, scratch_pool));
+ /* It doesn't make sense to have a non-copy inside a copy */
+ /*SVN_ERR_ASSERT(!(parent_is_copy && !final_copy_from));*/
+ }
+ else
+ {
+ final_payload = NULL;
+ final_copy_from.relpath = NULL;
+ }
+
+ /* Succession means:
+ for a copy (inc. child) -- copy-from same place as natural predecessor
+ otherwise -- it's succession if it's the same element
+ (which also implies the same kind) */
+ if (pred_loc && final_copy_from.relpath)
+ {
+ succession = pathrev_equal(pred_loc, &final_copy_from);
+ }
+ else if (pred_loc && final_el_rev)
+ {
+ svn_branch__el_rev_id_t *pred_el_rev;
+
+ SVN_ERR(svn_branch__repos_find_el_rev_by_path_rev(&pred_el_rev,
+ eb->txn->repos,
+ pred_loc->rev,
+ top_branch_id,
+ pred_loc->relpath,
+ scratch_pool, scratch_pool));
+
+ succession = (pred_el_rev->eid == final_el_rev->eid);
+ }
+ else
+ {
+ succession = FALSE;
+ }
+
+ /* If there's an initial node that isn't also going to be the final
+ node at this path, then it's being deleted or replaced: delete it. */
+ if (pred_loc && !succession)
+ {
+ /* Issue an Ev1 delete unless this path is inside a path at which
+ we've already issued a delete. */
+ if (check_existence(eb->changes, rrpath) != svn_tristate_false)
+ {
+ /*SVN_DBG(("ev1:del(%s)", rrpath));*/
+ /* ### We don't need "delete_subtree", we only need to insert a
+ single delete operation, as we know we haven't
+ inserted any changes inside this subtree. */
+ SVN_ERR(delete_subtree(eb->changes, rrpath, pred_loc->rev));
+ }
+ else
+ {
+ /*SVN_DBG(("ev1:del(%s): parent is already deleted", rrpath))*/
+ }
+ }
+
+ /* If there's a final node, it's being added or modified.
+ Or it's unchanged -- we do nothing in that case. */
+ if (final_el_rev)
+ {
+ svn_element__payload_t *current_payload = NULL;
+ apr_hash_t *current_children = NULL;
+ change_node_t *change = NULL;
+
+ /* Get the full payload of the final node. If we have
+ only a reference to the payload, fetch it in full. */
+ SVN_ERR_ASSERT(final_payload);
+ SVN_ERR(payload_resolve(final_payload, eb, scratch_pool));
+
+ /* If the final node was also the initial node, it's being
+ modified, otherwise it's being added (perhaps a replacement). */
+ if (succession)
+ {
+ /* Get full payload of the current node */
+ SVN_ERR(payload_fetch(&current_payload, &current_children,
+ eb, pred_loc,
+ scratch_pool, scratch_pool));
+
+ /* If no changes to make, then skip this path */
+ if (svn_element__payload_equal(current_payload,
+ final_payload, scratch_pool))
+ {
+ /*SVN_DBG(("ev1:no-op(%s)", rrpath));*/
+ }
+ else
+ {
+ /*SVN_DBG(("ev1:mod(%s)", rrpath));*/
+ SVN_ERR(insert_change(&change, eb->changes, rrpath,
+ RESTRUCTURE_NONE));
+ change->changing_rev = pred_loc->rev;
+ }
+ }
+ else /* add or copy/move */
+ {
+ /*SVN_DBG(("ev1:add(%s)", rrpath));*/
+ SVN_ERR(insert_change(&change, eb->changes, rrpath,
+ RESTRUCTURE_ADD));
+
+ /* If the node is to be copied (and possibly modified) ... */
+ if (final_copy_from.relpath)
+ {
+ change->copyfrom_rev = final_copy_from.rev;
+ change->copyfrom_path = final_copy_from.relpath;
+
+ /* Get full payload of the copy source node */
+ SVN_ERR(payload_fetch(&current_payload, &current_children,
+ eb, &final_copy_from,
+ scratch_pool, scratch_pool));
+ }
+ }
+
+ if (change)
+ {
+ /* Copy the required content into the change record. Avoid no-op
+ changes of props / text, not least to minimize clutter when
+ debugging Ev1 operations. */
+ SVN_ERR_ASSERT(final_payload->kind == svn_node_dir
+ || final_payload->kind == svn_node_file);
+ change->kind = final_payload->kind;
+ if (!props_equal(current_payload, final_payload, scratch_pool))
+ {
+ change->props = final_payload->props;
+ }
+ if (final_payload->kind == svn_node_file
+ && !text_equal(current_payload, final_payload))
+ {
+ change->contents_text = final_payload->text;
+ }
+ }
+
+ /* Recurse to process this directory's children */
+ if (final_payload->kind == svn_node_dir)
+ {
+ apr_hash_t *final_children;
+ apr_hash_t *union_children;
+ apr_hash_index_t *hi;
+
+ final_children = get_immediate_children_names(paths_final, rrpath,
+ scratch_pool,
+ scratch_pool);
+ union_children = (current_children
+ ? hash_overlay(current_children, final_children)
+ : final_children);
+ for (hi = apr_hash_first(scratch_pool, union_children);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *name = apr_hash_this_key(hi);
+ const char *this_rrpath = svn_relpath_join(rrpath, name,
+ scratch_pool);
+ svn_boolean_t child_in_current
+ = current_children && svn_hash_gets(current_children, name);
+ svn_pathrev_t *child_pred = NULL;
+
+ if (child_in_current)
+ {
+ /* If the parent dir is copied, then this child has been
+ copied along with it: predecessor is parent's copy-from
+ location extended by the child's name. */
+ child_pred = apr_palloc(scratch_pool, sizeof(*child_pred));
+ if (final_copy_from.relpath)
+ {
+ child_pred->rev = final_copy_from.rev;
+ child_pred->relpath
+ = svn_relpath_join(final_copy_from.relpath, name,
+ scratch_pool);
+ }
+ else
+ {
+ child_pred->rev = pred_loc->rev;
+ child_pred->relpath = this_rrpath;
+ }
+ }
+
+ SVN_ERR(drive_changes_r(this_rrpath,
+ child_pred,
+ paths_final, top_branch_id,
+ eb, scratch_pool));
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Drive svn_delta_editor_t (actions: add/copy/delete/modify) from
+ * a before-and-after element mapping.
+ */
+static svn_error_t *
+drive_changes(svn_branch__txn_priv_t *eb,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *branches;
+ int i;
+ const apr_array_header_t *paths;
+
+ /* Convert the element mappings to an svn_delta_editor_t traversal.
+
+ 1. find union of paths in initial and final states, across all branches.
+ 2. traverse paths in depth-first order.
+ 3. modify/delete/add/replace as needed at each path.
+ */
+
+ /* Process one hierarchy of nested branches at a time. */
+ branches = svn_branch__txn_get_branches(eb->txn, scratch_pool);
+ for (i = 0; i < branches->nelts; i++)
+ {
+ svn_branch__state_t *root_branch = APR_ARRAY_IDX(branches, i, void *);
+ apr_hash_t *paths_final;
+
+ const char *top_path = branch_get_storage_root_rrpath(root_branch,
+ scratch_pool);
+ svn_pathrev_t current;
+ svn_branch__state_t *base_root_branch;
+ svn_boolean_t branch_is_new;
+
+ if (strchr(root_branch->bid, '.'))
+ continue; /* that's not a root branch */
+
+ SVN_ERR(svn_branch__repos_get_branch_by_id(&base_root_branch,
+ eb->txn->repos,
+ eb->txn->base_rev,
+ root_branch->bid, scratch_pool));
+ branch_is_new = !base_root_branch;
+
+ paths_final = apr_hash_make(scratch_pool);
+ SVN_ERR(convert_branch_to_paths_r(paths_final,
+ root_branch,
+ scratch_pool, scratch_pool));
+
+ current.rev = eb->txn->base_rev;
+ current.relpath = top_path;
+
+ /* Create the top-level storage node if the branch is new, or if this is
+ the first commit to branch B0 which was created in r0 but had no
+ storage node there. */
+ if (branch_is_new || current.rev == 0)
+ {
+ change_node_t *change;
+
+ SVN_ERR(insert_change(&change, eb->changes, top_path, RESTRUCTURE_ADD));
+ change->kind = svn_node_dir;
+ }
+
+ SVN_ERR(drive_changes_r(top_path, &current,
+ paths_final, svn_branch__get_id(root_branch,
+ scratch_pool),
+ eb, scratch_pool));
+ }
+
+ /* If the driver has not explicitly opened the root directory via the
+ start_edit (aka open_root) callback, do so now. */
+ if (eb->ev1_root_dir_baton == NULL)
+ SVN_ERR(open_root_ev3(eb, SVN_INVALID_REVNUM));
+
+ /* Make the path driver visit the root dir of the edit. Otherwise, it
+ will attempt an open_root() instead, which we already did. */
+ if (! svn_hash_gets(eb->changes, ""))
+ {
+ change_node_t *change;
+
+ SVN_ERR(insert_change(&change, eb->changes, "", RESTRUCTURE_NONE));
+ change->kind = svn_node_dir;
+ }
+
+ /* Apply the appropriate Ev1 change to each Ev1-relative path. */
+ paths = get_unsorted_paths(eb->changes, scratch_pool);
+ SVN_ERR(svn_delta_path_driver2(eb->deditor, eb->dedit_baton,
+ paths, TRUE /*sort*/,
+ apply_change, (void *)eb,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static apr_array_header_t *
+compat_branch_txn_get_branches(const svn_branch__txn_t *txn,
+ apr_pool_t *result_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ apr_array_header_t *branches
+ = svn_branch__txn_get_branches(txn->priv->txn,
+ result_pool);
+
+ return branches;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+compat_branch_txn_delete_branch(svn_branch__txn_t *txn,
+ const char *bid,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_delete_branch(txn->priv->txn,
+ bid,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+compat_branch_txn_get_num_new_eids(const svn_branch__txn_t *txn,
+ int *num_new_eids_p,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_get_num_new_eids(txn->priv->txn,
+ num_new_eids_p,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+compat_branch_txn_new_eid(svn_branch__txn_t *txn,
+ svn_branch__eid_t *eid_p,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_new_eid(txn->priv->txn,
+ eid_p,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+compat_branch_txn_finalize_eids(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_finalize_eids(txn->priv->txn,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+compat_branch_txn_open_branch(svn_branch__txn_t *txn,
+ svn_branch__state_t **new_branch_p,
+ const char *new_branch_id,
+ int root_eid,
+ svn_branch__rev_bid_eid_t *tree_ref,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_open_branch(txn->priv->txn,
+ new_branch_p,
+ new_branch_id, root_eid, tree_ref,
+ result_pool,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+compat_branch_txn_serialize(svn_branch__txn_t *txn,
+ svn_stream_t *stream,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_serialize(txn->priv->txn,
+ stream,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+compat_branch_txn_sequence_point(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_sequence_point(txn->priv->txn,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+compat_branch_txn_complete(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__txn_priv_t *eb = txn->priv;
+ svn_error_t *err;
+
+ /* Convert the transaction to a revision */
+ SVN_ERR(svn_branch__txn_sequence_point(txn->priv->txn, scratch_pool));
+ SVN_ERR(svn_branch__txn_finalize_eids(txn->priv->txn, scratch_pool));
+
+ err = drive_changes(eb, scratch_pool);
+
+ if (!err)
+ {
+ err = svn_error_compose_create(err, eb->deditor->close_edit(
+ eb->dedit_baton,
+ scratch_pool));
+ }
+
+ if (err)
+ svn_error_clear(eb->deditor->abort_edit(eb->dedit_baton, scratch_pool));
+
+ SVN_ERR(svn_branch__txn_complete(txn->priv->txn, scratch_pool));
+
+ return err;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+compat_branch_txn_abort(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__txn_priv_t *eb = txn->priv;
+
+ SVN_ERR(eb->deditor->abort_edit(eb->dedit_baton, scratch_pool));
+
+ SVN_ERR(svn_branch__txn_abort(txn->priv->txn,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Baton for wrap_fetch_func. */
+typedef struct wrap_fetch_baton_t
+{
+ /* Wrapped fetcher */
+ svn_branch__compat_fetch_func_t fetch_func;
+ void *fetch_baton;
+} wrap_fetch_baton_t;
+
+/* The purpose of this fetcher-wrapper is to make it appear that B0
+ * was created (as an empty dir) in r0.
+ */
+static svn_error_t *
+wrap_fetch_func(svn_node_kind_t *kind,
+ apr_hash_t **props,
+ svn_stringbuf_t **file_text,
+ apr_hash_t **children_names,
+ void *baton,
+ const char *repos_relpath,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ wrap_fetch_baton_t *b = baton;
+
+ if (revision == 0 && strcmp(repos_relpath, "top0") == 0)
+ {
+ if (kind)
+ *kind = svn_node_dir;
+ if (props)
+ *props = apr_hash_make(result_pool);
+ if (file_text)
+ *file_text = NULL;
+ if (children_names)
+ *children_names = apr_hash_make(result_pool);
+ }
+ else
+ {
+ SVN_ERR(b->fetch_func(kind, props, file_text, children_names,
+ b->fetch_baton,
+ repos_relpath, revision,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__compat_txn_from_delta_for_commit(
+ svn_branch__txn_t **txn_p,
+ svn_branch__compat_shim_connector_t **shim_connector,
+ const svn_delta_editor_t *deditor,
+ void *dedit_baton,
+ svn_branch__txn_t *branching_txn,
+ const char *repos_root_url,
+ svn_branch__compat_fetch_func_t fetch_func,
+ void *fetch_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ static const svn_branch__txn_vtable_t vtable = {
+ {0},
+ compat_branch_txn_get_branches,
+ compat_branch_txn_delete_branch,
+ compat_branch_txn_get_num_new_eids,
+ compat_branch_txn_new_eid,
+ compat_branch_txn_open_branch,
+ compat_branch_txn_finalize_eids,
+ compat_branch_txn_serialize,
+ compat_branch_txn_sequence_point,
+ compat_branch_txn_complete,
+ compat_branch_txn_abort
+ };
+ svn_branch__txn_t *txn;
+ svn_branch__txn_priv_t *eb = apr_pcalloc(result_pool, sizeof(*eb));
+ wrap_fetch_baton_t *wb = apr_pcalloc(result_pool, sizeof(*wb));
+
+ eb->deditor = deditor;
+ eb->dedit_baton = dedit_baton;
+
+ eb->repos_root_url = apr_pstrdup(result_pool, repos_root_url);
+
+ eb->changes = apr_hash_make(result_pool);
+
+ wb->fetch_func = fetch_func;
+ wb->fetch_baton = fetch_baton;
+ eb->fetch_func = wrap_fetch_func;
+ eb->fetch_baton = wb;
+
+ eb->edit_pool = result_pool;
+
+ branching_txn = svn_branch__nested_txn_create(branching_txn, result_pool);
+
+ eb->txn = branching_txn;
+
+ txn = svn_branch__txn_create(&vtable, NULL, NULL, result_pool);
+ txn->priv = eb;
+ txn->repos = branching_txn->repos;
+ txn->rev = branching_txn->rev;
+ txn->base_rev = branching_txn->base_rev;
+ *txn_p = txn;
+
+ if (shim_connector)
+ {
+#if 0
+ *shim_connector = apr_palloc(result_pool, sizeof(**shim_connector));
+#ifdef SHIM_WITH_ABS_PATHS
+ (*shim_connector)->ev1_absolute_paths
+ = apr_palloc(result_pool, sizeof(svn_boolean_t));
+ eb->make_abs_paths = (*shim_connector)->ev1_absolute_paths;
+#endif
+ (*shim_connector)->target_revision_func = set_target_revision_ev3;
+ (*shim_connector)->start_edit_func = open_root_ev3;
+#ifdef SHIM_WITH_UNLOCK
+ (*shim_connector)->unlock_func = do_unlock;
+#endif
+ (*shim_connector)->baton = eb;
+#endif
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__compat_txn_from_delta_for_update(
+ svn_branch__compat_update_editor3_t **update_editor_p,
+ const svn_delta_editor_t *deditor,
+ void *dedit_baton,
+ svn_branch__txn_t *branching_txn,
+ const char *repos_root_url,
+ const char *base_repos_relpath,
+ svn_branch__compat_fetch_func_t fetch_func,
+ void *fetch_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__compat_update_editor3_t *update_editor
+ = apr_pcalloc(result_pool, sizeof(*update_editor));
+ svn_branch__compat_shim_connector_t *shim_connector;
+
+ /*(("svn_delta__ev3_from_delta_for_update(base='%s')...",
+ base_repos_relpath));*/
+
+ /*SVN_ERR(svn_delta__get_debug_editor(&deditor, &dedit_baton,
+ deditor, dedit_baton,
+ "[1>UP] ", result_pool));*/
+ SVN_ERR(svn_branch__compat_txn_from_delta_for_commit(
+ &update_editor->edit_txn,
+ &shim_connector,
+ deditor, dedit_baton,
+ branching_txn, repos_root_url,
+ fetch_func, fetch_baton,
+ cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+
+ update_editor->set_target_revision_func = shim_connector->target_revision_func;
+ update_editor->set_target_revision_baton = shim_connector->baton;
+ /* shim_connector->start_edit_func = open_root_ev3; */
+#ifdef SHIM_WITH_ABS_PATHS
+ update_editor->ev1_absolute_paths /*...*/;
+#endif
+#ifdef SHIM_WITH_UNLOCK
+ update_editor->unlock_func = do_unlock;
+#endif
+
+ *update_editor_p = update_editor;
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_delta/branch_migrate.c b/subversion/libsvn_delta/branch_migrate.c
new file mode 100644
index 000000000000..7b741810638d
--- /dev/null
+++ b/subversion/libsvn_delta/branch_migrate.c
@@ -0,0 +1,366 @@
+/*
+ * migrate.c: Migrate history from non-move-tracking revisions
+ *
+ * ====================================================================
+ * 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 "svn_types.h"
+#include "svn_error.h"
+#include "svn_delta.h"
+#include "svn_ra.h"
+
+#include "private/svn_branch.h"
+#include "private/svn_branch_compat.h"
+
+#include "svn_private_config.h"
+
+
+struct edit_baton
+{
+ svn_branch__txn_t *edit_txn;
+ svn_ra_session_t *from_session;
+ svn_revnum_t revision;
+};
+
+struct dir_baton
+{
+ struct edit_baton *edit_baton;
+};
+
+struct file_baton
+{
+ struct edit_baton *edit_baton;
+};
+
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ /*struct edit_baton *eb = edit_baton;*/
+
+ /*SVN_ERR(eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
+ target_revision,
+ pool));*/
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ struct dir_baton *dir_baton = apr_palloc(pool, sizeof(*dir_baton));
+
+ /*SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
+ base_revision,
+ pool,
+ &dir_baton->wrapped_dir_baton));*/
+
+ dir_baton->edit_baton = eb;
+
+ *root_baton = dir_baton;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t base_revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ /*struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;*/
+
+ /*SVN_ERR(eb->wrapped_editor->delete_entry(path,
+ base_revision,
+ pb->wrapped_dir_baton,
+ pool);*/
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *db = apr_palloc(pool, sizeof(*db));
+
+ /*SVN_ERR(eb->wrapped_editor->add_directory(path,
+ pb->wrapped_dir_baton,
+ copyfrom_path,
+ copyfrom_revision,
+ pool,
+ &db->wrapped_dir_baton));*/
+
+ db->edit_baton = eb;
+ *child_baton = db;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *db = apr_palloc(pool, sizeof(*db));
+
+ /*SVN_ERR(eb->wrapped_editor->open_directory(path,
+ pb->wrapped_dir_baton,
+ base_revision,
+ pool,
+ &db->wrapped_dir_baton));*/
+
+ db->edit_baton = eb;
+ *child_baton = db;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *fb = apr_palloc(pool, sizeof(*fb));
+
+ /*SVN_ERR(eb->wrapped_editor->add_file(path,
+ pb->wrapped_dir_baton,
+ copyfrom_path,
+ copyfrom_revision,
+ pool,
+ &fb->wrapped_file_baton));*/
+
+ fb->edit_baton = eb;
+ *file_baton = fb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *fb = apr_palloc(pool, sizeof(*fb));
+
+ /*SVN_ERR(eb->wrapped_editor->open_file(path,
+ pb->wrapped_dir_baton,
+ base_revision,
+ pool,
+ &fb->wrapped_file_baton));*/
+
+ fb->edit_baton = eb;
+ *file_baton = fb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ /*struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;*/
+
+ /*SVN_ERR(eb->wrapped_editor->apply_textdelta(fb->wrapped_file_baton,
+ base_checksum,
+ pool,
+ handler,
+ handler_baton));*/
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ /*struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;*/
+
+ /*SVN_ERR(eb->wrapped_editor->close_file(fb->wrapped_file_baton,
+ text_checksum, pool));*/
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+absent_file(const char *path,
+ void *file_baton,
+ apr_pool_t *pool)
+{
+ /*struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;*/
+
+ /*SVN_ERR(eb->wrapped_editor->absent_file(path, fb->wrapped_file_baton,
+ pool));*/
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ /*struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;*/
+
+ /*SVN_ERR(eb->wrapped_editor->close_directory(db->wrapped_dir_baton,
+ pool));*/
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+absent_directory(const char *path,
+ void *dir_baton,
+ apr_pool_t *pool)
+{
+ /*struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;*/
+
+ /*SVN_ERR(eb->wrapped_editor->absent_directory(path, db->wrapped_dir_baton,
+ pool));*/
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ /*struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;*/
+
+ /*SVN_ERR(eb->wrapped_editor->change_file_prop(fb->wrapped_file_baton,
+ name,
+ value,
+ pool));*/
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ /*struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;*/
+
+ /*SVN_ERR(eb->wrapped_editor->change_dir_prop(db->wrapped_dir_baton,
+ name,
+ value,
+ pool));*/
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ /*struct edit_baton *eb = edit_baton;*/
+
+ /*SVN_ERR(eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool));*/
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+abort_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ /*struct edit_baton *eb = edit_baton;*/
+
+ /*SVN_ERR(eb->wrapped_editor->abort_edit(eb->wrapped_edit_baton, pool));*/
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__compat_get_migration_editor(
+ const svn_delta_editor_t **old_editor,
+ void **old_edit_baton,
+ svn_branch__txn_t *edit_txn,
+ svn_ra_session_t *from_session,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool)
+{
+ static const svn_delta_editor_t editor = {
+ set_target_revision,
+ open_root,
+ delete_entry,
+ add_directory,
+ open_directory,
+ change_dir_prop,
+ close_directory,
+ absent_directory,
+ add_file,
+ open_file,
+ apply_textdelta,
+ change_file_prop,
+ close_file,
+ absent_file,
+ close_edit,
+ abort_edit
+ };
+ struct edit_baton *eb = apr_palloc(result_pool, sizeof(*eb));
+
+ eb->edit_txn = edit_txn;
+ eb->from_session = from_session;
+ eb->revision = revision;
+
+ *old_editor = &editor;
+ *old_edit_baton = eb;
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/subversion/libsvn_delta/branch_nested.c b/subversion/libsvn_delta/branch_nested.c
new file mode 100644
index 000000000000..191d2d3b154b
--- /dev/null
+++ b/subversion/libsvn_delta/branch_nested.c
@@ -0,0 +1,660 @@
+/*
+ * branch_nested.c : Nested Branches
+ *
+ * ====================================================================
+ * 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_branch_nested.h"
+#include "private/svn_branch_impl.h"
+#include "private/svn_branch_repos.h"
+
+#include "svn_private_config.h"
+
+
+void
+svn_branch__get_outer_branch_and_eid(svn_branch__state_t **outer_branch_p,
+ int *outer_eid_p,
+ const svn_branch__state_t *branch,
+ apr_pool_t *scratch_pool)
+{
+ const char *outer_bid;
+
+ svn_branch__id_unnest(&outer_bid, outer_eid_p, branch->bid, scratch_pool);
+ *outer_branch_p = NULL;
+ if (outer_bid)
+ {
+ *outer_branch_p
+ = svn_branch__txn_get_branch_by_id(branch->txn, outer_bid,
+ scratch_pool);
+ }
+}
+
+const char *
+svn_branch__get_root_rrpath(const svn_branch__state_t *branch,
+ apr_pool_t *result_pool)
+{
+ svn_branch__state_t *outer_branch;
+ int outer_eid;
+ const char *root_rrpath;
+
+ svn_branch__get_outer_branch_and_eid(&outer_branch, &outer_eid, branch,
+ result_pool);
+ if (outer_branch)
+ {
+ root_rrpath
+ = svn_branch__get_rrpath_by_eid(outer_branch, outer_eid, result_pool);
+ }
+ else
+ {
+ root_rrpath = "";
+ }
+
+ SVN_ERR_ASSERT_NO_RETURN(root_rrpath);
+ return root_rrpath;
+}
+
+const char *
+svn_branch__get_rrpath_by_eid(const svn_branch__state_t *branch,
+ int eid,
+ apr_pool_t *result_pool)
+{
+ const char *path = svn_branch__get_path_by_eid(branch, eid, result_pool);
+ const char *rrpath = NULL;
+
+ if (path)
+ {
+ rrpath = svn_relpath_join(svn_branch__get_root_rrpath(branch, result_pool),
+ path, result_pool);
+ }
+ return rrpath;
+}
+
+svn_error_t *
+svn_branch__get_subbranch_at_eid(svn_branch__state_t *branch,
+ svn_branch__state_t **subbranch_p,
+ int eid,
+ apr_pool_t *scratch_pool)
+{
+ svn_element__content_t *element;
+
+ SVN_ERR(svn_branch__state_get_element(branch, &element, eid, scratch_pool));
+ if (element && element->payload->is_subbranch_root)
+ {
+ const char *branch_id = svn_branch__get_id(branch, scratch_pool);
+ const char *subbranch_id = svn_branch__id_nest(branch_id, eid,
+ scratch_pool);
+
+ *subbranch_p = svn_branch__txn_get_branch_by_id(branch->txn, subbranch_id,
+ scratch_pool);
+ }
+ else
+ {
+ *subbranch_p = NULL;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Set *SUBBRANCH_EIDS_P an array of EIDs of the subbranch-root elements in
+ * BRANCH.
+ */
+static svn_error_t *
+svn_branch__get_immediate_subbranch_eids(svn_branch__state_t *branch,
+ apr_array_header_t **subbranch_eids_p,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *subbranch_eids
+ = apr_array_make(result_pool, 0, sizeof(int));
+ svn_element__tree_t *elements;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(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);
+ svn_element__content_t *element = apr_hash_this_val(hi);
+
+ if (element->payload->is_subbranch_root)
+ {
+ APR_ARRAY_PUSH(subbranch_eids, int) = eid;
+ }
+ }
+ *subbranch_eids_p = subbranch_eids;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__get_immediate_subbranches(svn_branch__state_t *branch,
+ apr_array_header_t **subbranches_p,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *subbranch_eids;
+ apr_array_header_t *subbranches
+ = apr_array_make(result_pool, 0, sizeof(void *));
+ const char *branch_id = svn_branch__get_id(branch, scratch_pool);
+ int i;
+
+ SVN_ERR(svn_branch__get_immediate_subbranch_eids(branch, &subbranch_eids,
+ scratch_pool, scratch_pool));
+ for (i = 0; i < subbranch_eids->nelts; i++)
+ {
+ int eid = APR_ARRAY_IDX(subbranch_eids, i, int);
+ const char *subbranch_id
+ = svn_branch__id_nest(branch_id, eid, scratch_pool);
+ svn_branch__state_t *subbranch
+ = svn_branch__txn_get_branch_by_id(branch->txn, subbranch_id,
+ scratch_pool);
+
+ SVN_ERR_ASSERT_NO_RETURN(subbranch);
+ APR_ARRAY_PUSH(subbranches, void *) = subbranch;
+ }
+ *subbranches_p = subbranches;
+ return SVN_NO_ERROR;
+}
+
+svn_branch__subtree_t *
+svn_branch__subtree_create(apr_hash_t *e_map,
+ int root_eid,
+ apr_pool_t *result_pool)
+{
+ svn_branch__subtree_t *subtree = apr_pcalloc(result_pool, sizeof(*subtree));
+
+ subtree->tree = svn_element__tree_create(e_map, root_eid, result_pool);
+ subtree->subbranches = apr_hash_make(result_pool);
+ return subtree;
+}
+
+svn_error_t *
+svn_branch__get_subtree(svn_branch__state_t *branch,
+ svn_branch__subtree_t **subtree_p,
+ int eid,
+ apr_pool_t *result_pool)
+{
+ svn_element__tree_t *element_tree;
+ svn_branch__subtree_t *new_subtree;
+ apr_array_header_t *subbranch_eids;
+ int i;
+ apr_pool_t *iterpool = result_pool; /* ### not a proper iterpool */
+
+ SVN_ERR(svn_branch__state_get_elements(branch, &element_tree, result_pool));
+ element_tree = svn_element__tree_get_subtree_at_eid(element_tree, eid,
+ result_pool);
+ new_subtree
+ = svn_branch__subtree_create(element_tree->e_map, eid, result_pool);
+
+ /* Add subbranches */
+ SVN_ERR(svn_branch__get_immediate_subbranch_eids(branch, &subbranch_eids,
+ result_pool, result_pool));
+ for (i = 0; i < subbranch_eids->nelts; i++)
+ {
+ int outer_eid = APR_ARRAY_IDX(subbranch_eids, i, int);
+ const char *subbranch_relpath_in_subtree;
+
+ subbranch_relpath_in_subtree
+ = svn_element__tree_get_path_by_eid(new_subtree->tree, outer_eid,
+ iterpool);
+
+ /* Is it pathwise at or below EID? If so, add it into the subtree. */
+ if (subbranch_relpath_in_subtree)
+ {
+ svn_branch__state_t *subbranch;
+ svn_branch__subtree_t *this_subtree;
+
+ SVN_ERR(svn_branch__get_subbranch_at_eid(branch, &subbranch,
+ outer_eid, iterpool));
+ if (subbranch)
+ {
+ SVN_ERR(svn_branch__get_subtree(subbranch, &this_subtree,
+ svn_branch__root_eid(subbranch),
+ result_pool));
+ svn_eid__hash_set(new_subtree->subbranches, outer_eid,
+ this_subtree);
+ }
+ }
+ }
+ *subtree_p = new_subtree;
+ return SVN_NO_ERROR;
+}
+
+svn_branch__subtree_t *
+svn_branch__subtree_get_subbranch_at_eid(svn_branch__subtree_t *subtree,
+ int eid,
+ apr_pool_t *result_pool)
+{
+ subtree = svn_eid__hash_get(subtree->subbranches, eid);
+
+ return subtree;
+}
+
+/* Instantiate ELEMENTS in TO_BRANCH.
+ */
+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);
+
+ SVN_ERR(svn_branch__state_set_element(to_branch, this_eid,
+ this_element, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__instantiate_elements_r(svn_branch__state_t *to_branch,
+ svn_branch__subtree_t elements,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(branch_instantiate_elements(to_branch, elements.tree,
+ scratch_pool));
+
+ /* branch any subbranches */
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, elements.subbranches);
+ hi; hi = apr_hash_next(hi))
+ {
+ int this_outer_eid = svn_eid__hash_this_key(hi);
+ svn_branch__subtree_t *this_subtree = apr_hash_this_val(hi);
+ const char *new_branch_id;
+ svn_branch__state_t *new_branch;
+ /*### svn_branch__history_t *history;*/
+
+ /* branch this subbranch into NEW_BRANCH (recursing) */
+ new_branch_id = svn_branch__id_nest(to_branch->bid, this_outer_eid,
+ scratch_pool);
+ SVN_ERR(svn_branch__txn_open_branch(to_branch->txn, &new_branch,
+ new_branch_id,
+ this_subtree->tree->root_eid,
+ NULL /*tree_ref*/,
+ scratch_pool, scratch_pool));
+ /*### SVN_ERR(svn_branch__state_set_history(new_branch, history,
+ scratch_pool));*/
+
+ SVN_ERR(svn_branch__instantiate_elements_r(new_branch, *this_subtree,
+ scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * ========================================================================
+ */
+
+svn_error_t *
+svn_branch__find_nested_branch_element_by_relpath(
+ svn_branch__state_t **branch_p,
+ int *eid_p,
+ svn_branch__state_t *root_branch,
+ const char *relpath,
+ apr_pool_t *scratch_pool)
+{
+ /* The path we're looking for is (path-wise) in this branch. See if it
+ is also in a sub-branch. */
+ /* Loop invariants: RELPATH is the path we're looking for, relative to
+ ROOT_BRANCH which is the current level of nesting that we've descended
+ into. */
+ while (TRUE)
+ {
+ apr_array_header_t *subbranch_eids;
+ int i;
+ svn_boolean_t found = FALSE;
+
+ SVN_ERR(svn_branch__get_immediate_subbranch_eids(
+ root_branch, &subbranch_eids, scratch_pool, scratch_pool));
+ for (i = 0; i < subbranch_eids->nelts; i++)
+ {
+ int outer_eid = APR_ARRAY_IDX(subbranch_eids, i, int);
+ const char *relpath_to_subbranch;
+ const char *relpath_in_subbranch;
+
+ /* Check whether the RELPATH we're looking for is within this
+ subbranch at OUTER_EID. If it is, recurse in the subbranch. */
+ relpath_to_subbranch
+ = svn_branch__get_path_by_eid(root_branch, outer_eid, scratch_pool);
+ relpath_in_subbranch
+ = svn_relpath_skip_ancestor(relpath_to_subbranch, relpath);
+ if (relpath_in_subbranch)
+ {
+ svn_branch__state_t *subbranch;
+
+ SVN_ERR(svn_branch__get_subbranch_at_eid(
+ root_branch, &subbranch, outer_eid, scratch_pool));
+ /* If the branch hierarchy is not 'flat' then we might find
+ there is no actual branch where the subbranch-root element
+ says there should be one. In that case, ignore it. */
+ if (subbranch)
+ {
+ root_branch = subbranch;
+ relpath = relpath_in_subbranch;
+ found = TRUE;
+ break;
+ }
+ }
+ }
+ if (! found)
+ {
+ break;
+ }
+ }
+
+ *branch_p = root_branch;
+ if (eid_p)
+ *eid_p = svn_branch__get_eid_by_path(root_branch, relpath, scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__repos_find_el_rev_by_path_rev(svn_branch__el_rev_id_t **el_rev_p,
+ const svn_branch__repos_t *repos,
+ svn_revnum_t revnum,
+ const char *branch_id,
+ const char *relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__el_rev_id_t *el_rev = apr_palloc(result_pool, sizeof(*el_rev));
+ svn_branch__state_t *branch;
+
+ SVN_ERR(svn_branch__repos_get_branch_by_id(&branch,
+ repos, revnum, branch_id,
+ scratch_pool));
+ el_rev->rev = revnum;
+ SVN_ERR(svn_branch__find_nested_branch_element_by_relpath(&el_rev->branch,
+ &el_rev->eid,
+ branch, relpath,
+ scratch_pool));
+
+ /* Any relpath must at least be within the originally given branch */
+ SVN_ERR_ASSERT_NO_RETURN(el_rev->branch);
+ *el_rev_p = el_rev;
+ return SVN_NO_ERROR;
+}
+
+/* Set *BRANCH_P to the branch found in the repository of TXN, at the
+ * location (in a revision or in this txn) SRC_EL_REV.
+ *
+ * Return an error if REVNUM or BRANCH_ID is not found.
+ */
+static svn_error_t *
+branch_in_rev_or_txn(svn_branch__state_t **branch_p,
+ 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(branch_p,
+ txn->repos,
+ src_el_rev->rev,
+ src_el_rev->bid,
+ result_pool));
+ }
+ else
+ {
+ *branch_p
+ = svn_branch__txn_get_branch_by_id(
+ txn, src_el_rev->bid, result_pool);
+ if (! *branch_p)
+ return svn_error_createf(SVN_BRANCH__ERR, NULL,
+ _("Branch %s not found"),
+ src_el_rev->bid);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct svn_branch__txn_priv_t
+{
+ /* The underlying branch-txn that supports only non-nested branching. */
+ svn_branch__txn_t *wrapped_txn;
+
+};
+
+/* Implements nested branching.
+ * An #svn_branch__txn_t method. */
+static apr_array_header_t *
+nested_branch_txn_get_branches(const svn_branch__txn_t *txn,
+ apr_pool_t *result_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ apr_array_header_t *branches
+ = svn_branch__txn_get_branches(txn->priv->wrapped_txn,
+ result_pool);
+
+ return branches;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+nested_branch_txn_delete_branch(svn_branch__txn_t *txn,
+ const char *bid,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_delete_branch(txn->priv->wrapped_txn,
+ bid,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Implements nested branching.
+ * An #svn_branch__txn_t method. */
+static svn_error_t *
+nested_branch_txn_get_num_new_eids(const svn_branch__txn_t *txn,
+ int *num_new_eids_p,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_get_num_new_eids(txn->priv->wrapped_txn,
+ num_new_eids_p,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Implements nested branching.
+ * An #svn_branch__txn_t method. */
+static svn_error_t *
+nested_branch_txn_new_eid(svn_branch__txn_t *txn,
+ svn_branch__eid_t *eid_p,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_new_eid(txn->priv->wrapped_txn,
+ eid_p,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Implements nested branching.
+ * An #svn_branch__txn_t method. */
+static svn_error_t *
+nested_branch_txn_open_branch(svn_branch__txn_t *txn,
+ svn_branch__state_t **new_branch_p,
+ const char *new_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;
+
+ SVN_ERR(svn_branch__txn_open_branch(txn->priv->wrapped_txn,
+ &new_branch,
+ new_branch_id, root_eid, tree_ref,
+ result_pool,
+ scratch_pool));
+
+ /* Recursively branch any nested branches */
+ if (tree_ref)
+ {
+ svn_branch__state_t *from_branch;
+ svn_branch__subtree_t *from_subtree;
+
+ /* (The way we're doing it here also redundantly re-instantiates all the
+ elements in NEW_BRANCH.) */
+ SVN_ERR(branch_in_rev_or_txn(&from_branch, tree_ref,
+ txn->priv->wrapped_txn, scratch_pool));
+ SVN_ERR(svn_branch__get_subtree(from_branch, &from_subtree,
+ tree_ref->eid, scratch_pool));
+ SVN_ERR(svn_branch__instantiate_elements_r(new_branch, *from_subtree,
+ scratch_pool));
+ }
+
+ if (new_branch_p)
+ *new_branch_p = new_branch;
+ return SVN_NO_ERROR;
+}
+
+/* Implements nested branching.
+ * An #svn_branch__txn_t method. */
+static svn_error_t *
+nested_branch_txn_finalize_eids(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_finalize_eids(txn->priv->wrapped_txn,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Implements nested branching.
+ * An #svn_branch__txn_t method. */
+static svn_error_t *
+nested_branch_txn_serialize(svn_branch__txn_t *txn,
+ svn_stream_t *stream,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_serialize(txn->priv->wrapped_txn,
+ stream,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Implements nested branching.
+ * An #svn_branch__txn_t method. */
+static svn_error_t *
+nested_branch_txn_sequence_point(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__txn_t *wrapped_txn = txn->priv->wrapped_txn;
+ apr_array_header_t *branches;
+ int i;
+
+ /* first, purge elements in each branch */
+ SVN_ERR(svn_branch__txn_sequence_point(wrapped_txn, scratch_pool));
+
+ /* second, purge branches that are no longer nested */
+ branches = svn_branch__txn_get_branches(wrapped_txn, scratch_pool);
+ for (i = 0; i < branches->nelts; i++)
+ {
+ svn_branch__state_t *b = APR_ARRAY_IDX(branches, i, void *);
+ svn_branch__state_t *outer_branch;
+ int outer_eid;
+
+ svn_branch__get_outer_branch_and_eid(&outer_branch, &outer_eid,
+ b, scratch_pool);
+ if (outer_branch)
+ {
+ svn_element__content_t *element;
+
+ SVN_ERR(svn_branch__state_get_element(outer_branch, &element,
+ outer_eid, scratch_pool));
+ if (! element)
+ SVN_ERR(svn_branch__txn_delete_branch(wrapped_txn, b->bid,
+ scratch_pool));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+nested_branch_txn_complete(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_complete(txn->priv->wrapped_txn,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* An #svn_branch__txn_t method. */
+static svn_error_t *
+nested_branch_txn_abort(svn_branch__txn_t *txn,
+ apr_pool_t *scratch_pool)
+{
+ /* Just forwarding: nothing more is needed. */
+ SVN_ERR(svn_branch__txn_abort(txn->priv->wrapped_txn,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_branch__txn_t *
+svn_branch__nested_txn_create(svn_branch__txn_t *wrapped_txn,
+ apr_pool_t *result_pool)
+{
+ static const svn_branch__txn_vtable_t vtable = {
+ {0},
+ nested_branch_txn_get_branches,
+ nested_branch_txn_delete_branch,
+ nested_branch_txn_get_num_new_eids,
+ nested_branch_txn_new_eid,
+ nested_branch_txn_open_branch,
+ nested_branch_txn_finalize_eids,
+ nested_branch_txn_serialize,
+ nested_branch_txn_sequence_point,
+ nested_branch_txn_complete,
+ nested_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->priv->wrapped_txn = wrapped_txn;
+ txn->repos = wrapped_txn->repos;
+ txn->rev = wrapped_txn->rev;
+ txn->base_rev = wrapped_txn->base_rev;
+ return txn;
+}
+
diff --git a/subversion/libsvn_delta/branch_repos.c b/subversion/libsvn_delta/branch_repos.c
new file mode 100644
index 000000000000..1ba86b193c4d
--- /dev/null
+++ b/subversion/libsvn_delta/branch_repos.c
@@ -0,0 +1,132 @@
+/*
+ * branch_repos.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_iter.h"
+
+#include "private/svn_branch_repos.h"
+#include "svn_private_config.h"
+
+
+/* Per-repository branching info.
+ */
+struct svn_branch__repos_t
+{
+ /* Array of (svn_branch__txn_t *), indexed by revision number. */
+ apr_array_header_t *rev_roots;
+
+ /* The pool in which this object lives. */
+ apr_pool_t *pool;
+};
+
+
+svn_branch__repos_t *
+svn_branch__repos_create(apr_pool_t *result_pool)
+{
+ svn_branch__repos_t *repos = apr_pcalloc(result_pool, sizeof(*repos));
+
+ repos->rev_roots = apr_array_make(result_pool, 0, sizeof(void *));
+ repos->pool = result_pool;
+ return repos;
+}
+
+svn_error_t *
+svn_branch__repos_add_revision(svn_branch__repos_t *repos,
+ svn_branch__txn_t *rev_root)
+{
+ APR_ARRAY_PUSH(repos->rev_roots, void *) = rev_root;
+
+ return SVN_NO_ERROR;
+}
+
+struct svn_branch__txn_t *
+svn_branch__repos_get_revision(const svn_branch__repos_t *repos,
+ svn_revnum_t revnum)
+{
+ assert(revnum < repos->rev_roots->nelts);
+ return APR_ARRAY_IDX(repos->rev_roots, revnum, void *);
+}
+
+svn_branch__txn_t *
+svn_branch__repos_get_base_revision_root(svn_branch__txn_t *rev_root)
+{
+ return svn_branch__repos_get_revision(rev_root->repos, rev_root->base_rev);
+}
+
+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)
+{
+ svn_branch__txn_t *rev_root;
+
+ if (revnum < 0 || revnum >= repos->rev_roots->nelts)
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld"), revnum);
+
+ rev_root = svn_branch__repos_get_revision(repos, revnum);
+ *branch_p = svn_branch__txn_get_branch_by_id(rev_root, branch_id,
+ scratch_pool);
+ if (! *branch_p)
+ return svn_error_createf(SVN_BRANCH__ERR, NULL,
+ _("Branch %s not found in r%ld"),
+ branch_id, revnum);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch__repos_find_el_rev_by_id(svn_branch__el_rev_id_t **el_rev_p,
+ const svn_branch__repos_t *repos,
+ svn_revnum_t revnum,
+ const char *branch_id,
+ int eid,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch__el_rev_id_t *el_rev = apr_palloc(result_pool, sizeof(*el_rev));
+ svn_element__content_t *element;
+
+ el_rev->rev = revnum;
+ SVN_ERR(svn_branch__repos_get_branch_by_id(&el_rev->branch,
+ repos, revnum, branch_id,
+ scratch_pool));
+ SVN_ERR(svn_branch__state_get_element(el_rev->branch, &element,
+ eid, scratch_pool));
+ if (element)
+ {
+ el_rev->eid = eid;
+ }
+ else
+ {
+ el_rev->eid = -1;
+ }
+ *el_rev_p = el_rev;
+ return SVN_NO_ERROR;
+}
+
diff --git a/subversion/libsvn_delta/cancel.c b/subversion/libsvn_delta/cancel.c
index 89995a85ebb6..ef245b821a35 100644
--- a/subversion/libsvn_delta/cancel.c
+++ b/subversion/libsvn_delta/cancel.c
@@ -222,6 +222,26 @@ apply_textdelta(void *file_baton,
}
static svn_error_t *
+apply_textdelta_stream(const svn_delta_editor_t *editor,
+ void *file_baton,
+ const char *base_checksum,
+ svn_txdelta_stream_open_func_t open_func,
+ void *open_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ return eb->wrapped_editor->apply_textdelta_stream(eb->wrapped_editor,
+ fb->wrapped_file_baton,
+ base_checksum,
+ open_func, open_baton,
+ scratch_pool);
+}
+
+static svn_error_t *
close_file(void *file_baton,
const char *text_checksum,
apr_pool_t *pool)
@@ -354,6 +374,7 @@ svn_delta_get_cancellation_editor(svn_cancel_func_t cancel_func,
tree_editor->add_file = add_file;
tree_editor->open_file = open_file;
tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->apply_textdelta_stream = apply_textdelta_stream;
tree_editor->change_file_prop = change_file_prop;
tree_editor->close_file = close_file;
tree_editor->absent_file = absent_file;
diff --git a/subversion/libsvn_delta/compat.c b/subversion/libsvn_delta/compat.c
index dfa97437b92d..c89e9367cd37 100644
--- a/subversion/libsvn_delta/compat.c
+++ b/subversion/libsvn_delta/compat.c
@@ -816,8 +816,9 @@ static svn_error_t *
open_delta_target(svn_stream_t **stream, void *baton,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
- const char **delta_target = baton;
- return svn_stream_open_unique(stream, delta_target, NULL,
+ struct change_node *change = baton;
+ return svn_stream_open_unique(stream, &change->contents_abspath,
+ NULL,
svn_io_file_del_on_pool_cleanup,
result_pool, scratch_pool);
}
@@ -850,8 +851,7 @@ ev2_apply_textdelta(void *file_baton,
FALSE, handler_pool);
change->contents_changed = TRUE;
- target = svn_stream_lazyopen_create(open_delta_target,
- &change->contents_abspath,
+ target = svn_stream_lazyopen_create(open_delta_target, change,
FALSE, fb->eb->edit_pool);
svn_txdelta_apply(hb->source, target,
@@ -1223,15 +1223,23 @@ alter_file_cb(void *baton,
apr_pool_t *scratch_pool)
{
struct editor_baton *eb = baton;
- const char *tmp_filename;
svn_stream_t *tmp_stream;
- svn_checksum_t *md5_checksum;
struct change_node *change = insert_change(relpath, eb->changes);
+ /* Note: this node may already have information in CHANGE as a result
+ of an earlier copy/move operation. */
+
/* ### should we verify the kind is truly a file? */
+ change->kind = svn_node_file;
+ change->changing = revision;
+ if (props != NULL)
+ change->props = svn_prop_hash_dup(props, eb->edit_pool);
if (contents)
{
+ const char *tmp_filename;
+ svn_checksum_t *md5_checksum;
+
/* We may need to re-checksum these contents */
if (checksum && checksum->kind == svn_checksum_md5)
md5_checksum = (svn_checksum_t *)checksum;
@@ -1246,17 +1254,7 @@ alter_file_cb(void *baton,
eb->edit_pool, scratch_pool));
SVN_ERR(svn_stream_copy3(contents, tmp_stream, NULL, NULL,
scratch_pool));
- }
- /* Note: this node may already have information in CHANGE as a result
- of an earlier copy/move operation. */
-
- change->kind = svn_node_file;
- change->changing = revision;
- if (props != NULL)
- change->props = svn_prop_hash_dup(props, eb->edit_pool);
- if (contents != NULL)
- {
change->contents_changed = TRUE;
change->contents_abspath = tmp_filename;
change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool);
diff --git a/subversion/libsvn_delta/debug_editor.c b/subversion/libsvn_delta/debug_editor.c
index 8ca7b2040137..5f26936d621b 100644
--- a/subversion/libsvn_delta/debug_editor.c
+++ b/subversion/libsvn_delta/debug_editor.c
@@ -23,7 +23,7 @@
#include "svn_io.h"
-#include "debug_editor.h"
+#include "private/svn_delta_private.h"
struct edit_baton
{
diff --git a/subversion/libsvn_delta/debug_editor.h b/subversion/libsvn_delta/debug_editor.h
deleted file mode 100644
index 63c90d32fb12..000000000000
--- a/subversion/libsvn_delta/debug_editor.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * ====================================================================
- * 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.
- * ====================================================================
- */
-
-
-#ifndef SVN_DEBUG_EDITOR_H
-#define SVN_DEBUG_EDITOR_H
-
-#include "svn_delta.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-/* Return a debug editor that wraps @a wrapped_editor.
- *
- * The debug editor simply prints an indication of what callbacks are being
- * called to @c stdout, and is only intended for use in debugging subversion
- * editors.
- *
- * @a prefix, if non-null, is printed between "DBG: " and each indication.
- *
- * Note: Our test suite generally ignores stdout lines starting with "DBG:".
- */
-svn_error_t *
-svn_delta__get_debug_editor(const svn_delta_editor_t **editor,
- void **edit_baton,
- const svn_delta_editor_t *wrapped_editor,
- void *wrapped_baton,
- const char *prefix,
- apr_pool_t *pool);
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-
-#endif /* SVN_DEBUG_EDITOR_H */
diff --git a/subversion/libsvn_delta/default_editor.c b/subversion/libsvn_delta/default_editor.c
index 2f1c9734ba6a..63cbc1599231 100644
--- a/subversion/libsvn_delta/default_editor.c
+++ b/subversion/libsvn_delta/default_editor.c
@@ -133,6 +133,33 @@ close_file(void *file_baton,
}
+static svn_error_t *
+apply_textdelta_stream(const svn_delta_editor_t *editor,
+ void *file_baton,
+ const char *base_checksum,
+ svn_txdelta_stream_open_func_t open_func,
+ void *open_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_txdelta_window_handler_t handler;
+ void *handler_baton;
+
+ SVN_ERR(editor->apply_textdelta(file_baton, base_checksum,
+ scratch_pool, &handler,
+ &handler_baton));
+ if (handler != svn_delta_noop_window_handler)
+ {
+ svn_txdelta_stream_t *txdelta_stream;
+
+ SVN_ERR(open_func(&txdelta_stream, open_baton, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_txdelta_send_txstream(txdelta_stream, handler,
+ handler_baton, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
static const svn_delta_editor_t default_editor =
{
@@ -151,7 +178,8 @@ static const svn_delta_editor_t default_editor =
close_file,
absent_xxx_func,
single_baton_func,
- single_baton_func
+ single_baton_func,
+ apply_textdelta_stream
};
svn_delta_editor_t *
diff --git a/subversion/libsvn_delta/element.c b/subversion/libsvn_delta/element.c
new file mode 100644
index 000000000000..be7b2171b9b4
--- /dev/null
+++ b/subversion/libsvn_delta/element.c
@@ -0,0 +1,471 @@
+/*
+ * element.c : editing trees of versioned resources
+ *
+ * ====================================================================
+ * 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 <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_string.h"
+#include "svn_props.h"
+#include "svn_dirent_uri.h"
+#include "svn_iter.h"
+#include "private/svn_sorts_private.h"
+
+#include "private/svn_element.h"
+#include "svn_private_config.h"
+
+
+void *
+svn_eid__hash_get(apr_hash_t *ht,
+ int key)
+{
+ return apr_hash_get(ht, &key, sizeof(key));
+}
+
+void
+svn_eid__hash_set(apr_hash_t *ht,
+ int key,
+ const void *val)
+{
+ int *id_p = apr_pmemdup(apr_hash_pool_get(ht), &key, sizeof(key));
+
+ apr_hash_set(ht, id_p, sizeof(key), val);
+}
+
+int
+svn_eid__hash_this_key(apr_hash_index_t *hi)
+{
+ return *(const int *)apr_hash_this_key(hi);
+}
+
+svn_eid__hash_iter_t *
+svn_eid__hash_sorted_first(apr_pool_t *pool,
+ apr_hash_t *ht,
+ int (*comparison_func)(const svn_sort__item_t *,
+ const svn_sort__item_t *))
+{
+ svn_eid__hash_iter_t *hi = apr_palloc(pool, sizeof(*hi));
+
+ if (apr_hash_count(ht) == 0)
+ return NULL;
+
+ hi->array = svn_sort__hash(ht, comparison_func, pool);
+ hi->i = 0;
+ hi->eid = *(const int *)(APR_ARRAY_IDX(hi->array, hi->i,
+ svn_sort__item_t).key);
+ hi->val = APR_ARRAY_IDX(hi->array, hi->i, svn_sort__item_t).value;
+ return hi;
+}
+
+svn_eid__hash_iter_t *
+svn_eid__hash_sorted_next(svn_eid__hash_iter_t *hi)
+{
+ hi->i++;
+ if (hi->i >= hi->array->nelts)
+ {
+ return NULL;
+ }
+ hi->eid = *(const int *)(APR_ARRAY_IDX(hi->array, hi->i,
+ svn_sort__item_t).key);
+ hi->val = APR_ARRAY_IDX(hi->array, hi->i, svn_sort__item_t).value;
+ return hi;
+}
+
+int
+svn_eid__hash_sort_compare_items_by_eid(const svn_sort__item_t *a,
+ const svn_sort__item_t *b)
+{
+ int eid_a = *(const int *)a->key;
+ int eid_b = *(const int *)b->key;
+
+ return eid_a - eid_b;
+}
+
+
+/*
+ * ===================================================================
+ * Element payload
+ * ===================================================================
+ */
+
+svn_boolean_t
+svn_element__payload_invariants(const svn_element__payload_t *payload)
+{
+ if (payload->is_subbranch_root)
+ return TRUE;
+
+ /* If kind is unknown, it's a reference; otherwise it has content
+ specified and may also have a reference. */
+ if (payload->kind == svn_node_unknown)
+ if (SVN_IS_VALID_REVNUM(payload->branch_ref.rev)
+ && payload->branch_ref.branch_id
+ && payload->branch_ref.eid != -1)
+ return TRUE;
+ if ((payload->kind == svn_node_dir
+ || payload->kind == svn_node_file
+ || payload->kind == svn_node_symlink)
+ && (payload->props
+ && ((payload->kind == svn_node_file) == !!payload->text)
+ && ((payload->kind == svn_node_symlink) == !!payload->target)))
+ return TRUE;
+ return FALSE;
+}
+
+svn_element__payload_t *
+svn_element__payload_dup(const svn_element__payload_t *old,
+ apr_pool_t *result_pool)
+{
+ svn_element__payload_t *new_payload;
+
+ assert(! old || svn_element__payload_invariants(old));
+
+ if (old == NULL)
+ return NULL;
+
+ new_payload = apr_pmemdup(result_pool, old, sizeof(*new_payload));
+ if (old->branch_ref.branch_id)
+ new_payload->branch_ref.branch_id
+ = apr_pstrdup(result_pool, old->branch_ref.branch_id);
+ if (old->props)
+ new_payload->props = svn_prop_hash_dup(old->props, result_pool);
+ if (old->kind == svn_node_file && old->text)
+ new_payload->text = svn_stringbuf_dup(old->text, result_pool);
+ if (old->kind == svn_node_symlink && old->target)
+ new_payload->target = apr_pstrdup(result_pool, old->target);
+ return new_payload;
+}
+
+svn_boolean_t
+svn_element__payload_equal(const svn_element__payload_t *left,
+ const svn_element__payload_t *right,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *prop_diffs;
+
+ assert(svn_element__payload_invariants(left));
+ assert(svn_element__payload_invariants(right));
+
+ /* any two subbranch-root elements compare equal */
+ if (left->is_subbranch_root && right->is_subbranch_root)
+ {
+ return TRUE;
+ }
+ else if (left->is_subbranch_root || right->is_subbranch_root)
+ {
+ return FALSE;
+ }
+
+ /* content defined only by reference is not supported */
+ SVN_ERR_ASSERT_NO_RETURN(left->kind != svn_node_unknown
+ && right->kind != svn_node_unknown);
+
+ if (left->kind != right->kind)
+ {
+ return FALSE;
+ }
+
+ svn_error_clear(svn_prop_diffs(&prop_diffs,
+ left->props, right->props,
+ scratch_pool));
+
+ if (prop_diffs->nelts != 0)
+ {
+ return FALSE;
+ }
+ switch (left->kind)
+ {
+ case svn_node_dir:
+ break;
+ case svn_node_file:
+ if (! svn_stringbuf_compare(left->text, right->text))
+ {
+ return FALSE;
+ }
+ break;
+ case svn_node_symlink:
+ if (strcmp(left->target, right->target) != 0)
+ {
+ return FALSE;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+svn_element__payload_t *
+svn_element__payload_create_subbranch(apr_pool_t *result_pool)
+{
+ svn_element__payload_t *new_payload
+ = apr_pcalloc(result_pool, sizeof(*new_payload));
+
+ new_payload->pool = result_pool;
+ new_payload->is_subbranch_root = TRUE;
+ assert(svn_element__payload_invariants(new_payload));
+ return new_payload;
+}
+
+svn_element__payload_t *
+svn_element__payload_create_ref(svn_revnum_t rev,
+ const char *branch_id,
+ int eid,
+ apr_pool_t *result_pool)
+{
+ svn_element__payload_t *new_payload
+ = apr_pcalloc(result_pool, sizeof(*new_payload));
+
+ new_payload->pool = result_pool;
+ new_payload->kind = svn_node_unknown;
+ new_payload->branch_ref.rev = rev;
+ new_payload->branch_ref.branch_id = apr_pstrdup(result_pool, branch_id);
+ new_payload->branch_ref.eid = eid;
+ assert(svn_element__payload_invariants(new_payload));
+ return new_payload;
+}
+
+svn_element__payload_t *
+svn_element__payload_create_dir(apr_hash_t *props,
+ apr_pool_t *result_pool)
+{
+ svn_element__payload_t *new_payload
+ = apr_pcalloc(result_pool, sizeof(*new_payload));
+
+ new_payload->pool = result_pool;
+ new_payload->kind = svn_node_dir;
+ new_payload->props = props ? svn_prop_hash_dup(props, result_pool) : NULL;
+ assert(svn_element__payload_invariants(new_payload));
+ return new_payload;
+}
+
+svn_element__payload_t *
+svn_element__payload_create_file(apr_hash_t *props,
+ svn_stringbuf_t *text,
+ apr_pool_t *result_pool)
+{
+ svn_element__payload_t *new_payload
+ = apr_pcalloc(result_pool, sizeof(*new_payload));
+
+ SVN_ERR_ASSERT_NO_RETURN(text);
+
+ new_payload->pool = result_pool;
+ new_payload->kind = svn_node_file;
+ new_payload->props = props ? svn_prop_hash_dup(props, result_pool) : NULL;
+ new_payload->text = svn_stringbuf_dup(text, result_pool);
+ assert(svn_element__payload_invariants(new_payload));
+ return new_payload;
+}
+
+svn_element__payload_t *
+svn_element__payload_create_symlink(apr_hash_t *props,
+ const char *target,
+ apr_pool_t *result_pool)
+{
+ svn_element__payload_t *new_payload
+ = apr_pcalloc(result_pool, sizeof(*new_payload));
+
+ SVN_ERR_ASSERT_NO_RETURN(target);
+
+ new_payload->pool = result_pool;
+ new_payload->kind = svn_node_symlink;
+ new_payload->props = props ? svn_prop_hash_dup(props, result_pool) : NULL;
+ new_payload->target = apr_pstrdup(result_pool, target);
+ assert(svn_element__payload_invariants(new_payload));
+ return new_payload;
+}
+
+svn_element__content_t *
+svn_element__content_create(int parent_eid,
+ const char *name,
+ const svn_element__payload_t *payload,
+ apr_pool_t *result_pool)
+{
+ svn_element__content_t *content
+ = apr_palloc(result_pool, sizeof(*content));
+
+ content->parent_eid = parent_eid;
+ content->name = apr_pstrdup(result_pool, name);
+ content->payload = svn_element__payload_dup(payload, result_pool);
+ return content;
+}
+
+svn_element__content_t *
+svn_element__content_dup(const svn_element__content_t *old,
+ apr_pool_t *result_pool)
+{
+ svn_element__content_t *content
+ = apr_pmemdup(result_pool, old, sizeof(*content));
+
+ content->name = apr_pstrdup(result_pool, old->name);
+ content->payload = svn_element__payload_dup(old->payload, result_pool);
+ return content;
+}
+
+svn_boolean_t
+svn_element__content_equal(const svn_element__content_t *content_left,
+ const svn_element__content_t *content_right,
+ apr_pool_t *scratch_pool)
+{
+ if (!content_left && !content_right)
+ {
+ return TRUE;
+ }
+ else if (!content_left || !content_right)
+ {
+ return FALSE;
+ }
+
+ if (content_left->parent_eid != content_right->parent_eid)
+ {
+ return FALSE;
+ }
+ if (strcmp(content_left->name, content_right->name) != 0)
+ {
+ return FALSE;
+ }
+ if (! svn_element__payload_equal(content_left->payload, content_right->payload,
+ scratch_pool))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+svn_element__tree_t *
+svn_element__tree_create(apr_hash_t *e_map,
+ int root_eid,
+ apr_pool_t *result_pool)
+{
+ svn_element__tree_t *element_tree
+ = apr_pcalloc(result_pool, sizeof(*element_tree));
+
+ element_tree->e_map = e_map ? apr_hash_copy(result_pool, e_map)
+ : apr_hash_make(result_pool);
+ element_tree->root_eid = root_eid;
+ return element_tree;
+}
+
+svn_element__content_t *
+svn_element__tree_get(const svn_element__tree_t *tree,
+ int eid)
+{
+ return svn_eid__hash_get(tree->e_map, eid);
+}
+
+svn_error_t *
+svn_element__tree_set(svn_element__tree_t *tree,
+ int eid,
+ const svn_element__content_t *element)
+{
+ svn_eid__hash_set(tree->e_map, eid, element);
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_element__tree_purge_orphans(apr_hash_t *e_map,
+ int root_eid,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ svn_boolean_t changed;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_eid__hash_get(e_map, root_eid));
+
+ do
+ {
+ changed = FALSE;
+
+ for (hi = apr_hash_first(scratch_pool, 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);
+
+ if (this_eid != root_eid)
+ {
+ svn_element__content_t *parent_element
+ = svn_eid__hash_get(e_map, this_element->parent_eid);
+
+ /* Purge if parent is deleted */
+ if (! parent_element)
+ {
+ svn_eid__hash_set(e_map, this_eid, NULL);
+ changed = TRUE;
+ }
+ else
+ SVN_ERR_ASSERT_NO_RETURN(
+ ! parent_element->payload->is_subbranch_root);
+ }
+ }
+ }
+ while (changed);
+}
+
+const char *
+svn_element__tree_get_path_by_eid(const svn_element__tree_t *tree,
+ int eid,
+ apr_pool_t *result_pool)
+{
+ const char *path = "";
+ svn_element__content_t *element;
+
+ for (; eid != tree->root_eid; eid = element->parent_eid)
+ {
+ element = svn_element__tree_get(tree, eid);
+ if (! element)
+ return NULL;
+ path = svn_relpath_join(element->name, path, result_pool);
+ }
+ SVN_ERR_ASSERT_NO_RETURN(eid == tree->root_eid);
+ return path;
+}
+
+svn_element__tree_t *
+svn_element__tree_get_subtree_at_eid(svn_element__tree_t *element_tree,
+ int eid,
+ apr_pool_t *result_pool)
+{
+ svn_element__tree_t *new_subtree;
+ svn_element__content_t *subtree_root_element;
+
+ new_subtree = svn_element__tree_create(element_tree->e_map, eid,
+ result_pool);
+
+ /* Purge orphans */
+ svn_element__tree_purge_orphans(new_subtree->e_map,
+ new_subtree->root_eid, result_pool);
+
+ /* Remove 'parent' and 'name' attributes from subtree root element */
+ subtree_root_element
+ = svn_element__tree_get(new_subtree, new_subtree->root_eid);
+ svn_element__tree_set(new_subtree, new_subtree->root_eid,
+ svn_element__content_create(
+ -1, "", subtree_root_element->payload, result_pool));
+
+ return new_subtree;
+}
+
diff --git a/subversion/libsvn_delta/svndiff.c b/subversion/libsvn_delta/svndiff.c
index 070c638a74d6..d95dde42b199 100644
--- a/subversion/libsvn_delta/svndiff.c
+++ b/subversion/libsvn_delta/svndiff.c
@@ -36,6 +36,23 @@
#include "private/svn_string_private.h"
#include "private/svn_dep_compat.h"
+static const char SVNDIFF_V0[] = { 'S', 'V', 'N', 0 };
+static const char SVNDIFF_V1[] = { 'S', 'V', 'N', 1 };
+static const char SVNDIFF_V2[] = { 'S', 'V', 'N', 2 };
+
+#define SVNDIFF_HEADER_SIZE (sizeof(SVNDIFF_V0))
+
+static const char *
+get_svndiff_header(int version)
+{
+ if (version == 2)
+ return SVNDIFF_V2;
+ else if (version == 1)
+ return SVNDIFF_V1;
+ else
+ return SVNDIFF_V0;
+}
+
/* ----- Text delta to svndiff ----- */
/* We make one of these and get it passed back to us in calls to the
@@ -46,7 +63,8 @@ struct encoder_baton {
svn_boolean_t header_done;
int version;
int compression_level;
- apr_pool_t *pool;
+ /* Pool for temporary allocations, will be cleared periodically. */
+ apr_pool_t *scratch_pool;
};
/* This is at least as big as the largest size for a single instruction. */
@@ -72,7 +90,7 @@ static svn_error_t *
send_simple_insertion_window(svn_txdelta_window_t *window,
struct encoder_baton *eb)
{
- unsigned char headers[4 + 5 * SVN__MAX_ENCODED_UINT_LEN
+ unsigned char headers[SVNDIFF_HEADER_SIZE + 5 * SVN__MAX_ENCODED_UINT_LEN
+ MAX_INSTRUCTION_LEN];
unsigned char ibuf[MAX_INSTRUCTION_LEN];
unsigned char *header_current;
@@ -89,11 +107,8 @@ send_simple_insertion_window(svn_txdelta_window_t *window,
if (!eb->header_done)
{
eb->header_done = TRUE;
- headers[0] = 'S';
- headers[1] = 'V';
- headers[2] = 'N';
- headers[3] = (unsigned char)eb->version;
- header_current = headers + 4;
+ memcpy(headers, get_svndiff_header(eb->version), SVNDIFF_HEADER_SIZE);
+ header_current = headers + SVNDIFF_HEADER_SIZE;
}
else
{
@@ -135,58 +150,28 @@ send_simple_insertion_window(svn_txdelta_window_t *window,
return SVN_NO_ERROR;
}
+/* Encodes delta window WINDOW to svndiff-format.
+ The svndiff version is VERSION. COMPRESSION_LEVEL is the
+ compression level to use.
+ Returned values will be allocated in POOL or refer to *WINDOW
+ fields. */
static svn_error_t *
-window_handler(svn_txdelta_window_t *window, void *baton)
+encode_window(svn_stringbuf_t **instructions_p,
+ svn_stringbuf_t **header_p,
+ const svn_string_t **newdata_p,
+ svn_txdelta_window_t *window,
+ int version,
+ int compression_level,
+ apr_pool_t *pool)
{
- struct encoder_baton *eb = baton;
- apr_pool_t *pool;
svn_stringbuf_t *instructions;
- svn_stringbuf_t *i1;
svn_stringbuf_t *header;
const svn_string_t *newdata;
unsigned char ibuf[MAX_INSTRUCTION_LEN], *ip;
const svn_txdelta_op_t *op;
- apr_size_t len;
-
- /* use specialized code if there is no source */
- if (window && !window->src_ops && window->num_ops == 1 && !eb->version)
- return svn_error_trace(send_simple_insertion_window(window, eb));
-
- /* Make sure we write the header. */
- if (!eb->header_done)
- {
- char svnver[4] = {'S','V','N','\0'};
- len = 4;
- svnver[3] = (char)eb->version;
- SVN_ERR(svn_stream_write(eb->output, svnver, &len));
- eb->header_done = TRUE;
- }
-
- if (window == NULL)
- {
- svn_stream_t *output = eb->output;
-
- /* We're done; clean up.
-
- We clean our pool first. Given that the output stream was passed
- TO us, we'll assume it has a longer lifetime, and that it will not
- be affected by our pool destruction.
-
- The contrary point of view (close the stream first): that could
- tell our user that everything related to the output stream is done,
- and a cleanup of the user pool should occur. However, that user
- pool could include the subpool we created for our work (eb->pool),
- which would then make our call to svn_pool_destroy() puke.
- */
- svn_pool_destroy(eb->pool);
-
- return svn_stream_close(output);
- }
/* create the necessary data buffers */
- pool = svn_pool_create(eb->pool);
instructions = svn_stringbuf_create_empty(pool);
- i1 = svn_stringbuf_create_empty(pool);
header = svn_stringbuf_create_empty(pool);
/* Encode the instructions. */
@@ -213,21 +198,39 @@ window_handler(svn_txdelta_window_t *window, void *baton)
append_encoded_int(header, window->sview_offset);
append_encoded_int(header, window->sview_len);
append_encoded_int(header, window->tview_len);
- if (eb->version == 1)
+ if (version == 2)
+ {
+ svn_stringbuf_t *compressed_instructions;
+ compressed_instructions = svn_stringbuf_create_empty(pool);
+ SVN_ERR(svn__compress_lz4(instructions->data, instructions->len,
+ compressed_instructions));
+ instructions = compressed_instructions;
+ }
+ else if (version == 1)
{
- SVN_ERR(svn__compress(instructions, i1, eb->compression_level));
- instructions = i1;
+ svn_stringbuf_t *compressed_instructions;
+ compressed_instructions = svn_stringbuf_create_empty(pool);
+ SVN_ERR(svn__compress_zlib(instructions->data, instructions->len,
+ compressed_instructions, compression_level));
+ instructions = compressed_instructions;
}
append_encoded_int(header, instructions->len);
- if (eb->version == 1)
+
+ /* Encode the data. */
+ if (version == 2)
{
svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool);
- svn_stringbuf_t *original = svn_stringbuf_create_empty(pool);
- original->data = (char *)window->new_data->data; /* won't be modified */
- original->len = window->new_data->len;
- original->blocksize = window->new_data->len + 1;
- SVN_ERR(svn__compress(original, compressed, eb->compression_level));
+ SVN_ERR(svn__compress_lz4(window->new_data->data, window->new_data->len,
+ compressed));
+ newdata = svn_stringbuf__morph_into_string(compressed);
+ }
+ else if (version == 1)
+ {
+ svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool);
+
+ SVN_ERR(svn__compress_zlib(window->new_data->data, window->new_data->len,
+ compressed, compression_level));
newdata = svn_stringbuf__morph_into_string(compressed);
}
else
@@ -235,6 +238,53 @@ window_handler(svn_txdelta_window_t *window, void *baton)
append_encoded_int(header, newdata->len);
+ *instructions_p = instructions;
+ *header_p = header;
+ *newdata_p = newdata;
+
+ return SVN_NO_ERROR;
+}
+
+/* Note: When changing things here, check the related comment in
+ the svn_txdelta_to_svndiff_stream() function. */
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct encoder_baton *eb = baton;
+ apr_size_t len;
+ svn_stringbuf_t *instructions;
+ svn_stringbuf_t *header;
+ const svn_string_t *newdata;
+
+ /* use specialized code if there is no source */
+ if (window && !window->src_ops && window->num_ops == 1 && !eb->version)
+ return svn_error_trace(send_simple_insertion_window(window, eb));
+
+ /* Make sure we write the header. */
+ if (!eb->header_done)
+ {
+ len = SVNDIFF_HEADER_SIZE;
+ SVN_ERR(svn_stream_write(eb->output, get_svndiff_header(eb->version),
+ &len));
+ eb->header_done = TRUE;
+ }
+
+ if (window == NULL)
+ {
+ /* We're done; clean up. */
+ SVN_ERR(svn_stream_close(eb->output));
+
+ svn_pool_destroy(eb->scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ svn_pool_clear(eb->scratch_pool);
+
+ SVN_ERR(encode_window(&instructions, &header, &newdata, window,
+ eb->version, eb->compression_level,
+ eb->scratch_pool));
+
/* Write out the window. */
len = header->len;
SVN_ERR(svn_stream_write(eb->output, header->data, &len));
@@ -249,7 +299,6 @@ window_handler(svn_txdelta_window_t *window, void *baton)
SVN_ERR(svn_stream_write(eb->output, newdata->data, &len));
}
- svn_pool_destroy(pool);
return SVN_NO_ERROR;
}
@@ -261,13 +310,12 @@ svn_txdelta_to_svndiff3(svn_txdelta_window_handler_t *handler,
int compression_level,
apr_pool_t *pool)
{
- apr_pool_t *subpool = svn_pool_create(pool);
struct encoder_baton *eb;
- eb = apr_palloc(subpool, sizeof(*eb));
+ eb = apr_palloc(pool, sizeof(*eb));
eb->output = output;
eb->header_done = FALSE;
- eb->pool = subpool;
+ eb->scratch_pool = svn_pool_create(pool);
eb->version = svndiff_version;
eb->compression_level = compression_level;
@@ -334,6 +382,17 @@ struct decode_baton
/* svndiff version in use by delta. */
unsigned char version;
+
+ /* Length of parsed delta window header. 0 if window is not parsed yet. */
+ apr_size_t window_header_len;
+
+ /* Five integer fields of parsed delta window header. Valid only if
+ WINDOW_HEADER_LEN > 0 */
+ svn_filesize_t sview_offset;
+ apr_size_t sview_len;
+ apr_size_t tview_len;
+ apr_size_t inslen;
+ apr_size_t newlen;
};
@@ -483,21 +542,6 @@ count_and_verify_instructions(int *ninst,
return SVN_NO_ERROR;
}
-static svn_error_t *
-zlib_decode(const unsigned char *in, apr_size_t inLen, svn_stringbuf_t *out,
- apr_size_t limit)
-{
- /* construct a fake string buffer as parameter to svn__decompress.
- This is fine as that function never writes to it. */
- svn_stringbuf_t compressed;
- compressed.pool = NULL;
- compressed.data = (char *)in;
- compressed.len = inLen;
- compressed.blocksize = inLen + 1;
-
- return svn__decompress(&compressed, out, limit);
-}
-
/* Given the five integer fields of a window header and a pointer to
the remainder of the window contents, fill in a delta window
structure *WINDOW. New allocations will be performed in POOL;
@@ -513,7 +557,7 @@ decode_window(svn_txdelta_window_t *window, svn_filesize_t sview_offset,
int ninst;
apr_size_t npos;
svn_txdelta_op_t *ops, *op;
- svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data));
+ svn_string_t *new_data;
window->sview_offset = sview_offset;
window->sview_len = sview_len;
@@ -521,33 +565,43 @@ decode_window(svn_txdelta_window_t *window, svn_filesize_t sview_offset,
insend = data + inslen;
- if (version == 1)
+ if (version == 2)
{
svn_stringbuf_t *instout = svn_stringbuf_create_empty(pool);
svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool);
- SVN_ERR(zlib_decode(insend, newlen, ndout,
- SVN_DELTA_WINDOW_SIZE));
- SVN_ERR(zlib_decode(data, insend - data, instout,
- MAX_INSTRUCTION_SECTION_LEN));
+ SVN_ERR(svn__decompress_lz4(insend, newlen, ndout,
+ SVN_DELTA_WINDOW_SIZE));
+ SVN_ERR(svn__decompress_lz4(data, insend - data, instout,
+ MAX_INSTRUCTION_SECTION_LEN));
newlen = ndout->len;
data = (unsigned char *)instout->data;
insend = (unsigned char *)instout->data + instout->len;
- new_data->data = (const char *) ndout->data;
- new_data->len = newlen;
+ new_data = svn_stringbuf__morph_into_string(ndout);
+ }
+ else if (version == 1)
+ {
+ svn_stringbuf_t *instout = svn_stringbuf_create_empty(pool);
+ svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool);
+
+ SVN_ERR(svn__decompress_zlib(insend, newlen, ndout,
+ SVN_DELTA_WINDOW_SIZE));
+ SVN_ERR(svn__decompress_zlib(data, insend - data, instout,
+ MAX_INSTRUCTION_SECTION_LEN));
+
+ newlen = ndout->len;
+ data = (unsigned char *)instout->data;
+ insend = (unsigned char *)instout->data + instout->len;
+
+ new_data = svn_stringbuf__morph_into_string(ndout);
}
else
{
/* Copy the data because an svn_string_t must have the invariant
data[len]=='\0'. */
- char *buf = apr_palloc(pool, newlen + 1);
-
- memcpy(buf, insend, newlen);
- buf[newlen] = '\0';
- new_data->data = buf;
- new_data->len = newlen;
+ new_data = svn_string_ncreate((const char*)insend, newlen, pool);
}
/* Count the instructions and make sure they are all valid. */
@@ -578,10 +632,6 @@ decode_window(svn_txdelta_window_t *window, svn_filesize_t sview_offset,
return SVN_NO_ERROR;
}
-static const char SVNDIFF_V0[] = { 'S', 'V', 'N', 0 };
-static const char SVNDIFF_V1[] = { 'S', 'V', 'N', 1 };
-#define SVNDIFF_HEADER_SIZE (sizeof(SVNDIFF_V0))
-
static svn_error_t *
write_handler(void *baton,
const char *buffer,
@@ -589,8 +639,6 @@ write_handler(void *baton,
{
struct decode_baton *db = (struct decode_baton *) baton;
const unsigned char *p, *end;
- svn_filesize_t sview_offset;
- apr_size_t sview_len, tview_len, inslen, newlen, remaining;
apr_size_t buflen = *len;
/* Chew up four bytes at the beginning for the header. */
@@ -603,6 +651,8 @@ write_handler(void *baton,
db->version = 0;
else if (memcmp(buffer, SVNDIFF_V1 + db->header_bytes, nheader) == 0)
db->version = 1;
+ else if (memcmp(buffer, SVNDIFF_V2 + db->header_bytes, nheader) == 0)
+ db->version = 2;
else
return svn_error_create(SVN_ERR_SVNDIFF_INVALID_HEADER, NULL,
_("Svndiff has invalid header"));
@@ -628,92 +678,108 @@ write_handler(void *baton,
while (1)
{
- apr_pool_t *newpool;
svn_txdelta_window_t window;
/* Read the header, if we have enough bytes for that. */
p = (const unsigned char *) db->buffer->data;
end = (const unsigned char *) db->buffer->data + db->buffer->len;
- p = decode_file_offset(&sview_offset, p, end);
- if (p == NULL)
- break;
-
- p = decode_size(&sview_len, p, end);
- if (p == NULL)
- break;
-
- p = decode_size(&tview_len, p, end);
- if (p == NULL)
- break;
-
- p = decode_size(&inslen, p, end);
- if (p == NULL)
- break;
-
- p = decode_size(&newlen, p, end);
- if (p == NULL)
- break;
-
- if (tview_len > SVN_DELTA_WINDOW_SIZE ||
- sview_len > SVN_DELTA_WINDOW_SIZE ||
- /* for svndiff1, newlen includes the original length */
- newlen > SVN_DELTA_WINDOW_SIZE + SVN__MAX_ENCODED_UINT_LEN ||
- inslen > MAX_INSTRUCTION_SECTION_LEN)
- return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
- _("Svndiff contains a too-large window"));
-
- /* Check for integer overflow. */
- if (sview_offset < 0 || inslen + newlen < inslen
- || sview_len + tview_len < sview_len
- || (apr_size_t)sview_offset + sview_len < (apr_size_t)sview_offset)
- return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
- _("Svndiff contains corrupt window header"));
-
- /* Check for source windows which slide backwards. */
- if (sview_len > 0
- && (sview_offset < db->last_sview_offset
- || (sview_offset + sview_len
- < db->last_sview_offset + db->last_sview_len)))
- return svn_error_create
- (SVN_ERR_SVNDIFF_BACKWARD_VIEW, NULL,
- _("Svndiff has backwards-sliding source views"));
+ if (db->window_header_len == 0)
+ {
+ svn_filesize_t sview_offset;
+ apr_size_t sview_len, tview_len, inslen, newlen;
+ const unsigned char *hdr_start = p;
+
+ p = decode_file_offset(&sview_offset, p, end);
+ if (p == NULL)
+ break;
+
+ p = decode_size(&sview_len, p, end);
+ if (p == NULL)
+ break;
+
+ p = decode_size(&tview_len, p, end);
+ if (p == NULL)
+ break;
+
+ p = decode_size(&inslen, p, end);
+ if (p == NULL)
+ break;
+
+ p = decode_size(&newlen, p, end);
+ if (p == NULL)
+ break;
+
+ if (tview_len > SVN_DELTA_WINDOW_SIZE ||
+ sview_len > SVN_DELTA_WINDOW_SIZE ||
+ /* for svndiff1, newlen includes the original length */
+ newlen > SVN_DELTA_WINDOW_SIZE + SVN__MAX_ENCODED_UINT_LEN ||
+ inslen > MAX_INSTRUCTION_SECTION_LEN)
+ return svn_error_create(
+ SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
+ _("Svndiff contains a too-large window"));
+
+ /* Check for integer overflow. */
+ if (sview_offset < 0 || inslen + newlen < inslen
+ || sview_len + tview_len < sview_len
+ || (apr_size_t)sview_offset + sview_len < (apr_size_t)sview_offset)
+ return svn_error_create(
+ SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
+ _("Svndiff contains corrupt window header"));
+
+ /* Check for source windows which slide backwards. */
+ if (sview_len > 0
+ && (sview_offset < db->last_sview_offset
+ || (sview_offset + sview_len
+ < db->last_sview_offset + db->last_sview_len)))
+ return svn_error_create(
+ SVN_ERR_SVNDIFF_BACKWARD_VIEW, NULL,
+ _("Svndiff has backwards-sliding source views"));
+
+ /* Remember parsed window header. */
+ db->window_header_len = p - hdr_start;
+ db->sview_offset = sview_offset;
+ db->sview_len = sview_len;
+ db->tview_len = tview_len;
+ db->inslen = inslen;
+ db->newlen = newlen;
+ }
+ else
+ {
+ /* Skip already parsed window header. */
+ p += db->window_header_len;
+ }
/* Wait for more data if we don't have enough bytes for the
- whole window. */
- if ((apr_size_t) (end - p) < inslen + newlen)
+ whole window. */
+ if ((apr_size_t) (end - p) < db->inslen + db->newlen)
return SVN_NO_ERROR;
/* Decode the window and send it off. */
- SVN_ERR(decode_window(&window, sview_offset, sview_len, tview_len,
- inslen, newlen, p, db->subpool,
- db->version));
+ SVN_ERR(decode_window(&window, db->sview_offset, db->sview_len,
+ db->tview_len, db->inslen, db->newlen, p,
+ db->subpool, db->version));
SVN_ERR(db->consumer_func(&window, db->consumer_baton));
- /* Make a new subpool and buffer, saving aside the remaining
- data in the old buffer. */
- newpool = svn_pool_create(db->pool);
- p += inslen + newlen;
- remaining = db->buffer->data + db->buffer->len - (const char *) p;
- db->buffer =
- svn_stringbuf_ncreate((const char *) p, remaining, newpool);
+ p += db->inslen + db->newlen;
+
+ /* Remove processed data from the buffer. */
+ svn_stringbuf_remove(db->buffer, 0, db->buffer->len - (end - p));
+
+ /* Reset window header length. */
+ db->window_header_len = 0;
/* Remember the offset and length of the source view for next time. */
- db->last_sview_offset = sview_offset;
- db->last_sview_len = sview_len;
-
- /* We've copied stuff out of the old pool. Toss that pool and use
- our new pool.
- ### might be nice to avoid the copy and just use svn_pool_clear
- ### to get rid of whatever the "other stuff" is. future project...
- */
- svn_pool_destroy(db->subpool);
- db->subpool = newpool;
+ db->last_sview_offset = db->sview_offset;
+ db->last_sview_len = db->sview_len;
+
+ /* Clear subpool. */
+ svn_pool_clear(db->subpool);
}
/* At this point we processed all integral windows and DB->BUFFER is empty
or contains partially read window header.
- Check that unprocessed data is not larger that theoretical maximum
+ Check that unprocessed data is not larger than theoretical maximum
window header size. */
if (db->buffer->len > 5 * SVN__MAX_ENCODED_UINT_LEN)
return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
@@ -757,23 +823,25 @@ svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler,
svn_boolean_t error_on_early_close,
apr_pool_t *pool)
{
- apr_pool_t *subpool = svn_pool_create(pool);
- struct decode_baton *db = apr_palloc(pool, sizeof(*db));
svn_stream_t *stream;
- db->consumer_func = handler;
- db->consumer_baton = handler_baton;
- db->pool = subpool;
- db->subpool = svn_pool_create(subpool);
- db->buffer = svn_stringbuf_create_empty(db->subpool);
- db->last_sview_offset = 0;
- db->last_sview_len = 0;
- db->header_bytes = 0;
- db->error_on_early_close = error_on_early_close;
- stream = svn_stream_create(db, pool);
-
if (handler != svn_delta_noop_window_handler)
{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ struct decode_baton *db = apr_palloc(pool, sizeof(*db));
+
+ db->consumer_func = handler;
+ db->consumer_baton = handler_baton;
+ db->pool = subpool;
+ db->subpool = svn_pool_create(subpool);
+ db->buffer = svn_stringbuf_create_empty(db->pool);
+ db->last_sview_offset = 0;
+ db->last_sview_len = 0;
+ db->header_bytes = 0;
+ db->error_on_early_close = error_on_early_close;
+ db->window_header_len = 0;
+ stream = svn_stream_create(db, pool);
+
svn_stream_set_write(stream, write_handler);
svn_stream_set_close(stream, close_handler);
}
@@ -781,6 +849,7 @@ svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler,
{
/* And else we just ignore everything as efficiently as we can.
by only hooking a no-op handler */
+ stream = svn_stream_create(NULL, pool);
svn_stream_set_write(stream, noop_write_handler);
}
return stream;
@@ -926,3 +995,107 @@ svn_txdelta__read_raw_window_len(apr_size_t *window_len,
return SVN_NO_ERROR;
}
+typedef struct svndiff_stream_baton_t
+{
+ apr_pool_t *scratch_pool;
+ svn_txdelta_stream_t *txstream;
+ svn_txdelta_window_handler_t handler;
+ void *handler_baton;
+ svn_stringbuf_t *window_buffer;
+ apr_size_t read_pos;
+ svn_boolean_t hit_eof;
+} svndiff_stream_baton_t;
+
+static svn_error_t *
+svndiff_stream_write_fn(void *baton, const char *data, apr_size_t *len)
+{
+ svndiff_stream_baton_t *b = baton;
+
+ /* The memory usage here is limited, as this buffer doesn't grow
+ beyond the (header size + max window size in svndiff format).
+ See the comment in svn_txdelta_to_svndiff_stream(). */
+ svn_stringbuf_appendbytes(b->window_buffer, data, *len);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svndiff_stream_read_fn(void *baton, char *buffer, apr_size_t *len)
+{
+ svndiff_stream_baton_t *b = baton;
+ apr_size_t left = *len;
+ apr_size_t read = 0;
+
+ while (left)
+ {
+ apr_size_t chunk_size;
+
+ if (b->read_pos == b->window_buffer->len && !b->hit_eof)
+ {
+ svn_txdelta_window_t *window;
+
+ svn_pool_clear(b->scratch_pool);
+ svn_stringbuf_setempty(b->window_buffer);
+ SVN_ERR(svn_txdelta_next_window(&window, b->txstream,
+ b->scratch_pool));
+ SVN_ERR(b->handler(window, b->handler_baton));
+ b->read_pos = 0;
+
+ if (!window)
+ b->hit_eof = TRUE;
+ }
+
+ if (left > b->window_buffer->len - b->read_pos)
+ chunk_size = b->window_buffer->len - b->read_pos;
+ else
+ chunk_size = left;
+
+ if (!chunk_size)
+ break;
+
+ memcpy(buffer, b->window_buffer->data + b->read_pos, chunk_size);
+ b->read_pos += chunk_size;
+ buffer += chunk_size;
+ read += chunk_size;
+ left -= chunk_size;
+ }
+
+ *len = read;
+ return SVN_NO_ERROR;
+}
+
+svn_stream_t *
+svn_txdelta_to_svndiff_stream(svn_txdelta_stream_t *txstream,
+ int svndiff_version,
+ int compression_level,
+ apr_pool_t *pool)
+{
+ svndiff_stream_baton_t *baton;
+ svn_stream_t *push_stream;
+ svn_stream_t *pull_stream;
+
+ baton = apr_pcalloc(pool, sizeof(*baton));
+ baton->scratch_pool = svn_pool_create(pool);
+ baton->txstream = txstream;
+ baton->window_buffer = svn_stringbuf_create_empty(pool);
+ baton->hit_eof = FALSE;
+ baton->read_pos = 0;
+
+ push_stream = svn_stream_create(baton, pool);
+ svn_stream_set_write(push_stream, svndiff_stream_write_fn);
+
+ /* We rely on the implementation detail of the svn_txdelta_to_svndiff3()
+ function, namely, on how the window_handler() function behaves.
+ As long as it writes one svndiff window at a time to the target
+ stream, the memory usage of this function (in other words, how
+ much data can be accumulated in the internal 'window_buffer')
+ is limited. */
+ svn_txdelta_to_svndiff3(&baton->handler, &baton->handler_baton,
+ push_stream, svndiff_version,
+ compression_level, pool);
+
+ pull_stream = svn_stream_create(baton, pool);
+ svn_stream_set_read2(pull_stream, NULL, svndiff_stream_read_fn);
+
+ return pull_stream;
+}
diff --git a/subversion/libsvn_delta/text_delta.c b/subversion/libsvn_delta/text_delta.c
index 04eca8afb8c1..4f834988bf38 100644
--- a/subversion/libsvn_delta/text_delta.c
+++ b/subversion/libsvn_delta/text_delta.c
@@ -102,7 +102,7 @@ struct apply_baton {
char *tbuf; /* Target buffer */
apr_size_t tbuf_size; /* Allocated target buffer space */
- apr_md5_ctx_t md5_context; /* Leads to result_digest below. */
+ svn_checksum_ctx_t *md5_context; /* Leads to result_digest below. */
unsigned char *result_digest; /* MD5 digest of resultant fulltext;
must point to at least APR_MD5_DIGESTSIZE
bytes of storage. */
@@ -180,8 +180,7 @@ svn_txdelta_window_dup(const svn_txdelta_window_t *window,
build_baton.num_ops = window->num_ops;
build_baton.src_ops = window->src_ops;
build_baton.ops_size = window->num_ops;
- build_baton.ops = apr_palloc(pool, ops_size);
- memcpy(build_baton.ops, window->ops, ops_size);
+ build_baton.ops = apr_pmemdup(pool, window->ops, ops_size);
build_baton.new_data =
svn_stringbuf_create_from_string(window->new_data, pool);
@@ -721,15 +720,23 @@ apply_window(svn_txdelta_window_t *window, void *baton)
{
struct apply_baton *ab = (struct apply_baton *) baton;
apr_size_t len;
- svn_error_t *err;
if (window == NULL)
{
+ svn_error_t *err = SVN_NO_ERROR;
+
/* We're done; just clean up. */
if (ab->result_digest)
- apr_md5_final(ab->result_digest, &(ab->md5_context));
+ {
+ svn_checksum_t *md5_checksum;
+
+ err = svn_checksum_final(&md5_checksum, ab->md5_context, ab->pool);
+ if (!err)
+ memcpy(ab->result_digest, md5_checksum->digest,
+ svn_checksum_size(md5_checksum));
+ }
- err = svn_stream_close(ab->target);
+ err = svn_error_compose_create(err, svn_stream_close(ab->target));
svn_pool_destroy(ab->pool);
return err;
@@ -773,12 +780,10 @@ apply_window(svn_txdelta_window_t *window, void *baton)
if (ab->sbuf_len < window->sview_len)
{
len = window->sview_len - ab->sbuf_len;
- err = svn_stream_read_full(ab->source, ab->sbuf + ab->sbuf_len, &len);
- if (err == SVN_NO_ERROR && len != window->sview_len - ab->sbuf_len)
- err = svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
- "Delta source ended unexpectedly");
- if (err != SVN_NO_ERROR)
- return err;
+ SVN_ERR(svn_stream_read_full(ab->source, ab->sbuf + ab->sbuf_len, &len));
+ if (len != window->sview_len - ab->sbuf_len)
+ return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
+ "Delta source ended unexpectedly");
ab->sbuf_len = window->sview_len;
}
@@ -792,7 +797,7 @@ apply_window(svn_txdelta_window_t *window, void *baton)
/* Just update the context here. */
if (ab->result_digest)
- apr_md5_update(&(ab->md5_context), ab->tbuf, len);
+ SVN_ERR(svn_checksum_update(ab->md5_context, ab->tbuf, len));
return svn_stream_write(ab->target, ab->tbuf, &len);
}
@@ -823,7 +828,7 @@ svn_txdelta_apply(svn_stream_t *source,
ab->result_digest = result_digest;
if (result_digest)
- apr_md5_init(&(ab->md5_context));
+ ab->md5_context = svn_checksum_ctx_create(svn_checksum_md5, subpool);
if (error_info)
ab->error_info = apr_pstrdup(subpool, error_info);
diff --git a/subversion/libsvn_delta/xdelta.c b/subversion/libsvn_delta/xdelta.c
index 2e5bb266acfb..ff95f77f095f 100644
--- a/subversion/libsvn_delta/xdelta.c
+++ b/subversion/libsvn_delta/xdelta.c
@@ -111,7 +111,7 @@ struct block
apr_uint32_t adlersum;
/* Even in 64 bit systems, store only 32 bit offsets in our hash table
- (our delta window size much much smaller then 4GB).
+ (our delta window size much much smaller than 4GB).
That reduces the hash table size by 50% from 32to 16KB
and makes it easier to fit into the CPU's L1 cache. */
apr_uint32_t pos; /* NO_POSITION -> block is not used */