| /* |
| * 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; |
| } |
| |