/* * 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 ""; 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 ""; 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 = ""; 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) : "", 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; }