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/branch_compat.c | |
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/branch_compat.c')
-rw-r--r-- | subversion/libsvn_delta/branch_compat.c | 2070 |
1 files changed, 2070 insertions, 0 deletions
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; +} |