diff options
author | Peter Wemm <peter@FreeBSD.org> | 2018-05-08 03:44:38 +0000 |
---|---|---|
committer | Peter Wemm <peter@FreeBSD.org> | 2018-05-08 03:44:38 +0000 |
commit | 3faf8d6bffc5d0fb2525ba37bb504c53366caf9d (patch) | |
tree | 7e47911263e75034b767fe34b2f8d3d17e91f66d /subversion/libsvn_delta | |
parent | a55fb3c0d5eca7d887798125d5b95942b1f01d4b (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.c | 1699 | ||||
-rw-r--r-- | subversion/libsvn_delta/branch_compat.c | 2070 | ||||
-rw-r--r-- | subversion/libsvn_delta/branch_migrate.c | 366 | ||||
-rw-r--r-- | subversion/libsvn_delta/branch_nested.c | 660 | ||||
-rw-r--r-- | subversion/libsvn_delta/branch_repos.c | 132 | ||||
-rw-r--r-- | subversion/libsvn_delta/cancel.c | 21 | ||||
-rw-r--r-- | subversion/libsvn_delta/compat.c | 30 | ||||
-rw-r--r-- | subversion/libsvn_delta/debug_editor.c | 2 | ||||
-rw-r--r-- | subversion/libsvn_delta/debug_editor.h | 54 | ||||
-rw-r--r-- | subversion/libsvn_delta/default_editor.c | 30 | ||||
-rw-r--r-- | subversion/libsvn_delta/element.c | 471 | ||||
-rw-r--r-- | subversion/libsvn_delta/svndiff.c | 527 | ||||
-rw-r--r-- | subversion/libsvn_delta/text_delta.c | 33 | ||||
-rw-r--r-- | subversion/libsvn_delta/xdelta.c | 2 |
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(¤t_payload, ¤t_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(¤t_payload, ¤t_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, ¤t, + 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 */ |