| /* |
| * 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_ERR(svn_sort__array_delete2(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; |
| } |
| |
| /* |
| * ======================================================================== |
| */ |
| |