| /* |
| * 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, |
| const svn_delta_editor_t *editor, |
| void *edit_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, |
| editor, *dir_baton, scratch_pool)); |
| |
| /* No further action possible for the root. */ |
| return SVN_NO_ERROR; |
| } |
| |
| if (change->action == RESTRUCTURE_DELETE) |
| { |
| SVN_ERR(editor->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(editor->absent_directory(ev1_relpath, parent_baton, |
| scratch_pool)); |
| else if (change->kind == svn_node_file) |
| SVN_ERR(editor->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(editor->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(editor->add_directory(ev1_relpath, parent_baton, |
| copyfrom_url, copyfrom_rev, |
| result_pool, dir_baton)); |
| else if (change->kind == svn_node_file) |
| SVN_ERR(editor->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(editor->open_directory(ev1_relpath, parent_baton, |
| change->changing_rev, |
| result_pool, dir_baton)); |
| else if (change->kind == svn_node_file) |
| SVN_ERR(editor->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, |
| editor, *dir_baton, scratch_pool)); |
| else |
| SVN_ERR(drive_ev1_props(ev1_relpath, change, base_props, |
| editor, 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(editor->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(editor->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_driver3(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; |
| } |