2 * branch_compat.c : Branching compatibility layer.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 #include "svn_types.h"
25 #include "svn_error.h"
26 #include "svn_delta.h"
27 #include "svn_dirent_uri.h"
31 #include "svn_props.h"
32 #include "svn_pools.h"
34 #include "private/svn_branch_impl.h"
35 #include "private/svn_branch_repos.h"
36 #include "private/svn_branch_nested.h"
37 #include "private/svn_delta_private.h"
38 #include "private/svn_branch_compat.h"
40 #include "svn_private_config.h"
43 /* Verify EXPR is true; raise an error if not. */
44 #define VERIFY(expr) SVN_ERR_ASSERT(expr)
48 * ===================================================================
50 * ===================================================================
53 /** A location in a committed revision.
55 * @a rev shall not be #SVN_INVALID_REVNUM unless the interface using this
56 * type specifically allows it and defines its meaning. */
57 typedef struct svn_pathrev_t
63 /* Return true iff PEG_PATH1 and PEG_PATH2 are both the same location.
66 pathrev_equal(const svn_pathrev_t *p1,
67 const svn_pathrev_t *p2)
69 if (p1->rev != p2->rev)
71 if (strcmp(p1->relpath, p2->relpath) != 0)
78 /* Return a human-readable string representation of LOC. */
80 pathrev_str(const svn_pathrev_t *loc,
81 apr_pool_t *result_pool)
85 return apr_psprintf(result_pool, "%s@%ld",
86 loc->relpath, loc->rev);
89 /* Return a string representation of the (string) keys of HASH. */
91 hash_keys_str(apr_hash_t *hash)
93 const char *str = NULL;
100 pool = apr_hash_pool_get(hash);
101 for (hi = apr_hash_first(pool, hash); hi; hi = apr_hash_next(hi))
103 const char *key = apr_hash_this_key(hi);
108 str = apr_psprintf(pool, "%s, %s", str, key);
110 return apr_psprintf(pool, "{%s}", str);
115 * Merge two hash tables into one new hash table. The values of the overlay
116 * hash override the values of the base if both have the same key.
118 * Unlike apr_hash_overlay(), this doesn't care whether the input hashes use
119 * the same hash function, nor about the relationship between the three pools.
121 * @param p The pool to use for the new hash table
122 * @param overlay The table to add to the initial table
123 * @param base The table that represents the initial values of the new table
124 * @return A new hash table containing all of the data from the two passed in
125 * @remark Makes a shallow copy: keys and values are not copied
128 hash_overlay(apr_hash_t *overlay,
131 apr_pool_t *pool = apr_hash_pool_get(base);
132 apr_hash_t *result = apr_hash_copy(pool, base);
133 apr_hash_index_t *hi;
135 for (hi = apr_hash_first(pool, overlay); hi; hi = apr_hash_next(hi))
137 svn_hash_sets(result, apr_hash_this_key(hi), apr_hash_this_val(hi));
144 * ========================================================================
145 * Configuration Options
146 * ========================================================================
149 /* Features that are not wanted for this commit editor shim but may be
150 * wanted in a similar but different shim such as for an update editor. */
151 /* #define SHIM_WITH_ADD_ABSENT */
152 /* #define SHIM_WITH_UNLOCK */
154 /* Whether to support switching from relative to absolute paths in the
156 /* #define SHIM_WITH_ABS_PATHS */
160 * ========================================================================
162 * ========================================================================
164 * The shim connector enables a more exact round-trip conversion from an
165 * Ev1 drive to Ev3 and back to Ev1.
167 struct svn_branch__compat_shim_connector_t
169 /* Set to true if and when an Ev1 receiving shim receives an absolute
170 * path (prefixed with '/') from the delta edit, and causes the Ev1
171 * sending shim to send absolute paths.
172 * ### NOT IMPLEMENTED
174 #ifdef SHIM_WITH_ABS_PATHS
175 svn_boolean_t *ev1_absolute_paths;
178 /* The Ev1 set_target_revision and start_edit methods, respectively, will
179 * call the TARGET_REVISION_FUNC and START_EDIT_FUNC callbacks, if non-null.
180 * Otherwise, default calls will be used.
182 * (Possibly more useful for update editors than for commit editors?) */
183 svn_branch__compat_set_target_revision_func_t target_revision_func;
185 /* If not null, a callback that the Ev3 driver may call to
186 * provide the "base revision" of the root directory, even if it is not
187 * going to modify that directory. (If it does modify it, then it will
188 * pass in the appropriate base revision at that time.) If null
189 * or if the driver does not call it, then the Ev1
190 * open_root() method will be called with SVN_INVALID_REVNUM as the base
191 * revision parameter.
193 svn_delta__start_edit_func_t start_edit_func;
195 #ifdef SHIM_WITH_UNLOCK
196 /* A callback which will be called when an unlocking action is received.
197 (For update editors?) */
198 svn_delta__unlock_func_t unlock_func;
205 svn_branch__compat_insert_shims(
206 const svn_delta_editor_t **new_deditor,
207 void **new_dedit_baton,
208 const svn_delta_editor_t *old_deditor,
209 void *old_dedit_baton,
210 const char *repos_root,
211 const char *base_relpath,
212 svn_branch__compat_fetch_func_t fetch_func,
214 apr_pool_t *result_pool,
215 apr_pool_t *scratch_pool)
218 svn_branch__txn_t *edit_txn;
219 svn_branch__compat_shim_connector_t *shim_connector;
222 /*SVN_ERR(svn_delta__get_debug_editor(&old_deditor, &old_dedit_baton,
223 old_deditor, old_dedit_baton,
224 "[OUT] ", result_pool));*/
226 SVN_ERR(svn_branch__compat_txn_from_delta_for_commit(
229 old_deditor, old_dedit_baton,
232 fetch_func, fetch_baton,
233 NULL, NULL /*cancel*/,
234 result_pool, scratch_pool));
235 SVN_ERR(svn_branch__compat_delta_from_txn_for_commit(
236 new_deditor, new_dedit_baton,
238 repos_root, base_relpath,
239 fetch_func, fetch_baton,
241 result_pool, scratch_pool));
243 /*SVN_ERR(svn_delta__get_debug_editor(new_deditor, new_dedit_baton,
244 *new_deditor, *new_dedit_baton,
245 "[IN] ", result_pool));*/
248 *new_deditor = old_deditor;
249 *new_dedit_baton = old_dedit_baton;
256 * ========================================================================
257 * Buffering the Delta Editor Changes
258 * ========================================================================
261 /* The kind of Ev1 restructuring operation on a particular path. For each
262 * visited path we use exactly one restructuring action. */
263 enum restructure_action_t
265 RESTRUCTURE_NONE = 0,
266 RESTRUCTURE_ADD, /* add the node, maybe replacing. maybe copy */
267 #ifdef SHIM_WITH_ADD_ABSENT
268 RESTRUCTURE_ADD_ABSENT, /* add an absent node, possibly replacing */
270 RESTRUCTURE_DELETE /* delete this node */
273 /* Records everything about how this node is to be changed, from an Ev1
275 typedef struct change_node_t
277 /* what kind of (tree) restructure is occurring at this node? */
278 enum restructure_action_t action;
280 svn_node_kind_t kind; /* the NEW kind of this node */
282 /* We may need to specify the revision we are altering or the revision
283 to delete or replace. These are mutually exclusive, but are separate
285 /* CHANGING_REV is the base revision of the change if ACTION is 'none',
286 else is SVN_INVALID_REVNUM. (If ACTION is 'add' and COPYFROM_PATH
287 is non-null, then COPYFROM_REV serves the equivalent purpose for the
289 /* ### Can also be SVN_INVALID_REVNUM for a pre-existing file/dir,
290 meaning the base is the youngest revision. This is probably not
291 a good idea -- it is at least confusing -- and we should instead
292 resolve to a real revnum when Ev1 passes in SVN_INVALID_REVNUM
294 svn_revnum_t changing_rev;
295 /* If ACTION is 'delete' or if ACTION is 'add' and it is a replacement,
296 DELETING is TRUE and DELETING_REV is the revision to delete. */
297 /* ### Can also be SVN_INVALID_REVNUM for a pre-existing file/dir,
298 meaning the base is the youngest revision. This is probably not
299 a good idea -- it is at least confusing -- and we should instead
300 resolve to a real revnum when Ev1 passes in SVN_INVALID_REVNUM
302 svn_boolean_t deleting;
303 svn_revnum_t deleting_rev;
305 /* new/final set of props to apply; null => no *change*, not no props */
308 /* new fulltext; null => no change */
309 svn_boolean_t contents_changed;
310 svn_stringbuf_t *contents_text;
312 /* If COPYFROM_PATH is not NULL, then copy PATH@REV to this node.
313 RESTRUCTURE must be RESTRUCTURE_ADD. */
314 const char *copyfrom_path;
315 svn_revnum_t copyfrom_rev;
317 #ifdef SHIM_WITH_UNLOCK
318 /* Record whether an incoming propchange unlocked this node. */
319 svn_boolean_t unlock;
324 /* Return a string representation of CHANGE. */
326 change_node_str(change_node_t *change,
327 apr_pool_t *result_pool)
329 const char *copyfrom = "<nil>";
332 if (change->copyfrom_path)
333 copyfrom = apr_psprintf(result_pool, "'%s'@%ld",
334 change->copyfrom_path, change->copyfrom_rev);
335 str = apr_psprintf(result_pool,
336 "action=%d, kind=%s, changing_rev=%ld, "
337 "deleting=%d, deleting_rev=%ld, ..., "
340 svn_node_kind_to_word(change->kind),
341 change->changing_rev,
342 change->deleting, change->deleting_rev,
348 /* Check whether RELPATH is known to exist, known to not exist, or unknown. */
349 static svn_tristate_t
350 check_existence(apr_hash_t *changes,
353 apr_pool_t *changes_pool = apr_hash_pool_get(changes);
354 apr_pool_t *scratch_pool = changes_pool;
355 change_node_t *change = svn_hash_gets(changes, relpath);
356 svn_tristate_t exists = svn_tristate_unknown;
358 if (change && change->action != RESTRUCTURE_DELETE)
359 exists = svn_tristate_true;
360 else if (change && change->action == RESTRUCTURE_DELETE)
361 exists = svn_tristate_false;
364 const char *parent_path = relpath;
366 /* Find the nearest parent change. If that's a delete or a simple
367 (non-recursive) add, this path cannot exist, else we don't know. */
368 while ((parent_path = svn_relpath_dirname(parent_path, scratch_pool)),
371 change = svn_hash_gets(changes, parent_path);
374 if ((change->action == RESTRUCTURE_ADD && !change->copyfrom_path)
375 || change->action == RESTRUCTURE_DELETE)
376 exists = svn_tristate_false;
385 /* Insert a new Ev1-style change for RELPATH, or return an existing one.
387 * Verify Ev3 rules. Primary differences from Ev1 rules are ...
389 * If ACTION is 'delete', elide any previous explicit deletes inside
390 * that subtree. (Other changes inside that subtree are not allowed.) We
391 * do not store multiple change records per path even with nested moves
392 * -- the most complex change is delete + copy which all fits in one
393 * record with action='add'.
396 insert_change(change_node_t **change_p, apr_hash_t *changes,
398 enum restructure_action_t action)
400 apr_pool_t *changes_pool = apr_hash_pool_get(changes);
401 change_node_t *change = svn_hash_gets(changes, relpath);
403 /* Check whether this op is allowed. */
406 case RESTRUCTURE_NONE:
409 /* A no-restructure change is allowed after add, but not allowed
410 * (in Ev3) after another no-restructure change, nor a delete. */
411 VERIFY(change->action == RESTRUCTURE_ADD);
415 case RESTRUCTURE_ADD:
418 /* Add or copy is allowed after delete (and replaces the delete),
419 * but not allowed after an add or a no-restructure change. */
420 VERIFY(change->action == RESTRUCTURE_DELETE);
421 change->action = action;
425 #ifdef SHIM_WITH_ADD_ABSENT
426 case RESTRUCTURE_ADD_ABSENT:
431 case RESTRUCTURE_DELETE:
432 SVN_ERR_MALFUNCTION();
437 if (action != RESTRUCTURE_NONE)
439 change->action = action;
444 /* Create a new change. Callers will set the other fields as needed. */
445 change = apr_pcalloc(changes_pool, sizeof(*change));
446 change->action = action;
447 change->changing_rev = SVN_INVALID_REVNUM;
449 svn_hash_sets(changes, apr_pstrdup(changes_pool, relpath), change);
456 /* Modify CHANGES so as to delete the subtree at RELPATH.
458 * Insert a new Ev1-style change record for RELPATH (or perhaps remove
459 * the existing record if this would have the same effect), and remove
460 * any change records for sub-paths under RELPATH.
462 * Follow Ev3 rules, although without knowing whether this delete is
463 * part of a move. Ev3 (incremental) "rm" operation says each node to
464 * be removed "MAY be a child of a copy but otherwise SHOULD NOT have
465 * been created or modified in this edit". "mv" operation says ...
468 delete_subtree(apr_hash_t *changes,
470 svn_revnum_t deleting_rev)
472 apr_pool_t *changes_pool = apr_hash_pool_get(changes);
473 apr_pool_t *scratch_pool = changes_pool;
474 change_node_t *change = svn_hash_gets(changes, relpath);
478 /* If this previous change was a non-replacing addition, there
479 is no longer any change to be made at this path. If it was
480 a replacement or a modification, it now becomes a delete.
481 (If it was a delete, this attempt to delete is an error.) */
482 VERIFY(change->action != RESTRUCTURE_DELETE);
483 if (change->action == RESTRUCTURE_ADD && !change->deleting)
485 svn_hash_sets(changes, relpath, NULL);
490 change->action = RESTRUCTURE_DELETE;
495 /* There was no change recorded at this path. Record a delete. */
496 change = apr_pcalloc(changes_pool, sizeof(*change));
497 change->action = RESTRUCTURE_DELETE;
498 change->changing_rev = SVN_INVALID_REVNUM;
499 change->deleting = TRUE;
500 change->deleting_rev = deleting_rev;
502 svn_hash_sets(changes, apr_pstrdup(changes_pool, relpath), change);
505 /* Elide all child ops. */
507 apr_hash_index_t *hi;
509 for (hi = apr_hash_first(scratch_pool, changes);
510 hi; hi = apr_hash_next(hi))
512 const char *this_relpath = apr_hash_this_key(hi);
513 const char *r = svn_relpath_skip_ancestor(relpath, this_relpath);
517 svn_hash_sets(changes, this_relpath, NULL);
527 * ===================================================================
528 * Commit Editor converter to join a v1 driver to a v3 consumer
529 * ===================================================================
535 svn_branch__compat_delta_from_txn_for_commit(
536 const svn_delta_editor_t **deditor,
538 svn_branch__txn_t *edit_txn,
539 const char *repos_root_url,
540 const char *base_relpath,
541 svn_branch__compat_fetch_func_t fetch_func,
543 const svn_branch__compat_shim_connector_t *shim_connector,
544 apr_pool_t *result_pool,
545 apr_pool_t *scratch_pool)
553 svn_branch__compat_delta_from_txn_for_update(
554 const svn_delta_editor_t **deditor,
556 svn_branch__compat_update_editor3_t *update_editor,
557 const char *repos_root_url,
558 const char *base_repos_relpath,
559 svn_branch__compat_fetch_func_t fetch_func,
561 apr_pool_t *result_pool,
562 apr_pool_t *scratch_pool)
564 svn_branch__compat_shim_connector_t *shim_connector
565 = apr_pcalloc(result_pool, sizeof(*shim_connector));
567 shim_connector->target_revision_func = update_editor->set_target_revision_func;
568 shim_connector->baton = update_editor->set_target_revision_baton;
569 #ifdef SHIM_WITH_ABS_PATHS
570 shim_connector->ev1_absolute_paths /*...*/;
573 SVN_ERR(svn_branch__compat_delta_from_txn_for_commit(
574 deditor, dedit_baton,
575 update_editor->edit_txn,
576 repos_root_url, base_repos_relpath,
577 fetch_func, fetch_baton,
579 result_pool, scratch_pool));
580 /*SVN_ERR(svn_delta__get_debug_editor(deditor, dedit_baton,
581 *deditor, *dedit_baton,
582 "[UP>1] ", result_pool));*/
589 * ===================================================================
590 * Commit Editor converter to join a v3 driver to a v1 consumer
591 * ===================================================================
593 * This editor buffers all the changes before driving the Ev1 at the end,
594 * since it needs to do a single depth-first traversal of the path space
595 * and this cannot be started until all moves are known.
597 * Moves are converted to copy-and-delete, with the copy being from
598 * the source peg rev. (### Should it request copy-from revision "-1"?)
600 * It works like this:
604 * Ev3 --> +------+--------+ --> Ev1
608 * 1. Ev3 changes are accumulated in a per-path table, EB->changes.
610 * 2. On Ev3 close-edit, walk through the table in a depth-first order,
611 * sending the equivalent Ev1 action for each change.
615 * ### For changes inside a copied subtree, the calls to the "open dir"
616 * and "open file" Ev1 methods may be passing the wrong revision
617 * number: see comment in apply_change().
619 * ### Have we got our rel-paths in order? Ev1, Ev3 and callbacks may
620 * all expect different paths. Are they relative to repos root or to
621 * some base path? Leading slash (unimplemented 'send_abs_paths'
624 * ### May be tidier for OPEN_ROOT_FUNC callback (see open_root_ev3())
625 * not to actually open the root in advance, but instead just to
626 * remember the base revision that the driver wants us to specify
627 * when we do open it.
633 * ========================================================================
634 * Driving the Delta Editor
635 * ========================================================================
638 /* Information needed for driving the delta editor. */
639 struct svn_branch__txn_priv_t
641 /* The Ev1 "delta editor" */
642 const svn_delta_editor_t *deditor;
646 svn_branch__compat_fetch_func_t fetch_func;
649 /* The Ev1 root directory baton if we have opened the root, else null. */
650 void *ev1_root_dir_baton;
652 #ifdef SHIM_WITH_ABS_PATHS
653 const svn_boolean_t *make_abs_paths;
656 /* Repository root URL
657 ### Some code allows this to be null -- but is that valid? */
658 const char *repos_root_url;
660 /* Ev1 changes recorded so far: REPOS_RELPATH -> change_node_ev3_t */
663 /* The branching state on which the per-element API is working */
664 svn_branch__txn_t *txn;
666 apr_pool_t *edit_pool;
669 /* Get all the (Ev1) paths that have changes.
671 static const apr_array_header_t *
672 get_unsorted_paths(apr_hash_t *changes,
673 apr_pool_t *scratch_pool)
675 apr_array_header_t *paths = apr_array_make(scratch_pool, 0, sizeof(void *));
676 apr_hash_index_t *hi;
678 for (hi = apr_hash_first(scratch_pool, changes); hi; hi = apr_hash_next(hi))
680 const char *this_path = apr_hash_this_key(hi);
681 APR_ARRAY_PUSH(paths, const char *) = this_path;
687 #if 0 /* needed only for shim connector */
690 set_target_revision_ev3(void *edit_baton,
691 svn_revnum_t target_revision,
692 apr_pool_t *scratch_pool)
694 svn_branch__txn_priv_t *eb = edit_baton;
696 SVN_ERR(eb->deditor->set_target_revision(eb->dedit_baton, target_revision,
705 open_root_ev3(void *baton,
706 svn_revnum_t base_revision)
708 svn_branch__txn_priv_t *eb = baton;
710 SVN_ERR(eb->deditor->open_root(eb->dedit_baton, base_revision,
711 eb->edit_pool, &eb->ev1_root_dir_baton));
715 /* If RELPATH is a child of a copy, return the path of the copy root,
719 find_enclosing_copy(apr_hash_t *changes,
721 apr_pool_t *result_pool)
725 const change_node_t *change = svn_hash_gets(changes, relpath);
729 if (change->copyfrom_path)
731 if (change->action != RESTRUCTURE_NONE)
734 relpath = svn_relpath_dirname(relpath, result_pool);
740 /* Set *BASE_PROPS to the 'base' properties, against which any changes
741 * will be described, for the changed path described in CHANGES at
744 * For a copied path, including a copy child path, fetch from the copy
745 * source path. For a plain add, return an empty set. For a delete,
749 fetch_base_props(apr_hash_t **base_props,
751 const char *repos_relpath,
752 svn_branch__compat_fetch_func_t fetch_func,
754 apr_pool_t *result_pool,
755 apr_pool_t *scratch_pool)
757 const change_node_t *change = svn_hash_gets(changes, repos_relpath);
758 const char *source_path = NULL;
759 svn_revnum_t source_rev;
761 if (change->action == RESTRUCTURE_DELETE)
766 else if (change->action == RESTRUCTURE_ADD && ! change->copyfrom_path)
768 *base_props = apr_hash_make(result_pool);
770 else if (change->copyfrom_path)
772 source_path = change->copyfrom_path;
773 source_rev = change->copyfrom_rev;
775 else /* RESTRUCTURE_NONE */
777 /* It's an edit, but possibly to a copy child. Discover if it's a
778 copy child, & find the copy-from source. */
780 const char *copy_path
781 = find_enclosing_copy(changes, repos_relpath, scratch_pool);
785 const change_node_t *enclosing_copy
786 = svn_hash_gets(changes, copy_path);
787 const char *remainder
788 = svn_relpath_skip_ancestor(copy_path, repos_relpath);
790 source_path = svn_relpath_join(enclosing_copy->copyfrom_path,
791 remainder, scratch_pool);
792 source_rev = enclosing_copy->copyfrom_rev;
796 /* It's a plain edit (not a copy child path). */
797 source_path = repos_relpath;
798 source_rev = change->changing_rev;
804 SVN_ERR(fetch_func(NULL, base_props, NULL, NULL,
805 fetch_baton, source_path, source_rev,
806 result_pool, scratch_pool));
812 /* Send property changes to Ev1 for the CHANGE at REPOS_RELPATH.
814 * Ev1 requires exactly one prop-change call for each prop whose value
815 * has changed. Therefore we *have* to fetch the original props from the
816 * repository, provide them as OLD_PROPS, and calculate the changes.
819 drive_ev1_props(const char *repos_relpath,
820 const change_node_t *change,
821 apr_hash_t *old_props,
822 const svn_delta_editor_t *deditor,
824 apr_pool_t *scratch_pool)
826 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
827 apr_array_header_t *propdiffs;
830 SVN_ERR_ASSERT(change->action != RESTRUCTURE_DELETE);
832 /* If there are no property changes, then just exit. */
833 if (change->props == NULL)
836 SVN_ERR(svn_prop_diffs(&propdiffs, change->props, old_props, scratch_pool));
838 /* Apply property changes. These should be changes against the empty set
839 for a new node, or changes against the source node for a copied node. */
840 for (i = 0; i < propdiffs->nelts; i++)
842 const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
844 svn_pool_clear(iterpool);
846 if (change->kind == svn_node_dir)
847 SVN_ERR(deditor->change_dir_prop(node_baton,
848 prop->name, prop->value,
851 SVN_ERR(deditor->change_file_prop(node_baton,
852 prop->name, prop->value,
856 #ifdef SHIM_WITH_UNLOCK
857 /* Handle the funky unlock protocol. Note: only possibly on files. */
860 SVN_ERR_ASSERT(change->kind == svn_node_file);
861 SVN_ERR(deditor->change_file_prop(node_baton,
862 SVN_PROP_ENTRY_LOCK_TOKEN, NULL,
867 svn_pool_destroy(iterpool);
871 /* Drive the Ev1 editor with the change recorded in EB->changes for the
874 * Conforms to svn_delta_path_driver_cb_func_t.
877 apply_change(void **dir_baton,
879 void *callback_baton,
880 const char *ev1_relpath,
881 apr_pool_t *result_pool)
883 apr_pool_t *scratch_pool = result_pool;
884 const svn_branch__txn_priv_t *eb = callback_baton;
885 const change_node_t *change = svn_hash_gets(eb->changes, ev1_relpath);
886 void *file_baton = NULL;
887 apr_hash_t *base_props;
889 /* The callback should only be called for paths in CHANGES. */
890 SVN_ERR_ASSERT(change != NULL);
892 /* Typically, we are not creating new directory batons. */
895 SVN_ERR(fetch_base_props(&base_props, eb->changes, ev1_relpath,
896 eb->fetch_func, eb->fetch_baton,
897 scratch_pool, scratch_pool));
899 /* Are we editing the root of the tree? */
900 if (parent_baton == NULL)
902 /* The root dir was already opened. */
903 *dir_baton = eb->ev1_root_dir_baton;
905 /* Only property edits are allowed on the root. */
906 SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE);
907 SVN_ERR(drive_ev1_props(ev1_relpath, change, base_props,
908 eb->deditor, *dir_baton, scratch_pool));
910 /* No further action possible for the root. */
914 if (change->action == RESTRUCTURE_DELETE)
916 SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting_rev,
917 parent_baton, scratch_pool));
919 /* No futher action possible for this node. */
923 /* If we're not deleting this node, then we should know its kind. */
924 SVN_ERR_ASSERT(change->kind != svn_node_unknown);
926 #ifdef SHIM_WITH_ADD_ABSENT
927 if (change->action == RESTRUCTURE_ADD_ABSENT)
929 if (change->kind == svn_node_dir)
930 SVN_ERR(eb->deditor->absent_directory(ev1_relpath, parent_baton,
932 else if (change->kind == svn_node_file)
933 SVN_ERR(eb->deditor->absent_file(ev1_relpath, parent_baton,
936 SVN_ERR_MALFUNCTION();
938 /* No further action possible for this node. */
942 /* RESTRUCTURE_NONE or RESTRUCTURE_ADD */
944 if (change->action == RESTRUCTURE_ADD)
946 const char *copyfrom_url = NULL;
947 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
949 /* Do we have an old node to delete first? If so, delete it. */
950 if (change->deleting)
951 SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting_rev,
952 parent_baton, scratch_pool));
954 /* If it's a copy, determine the copy source location. */
955 if (change->copyfrom_path)
957 /* ### What's this about URL vs. fspath? REPOS_ROOT_URL isn't
958 optional, is it, at least in a commit editor? */
959 if (eb->repos_root_url)
960 copyfrom_url = svn_path_url_add_component2(eb->repos_root_url,
961 change->copyfrom_path,
965 copyfrom_url = change->copyfrom_path;
967 /* Make this an FS path by prepending "/" */
968 if (copyfrom_url[0] != '/')
969 copyfrom_url = apr_pstrcat(scratch_pool, "/",
970 copyfrom_url, SVN_VA_NULL);
973 copyfrom_rev = change->copyfrom_rev;
976 if (change->kind == svn_node_dir)
977 SVN_ERR(eb->deditor->add_directory(ev1_relpath, parent_baton,
978 copyfrom_url, copyfrom_rev,
979 result_pool, dir_baton));
980 else if (change->kind == svn_node_file)
981 SVN_ERR(eb->deditor->add_file(ev1_relpath, parent_baton,
982 copyfrom_url, copyfrom_rev,
983 result_pool, &file_baton));
985 SVN_ERR_MALFUNCTION();
987 else /* RESTRUCTURE_NONE */
989 /* ### The code that inserts a "plain edit" change record sets
990 'changing_rev' to the peg rev of the pegged part of the path,
991 even when the full path refers to a child of a copy. Should we
992 instead be using the copy source rev here, in that case? (Like
993 when we fetch the base properties.) */
995 if (change->kind == svn_node_dir)
996 SVN_ERR(eb->deditor->open_directory(ev1_relpath, parent_baton,
997 change->changing_rev,
998 result_pool, dir_baton));
999 else if (change->kind == svn_node_file)
1000 SVN_ERR(eb->deditor->open_file(ev1_relpath, parent_baton,
1001 change->changing_rev,
1002 result_pool, &file_baton));
1004 SVN_ERR_MALFUNCTION();
1007 /* Apply any properties in CHANGE to the node. */
1008 if (change->kind == svn_node_dir)
1009 SVN_ERR(drive_ev1_props(ev1_relpath, change, base_props,
1010 eb->deditor, *dir_baton, scratch_pool));
1012 SVN_ERR(drive_ev1_props(ev1_relpath, change, base_props,
1013 eb->deditor, file_baton, scratch_pool));
1015 /* Send the text content delta, if new text content is provided. */
1016 if (change->contents_text)
1018 svn_stream_t *read_stream;
1019 svn_txdelta_window_handler_t handler;
1020 void *handler_baton;
1022 read_stream = svn_stream_from_stringbuf(change->contents_text,
1024 /* ### would be nice to have a BASE_CHECKSUM, but hey: this is the
1026 SVN_ERR(eb->deditor->apply_textdelta(file_baton, NULL, scratch_pool,
1027 &handler, &handler_baton));
1028 /* ### it would be nice to send a true txdelta here, but whatever. */
1029 SVN_ERR(svn_txdelta_send_stream(read_stream, handler, handler_baton,
1030 NULL, scratch_pool));
1031 SVN_ERR(svn_stream_close(read_stream));
1036 SVN_ERR(eb->deditor->close_file(file_baton, NULL, scratch_pool));
1039 return SVN_NO_ERROR;
1043 * ========================================================================
1044 * Old-repository storage paths for branch elements
1045 * ========================================================================
1047 * To support top-level branches, we map each top-level branch to its own
1048 * directory in the old repository, with each nested branch in a subdirectory:
1051 * ^/top0/.../trunk/... <= B0.12
1054 * It may be better to put each branch in its own directory:
1057 * B0.12 => ^/B0.12/...
1060 * (A branch root is not necessarily a directory, it could be a file.)
1063 /* Get the old-repository path for the storage of the root element of BRANCH.
1065 * Currently, this is the same as the nested-branching hierarchical path
1066 * (and thus assumes there is only one top-level branch).
1069 branch_get_storage_root_rrpath(const svn_branch__state_t *branch,
1070 apr_pool_t *result_pool)
1072 int top_branch_num = atoi(branch->bid + 1);
1073 const char *top_path = apr_psprintf(result_pool, "top%d", top_branch_num);
1074 const char *nested_path = svn_branch__get_root_rrpath(branch, result_pool);
1076 return svn_relpath_join(top_path, nested_path, result_pool);
1079 /* Get the old-repository path for the storage of element EID of BRANCH.
1081 * If the element EID doesn't exist in BRANCH, return NULL.
1084 branch_get_storage_rrpath_by_eid(const svn_branch__state_t *branch,
1086 apr_pool_t *result_pool)
1088 const char *path = svn_branch__get_path_by_eid(branch, eid, result_pool);
1089 const char *rrpath = NULL;
1093 rrpath = svn_relpath_join(branch_get_storage_root_rrpath(branch,
1100 /* Return, in *STORAGE_PATHREV_P, the storage-rrpath-rev for BRANCH_REF.
1102 static svn_error_t *
1103 storage_pathrev_from_branch_ref(svn_pathrev_t *storage_pathrev_p,
1104 svn_element__branch_ref_t branch_ref,
1105 svn_branch__repos_t *repos,
1106 apr_pool_t *result_pool,
1107 apr_pool_t *scratch_pool)
1109 svn_branch__el_rev_id_t *el_rev;
1111 SVN_ERR(svn_branch__repos_find_el_rev_by_id(&el_rev,
1114 branch_ref.branch_id,
1116 scratch_pool, scratch_pool));
1118 storage_pathrev_p->rev = el_rev->rev;
1119 storage_pathrev_p->relpath
1120 = branch_get_storage_rrpath_by_eid(el_rev->branch, el_rev->eid,
1123 return SVN_NO_ERROR;
1127 * ========================================================================
1128 * Editor for Commit (independent per-element changes; element-id addressing)
1129 * ========================================================================
1133 #define PAYLOAD_IS_ONLY_BY_REFERENCE(payload) \
1134 ((payload)->kind == svn_node_unknown)
1136 /* Fetch a payload as *PAYLOAD_P from its storage-pathrev PATH_REV.
1137 * Fetch names of immediate children of PATH_REV as *CHILDREN_NAMES.
1138 * Either of the outputs may be null if not wanted.
1140 static svn_error_t *
1141 payload_fetch(svn_element__payload_t **payload_p,
1142 apr_hash_t **children_names,
1143 svn_branch__txn_priv_t *eb,
1144 const svn_pathrev_t *path_rev,
1145 apr_pool_t *result_pool,
1146 apr_pool_t *scratch_pool)
1148 svn_element__payload_t *payload
1149 = apr_pcalloc(result_pool, sizeof (*payload));
1151 SVN_ERR(eb->fetch_func(&payload->kind,
1156 path_rev->relpath, path_rev->rev,
1157 result_pool, scratch_pool));
1159 SVN_ERR_ASSERT(svn_element__payload_invariants(payload));
1160 SVN_ERR_ASSERT(payload->kind == svn_node_dir
1161 || payload->kind == svn_node_file);
1163 *payload_p = payload;
1164 return SVN_NO_ERROR;
1167 /* Return the storage-pathrev of PAYLOAD as *STORAGE_PATHREV_P.
1169 * Find the storage-pathrev from PAYLOAD->branch_ref.
1171 static svn_error_t *
1172 payload_get_storage_pathrev(svn_pathrev_t *storage_pathrev_p,
1173 svn_element__payload_t *payload,
1174 svn_branch__repos_t *repos,
1175 apr_pool_t *result_pool)
1177 SVN_ERR_ASSERT(payload->branch_ref.branch_id /* && ... */);
1179 SVN_ERR(storage_pathrev_from_branch_ref(storage_pathrev_p,
1180 payload->branch_ref, repos,
1181 result_pool, result_pool));
1182 return SVN_NO_ERROR;
1186 svn_branch__compat_fetch(svn_element__payload_t **payload_p,
1187 svn_branch__txn_t *txn,
1188 svn_element__branch_ref_t branch_ref,
1189 svn_branch__compat_fetch_func_t fetch_func,
1191 apr_pool_t *result_pool,
1192 apr_pool_t *scratch_pool)
1194 svn_branch__txn_priv_t eb;
1195 svn_pathrev_t storage_pathrev;
1197 /* Simulate the existence of /top0 in r0. */
1198 if (branch_ref.rev == 0 && branch_ref.eid == 0)
1200 *payload_p = svn_element__payload_create_dir(apr_hash_make(result_pool),
1202 return SVN_NO_ERROR;
1206 eb.fetch_func = fetch_func;
1207 eb.fetch_baton = fetch_baton;
1209 SVN_ERR(storage_pathrev_from_branch_ref(&storage_pathrev,
1210 branch_ref, txn->repos,
1211 scratch_pool, scratch_pool));
1213 SVN_ERR(payload_fetch(payload_p, NULL,
1214 &eb, &storage_pathrev, result_pool, scratch_pool));
1215 (*payload_p)->branch_ref = branch_ref;
1216 return SVN_NO_ERROR;
1219 /* Fill in the actual payload, from its reference, if not already done.
1221 static svn_error_t *
1222 payload_resolve(svn_element__payload_t *payload,
1223 svn_branch__txn_priv_t *eb,
1224 apr_pool_t *scratch_pool)
1226 svn_pathrev_t storage;
1228 SVN_ERR_ASSERT(svn_element__payload_invariants(payload));
1230 if (! PAYLOAD_IS_ONLY_BY_REFERENCE(payload))
1231 return SVN_NO_ERROR;
1233 SVN_ERR(payload_get_storage_pathrev(&storage, payload,
1237 SVN_ERR(eb->fetch_func(&payload->kind,
1242 storage.relpath, storage.rev,
1243 payload->pool, scratch_pool));
1245 SVN_ERR_ASSERT(svn_element__payload_invariants(payload));
1246 SVN_ERR_ASSERT(! PAYLOAD_IS_ONLY_BY_REFERENCE(payload));
1247 return SVN_NO_ERROR;
1250 /* Update *PATHS, a hash of (storage_rrpath -> svn_branch__el_rev_id_t),
1251 * creating or filling in entries for all elements in BRANCH.
1253 static svn_error_t *
1254 convert_branch_to_paths(apr_hash_t *paths,
1255 svn_branch__state_t *branch,
1256 apr_pool_t *result_pool,
1257 apr_pool_t *scratch_pool)
1259 apr_hash_index_t *hi;
1260 svn_element__tree_t *elements;
1262 /* assert(branch is at a sequence point); */
1264 SVN_ERR(svn_branch__state_get_elements(branch, &elements, scratch_pool));
1265 for (hi = apr_hash_first(scratch_pool, elements->e_map);
1266 hi; hi = apr_hash_next(hi))
1268 int eid = svn_eid__hash_this_key(hi);
1269 svn_element__content_t *element = apr_hash_this_val(hi);
1271 = branch_get_storage_rrpath_by_eid(branch, eid, result_pool);
1272 svn_branch__el_rev_id_t *ba;
1274 /* A subbranch-root element carries no payload; the corresponding
1275 inner branch root element will provide the payload for this path. */
1276 if (element->payload->is_subbranch_root)
1279 /* No other element should exist at this path, given that we avoid
1280 storing anything for a subbranch-root element. */
1281 SVN_ERR_ASSERT(! svn_hash_gets(paths, rrpath));
1283 /* Fill in the details. */
1284 ba = svn_branch__el_rev_id_create(branch, eid, branch->txn->rev,
1286 svn_hash_sets(paths, rrpath, ba);
1287 /*SVN_DBG(("branch-to-path[%d]: b%s e%d -> %s",
1288 i, svn_branch__get_id(branch, scratch_pool), eid, rrpath));*/
1290 return SVN_NO_ERROR;
1293 /* Produce a mapping from paths to element ids, covering all elements in
1294 * BRANCH and all its sub-branches, recursively.
1296 * Update *PATHS_UNION, a hash of (storage_rrpath -> svn_branch__el_rev_id_t),
1297 * creating or filling in entries for all elements in all branches at and
1298 * under BRANCH, recursively.
1300 static svn_error_t *
1301 convert_branch_to_paths_r(apr_hash_t *paths_union,
1302 svn_branch__state_t *branch,
1303 apr_pool_t *result_pool,
1304 apr_pool_t *scratch_pool)
1306 apr_array_header_t *subbranches;
1309 /*SVN_DBG(("[%d] branch={b%s e%d at '%s'}", idx,
1310 svn_branch__get_id(branch, scratch_pool), branch->root_eid,
1311 svn_branch__get_root_rrpath(branch, scratch_pool)));*/
1312 SVN_ERR(convert_branch_to_paths(paths_union, branch,
1313 result_pool, scratch_pool));
1315 SVN_ERR(svn_branch__get_immediate_subbranches(branch, &subbranches,
1316 scratch_pool, scratch_pool));
1317 /* Rercurse into sub-branches */
1318 for (i = 0; i < subbranches->nelts; i++)
1320 svn_branch__state_t *b = APR_ARRAY_IDX(subbranches, i, void *);
1322 SVN_ERR(convert_branch_to_paths_r(paths_union, b, result_pool,
1325 return SVN_NO_ERROR;
1328 /* Return TRUE iff INITIAL_PAYLOAD and FINAL_PAYLOAD are both non-null
1329 * and have the same properties.
1331 static svn_boolean_t
1332 props_equal(svn_element__payload_t *initial_payload,
1333 svn_element__payload_t *final_payload,
1334 apr_pool_t *scratch_pool)
1336 apr_array_header_t *prop_diffs;
1338 if (!initial_payload || !final_payload)
1341 svn_error_clear(svn_prop_diffs(&prop_diffs,
1342 initial_payload->props,
1343 final_payload->props,
1345 return (prop_diffs->nelts == 0);
1348 /* Return TRUE iff INITIAL_PAYLOAD and FINAL_PAYLOAD are both file payload
1349 * and have the same text.
1351 static svn_boolean_t
1352 text_equal(svn_element__payload_t *initial_payload,
1353 svn_element__payload_t *final_payload)
1355 if (!initial_payload || !final_payload
1356 || initial_payload->kind != svn_node_file
1357 || final_payload->kind != svn_node_file)
1362 return svn_stringbuf_compare(initial_payload->text,
1363 final_payload->text);
1366 /* Return the copy-from location to be used if this is to be a copy;
1367 * otherwise return NULL.
1369 * ### Currently this is indicated by payload-by-reference, which is
1370 * an inadequate indication.
1372 static svn_error_t *
1373 get_copy_from(svn_pathrev_t *copyfrom_pathrev_p,
1374 svn_element__payload_t *final_payload,
1375 svn_branch__txn_priv_t *eb,
1376 apr_pool_t *result_pool)
1378 if (final_payload->branch_ref.branch_id)
1380 SVN_ERR(payload_get_storage_pathrev(copyfrom_pathrev_p, final_payload,
1386 copyfrom_pathrev_p->relpath = NULL;
1387 copyfrom_pathrev_p->rev = SVN_INVALID_REVNUM;
1390 return SVN_NO_ERROR;
1393 /* Return a hash whose keys are the names of the immediate children of
1397 get_immediate_children_names(apr_hash_t *paths,
1398 const char *parent_rrpath,
1399 apr_pool_t *result_pool,
1400 apr_pool_t *scratch_pool)
1402 apr_hash_t *children = apr_hash_make(result_pool);
1403 apr_hash_index_t *hi;
1405 for (hi = apr_hash_first(scratch_pool, paths); hi; hi = apr_hash_next(hi))
1407 const char *this_rrpath = apr_hash_this_key(hi);
1410 && strcmp(parent_rrpath, svn_relpath_dirname(this_rrpath,
1411 scratch_pool)) == 0)
1413 svn_hash_sets(children,
1414 svn_relpath_basename(this_rrpath, result_pool), "");
1421 /* Generate Ev1 instructions to edit from a current state to a final state
1422 * at RRPATH, recursing for child paths of RRPATH.
1424 * The current state at RRPATH might not be the initial state because,
1425 * although neither RRPATH nor any sub-paths have been explicitly visited
1426 * before, the current state at RRPATH and its sub-paths might be the
1429 * PRED_LOC is the predecessor location of the node currently at RRPATH in
1430 * the Ev1 transaction, or NULL if there is no node currently at RRPATH.
1431 * If the node is copied, including a child of a copy, this is its copy-from
1432 * location, otherwise this is its location in the txn base revision.
1433 * (The current node cannot be a plain added node on entry to this function,
1434 * as the function must be called only once for each path and there is no
1435 * recursive add operation.) PRED_LOC identifies the node content that the
1436 * that the Ev1 edit needs to delete, replace, update or leave unchanged.
1438 * Process a single hierarchy of nested branches, rooted in the top-level
1439 * branch TOP_BRANCH_NUM.
1441 static svn_error_t *
1442 drive_changes_r(const char *rrpath,
1443 svn_pathrev_t *pred_loc,
1444 apr_hash_t *paths_final,
1445 const char *top_branch_id,
1446 svn_branch__txn_priv_t *eb,
1447 apr_pool_t *scratch_pool)
1449 /* The el-rev-id of the element that will finally exist at RRPATH. */
1450 svn_branch__el_rev_id_t *final_el_rev = svn_hash_gets(paths_final, rrpath);
1451 svn_element__payload_t *final_payload;
1452 svn_pathrev_t final_copy_from;
1453 svn_boolean_t succession;
1455 /*SVN_DBG(("rrpath '%s' current=%s, final=e%d)",
1457 pred_loc ? pathrev_str(*pred_loc, scratch_pool) : "<nil>",
1458 final_el_rev ? final_el_rev->eid : -1));*/
1460 SVN_ERR_ASSERT(!pred_loc
1461 || (pred_loc->relpath && SVN_IS_VALID_REVNUM(pred_loc->rev)));
1465 svn_element__content_t *final_element;
1467 SVN_ERR(svn_branch__state_get_element(final_el_rev->branch, &final_element,
1468 final_el_rev->eid, scratch_pool));
1469 /* A non-null FINAL address means an element exists there. */
1470 SVN_ERR_ASSERT(final_element);
1472 final_payload = final_element->payload;
1474 /* Decide whether the state at this path should be a copy (incl. a
1476 SVN_ERR(get_copy_from(&final_copy_from, final_payload, eb, scratch_pool));
1477 /* It doesn't make sense to have a non-copy inside a copy */
1478 /*SVN_ERR_ASSERT(!(parent_is_copy && !final_copy_from));*/
1482 final_payload = NULL;
1483 final_copy_from.relpath = NULL;
1486 /* Succession means:
1487 for a copy (inc. child) -- copy-from same place as natural predecessor
1488 otherwise -- it's succession if it's the same element
1489 (which also implies the same kind) */
1490 if (pred_loc && final_copy_from.relpath)
1492 succession = pathrev_equal(pred_loc, &final_copy_from);
1494 else if (pred_loc && final_el_rev)
1496 svn_branch__el_rev_id_t *pred_el_rev;
1498 SVN_ERR(svn_branch__repos_find_el_rev_by_path_rev(&pred_el_rev,
1503 scratch_pool, scratch_pool));
1505 succession = (pred_el_rev->eid == final_el_rev->eid);
1512 /* If there's an initial node that isn't also going to be the final
1513 node at this path, then it's being deleted or replaced: delete it. */
1514 if (pred_loc && !succession)
1516 /* Issue an Ev1 delete unless this path is inside a path at which
1517 we've already issued a delete. */
1518 if (check_existence(eb->changes, rrpath) != svn_tristate_false)
1520 /*SVN_DBG(("ev1:del(%s)", rrpath));*/
1521 /* ### We don't need "delete_subtree", we only need to insert a
1522 single delete operation, as we know we haven't
1523 inserted any changes inside this subtree. */
1524 SVN_ERR(delete_subtree(eb->changes, rrpath, pred_loc->rev));
1528 /*SVN_DBG(("ev1:del(%s): parent is already deleted", rrpath))*/
1532 /* If there's a final node, it's being added or modified.
1533 Or it's unchanged -- we do nothing in that case. */
1536 svn_element__payload_t *current_payload = NULL;
1537 apr_hash_t *current_children = NULL;
1538 change_node_t *change = NULL;
1540 /* Get the full payload of the final node. If we have
1541 only a reference to the payload, fetch it in full. */
1542 SVN_ERR_ASSERT(final_payload);
1543 SVN_ERR(payload_resolve(final_payload, eb, scratch_pool));
1545 /* If the final node was also the initial node, it's being
1546 modified, otherwise it's being added (perhaps a replacement). */
1549 /* Get full payload of the current node */
1550 SVN_ERR(payload_fetch(¤t_payload, ¤t_children,
1552 scratch_pool, scratch_pool));
1554 /* If no changes to make, then skip this path */
1555 if (svn_element__payload_equal(current_payload,
1556 final_payload, scratch_pool))
1558 /*SVN_DBG(("ev1:no-op(%s)", rrpath));*/
1562 /*SVN_DBG(("ev1:mod(%s)", rrpath));*/
1563 SVN_ERR(insert_change(&change, eb->changes, rrpath,
1565 change->changing_rev = pred_loc->rev;
1568 else /* add or copy/move */
1570 /*SVN_DBG(("ev1:add(%s)", rrpath));*/
1571 SVN_ERR(insert_change(&change, eb->changes, rrpath,
1574 /* If the node is to be copied (and possibly modified) ... */
1575 if (final_copy_from.relpath)
1577 change->copyfrom_rev = final_copy_from.rev;
1578 change->copyfrom_path = final_copy_from.relpath;
1580 /* Get full payload of the copy source node */
1581 SVN_ERR(payload_fetch(¤t_payload, ¤t_children,
1582 eb, &final_copy_from,
1583 scratch_pool, scratch_pool));
1589 /* Copy the required content into the change record. Avoid no-op
1590 changes of props / text, not least to minimize clutter when
1591 debugging Ev1 operations. */
1592 SVN_ERR_ASSERT(final_payload->kind == svn_node_dir
1593 || final_payload->kind == svn_node_file);
1594 change->kind = final_payload->kind;
1595 if (!props_equal(current_payload, final_payload, scratch_pool))
1597 change->props = final_payload->props;
1599 if (final_payload->kind == svn_node_file
1600 && !text_equal(current_payload, final_payload))
1602 change->contents_text = final_payload->text;
1606 /* Recurse to process this directory's children */
1607 if (final_payload->kind == svn_node_dir)
1609 apr_hash_t *final_children;
1610 apr_hash_t *union_children;
1611 apr_hash_index_t *hi;
1613 final_children = get_immediate_children_names(paths_final, rrpath,
1616 union_children = (current_children
1617 ? hash_overlay(current_children, final_children)
1619 for (hi = apr_hash_first(scratch_pool, union_children);
1620 hi; hi = apr_hash_next(hi))
1622 const char *name = apr_hash_this_key(hi);
1623 const char *this_rrpath = svn_relpath_join(rrpath, name,
1625 svn_boolean_t child_in_current
1626 = current_children && svn_hash_gets(current_children, name);
1627 svn_pathrev_t *child_pred = NULL;
1629 if (child_in_current)
1631 /* If the parent dir is copied, then this child has been
1632 copied along with it: predecessor is parent's copy-from
1633 location extended by the child's name. */
1634 child_pred = apr_palloc(scratch_pool, sizeof(*child_pred));
1635 if (final_copy_from.relpath)
1637 child_pred->rev = final_copy_from.rev;
1639 = svn_relpath_join(final_copy_from.relpath, name,
1644 child_pred->rev = pred_loc->rev;
1645 child_pred->relpath = this_rrpath;
1649 SVN_ERR(drive_changes_r(this_rrpath,
1651 paths_final, top_branch_id,
1657 return SVN_NO_ERROR;
1661 * Drive svn_delta_editor_t (actions: add/copy/delete/modify) from
1662 * a before-and-after element mapping.
1664 static svn_error_t *
1665 drive_changes(svn_branch__txn_priv_t *eb,
1666 apr_pool_t *scratch_pool)
1668 apr_array_header_t *branches;
1670 const apr_array_header_t *paths;
1672 /* Convert the element mappings to an svn_delta_editor_t traversal.
1674 1. find union of paths in initial and final states, across all branches.
1675 2. traverse paths in depth-first order.
1676 3. modify/delete/add/replace as needed at each path.
1679 /* Process one hierarchy of nested branches at a time. */
1680 branches = svn_branch__txn_get_branches(eb->txn, scratch_pool);
1681 for (i = 0; i < branches->nelts; i++)
1683 svn_branch__state_t *root_branch = APR_ARRAY_IDX(branches, i, void *);
1684 apr_hash_t *paths_final;
1686 const char *top_path = branch_get_storage_root_rrpath(root_branch,
1688 svn_pathrev_t current;
1689 svn_branch__state_t *base_root_branch;
1690 svn_boolean_t branch_is_new;
1692 if (strchr(root_branch->bid, '.'))
1693 continue; /* that's not a root branch */
1695 SVN_ERR(svn_branch__repos_get_branch_by_id(&base_root_branch,
1698 root_branch->bid, scratch_pool));
1699 branch_is_new = !base_root_branch;
1701 paths_final = apr_hash_make(scratch_pool);
1702 SVN_ERR(convert_branch_to_paths_r(paths_final,
1704 scratch_pool, scratch_pool));
1706 current.rev = eb->txn->base_rev;
1707 current.relpath = top_path;
1709 /* Create the top-level storage node if the branch is new, or if this is
1710 the first commit to branch B0 which was created in r0 but had no
1711 storage node there. */
1712 if (branch_is_new || current.rev == 0)
1714 change_node_t *change;
1716 SVN_ERR(insert_change(&change, eb->changes, top_path, RESTRUCTURE_ADD));
1717 change->kind = svn_node_dir;
1720 SVN_ERR(drive_changes_r(top_path, ¤t,
1721 paths_final, svn_branch__get_id(root_branch,
1726 /* If the driver has not explicitly opened the root directory via the
1727 start_edit (aka open_root) callback, do so now. */
1728 if (eb->ev1_root_dir_baton == NULL)
1729 SVN_ERR(open_root_ev3(eb, SVN_INVALID_REVNUM));
1731 /* Make the path driver visit the root dir of the edit. Otherwise, it
1732 will attempt an open_root() instead, which we already did. */
1733 if (! svn_hash_gets(eb->changes, ""))
1735 change_node_t *change;
1737 SVN_ERR(insert_change(&change, eb->changes, "", RESTRUCTURE_NONE));
1738 change->kind = svn_node_dir;
1741 /* Apply the appropriate Ev1 change to each Ev1-relative path. */
1742 paths = get_unsorted_paths(eb->changes, scratch_pool);
1743 SVN_ERR(svn_delta_path_driver2(eb->deditor, eb->dedit_baton,
1744 paths, TRUE /*sort*/,
1745 apply_change, (void *)eb,
1748 return SVN_NO_ERROR;
1751 /* An #svn_branch__txn_t method. */
1752 static apr_array_header_t *
1753 compat_branch_txn_get_branches(const svn_branch__txn_t *txn,
1754 apr_pool_t *result_pool)
1756 /* Just forwarding: nothing more is needed. */
1757 apr_array_header_t *branches
1758 = svn_branch__txn_get_branches(txn->priv->txn,
1764 /* An #svn_branch__txn_t method. */
1765 static svn_error_t *
1766 compat_branch_txn_delete_branch(svn_branch__txn_t *txn,
1768 apr_pool_t *scratch_pool)
1770 /* Just forwarding: nothing more is needed. */
1771 SVN_ERR(svn_branch__txn_delete_branch(txn->priv->txn,
1774 return SVN_NO_ERROR;
1777 /* An #svn_branch__txn_t method. */
1778 static svn_error_t *
1779 compat_branch_txn_get_num_new_eids(const svn_branch__txn_t *txn,
1780 int *num_new_eids_p,
1781 apr_pool_t *scratch_pool)
1783 /* Just forwarding: nothing more is needed. */
1784 SVN_ERR(svn_branch__txn_get_num_new_eids(txn->priv->txn,
1787 return SVN_NO_ERROR;
1790 /* An #svn_branch__txn_t method. */
1791 static svn_error_t *
1792 compat_branch_txn_new_eid(svn_branch__txn_t *txn,
1793 svn_branch__eid_t *eid_p,
1794 apr_pool_t *scratch_pool)
1796 /* Just forwarding: nothing more is needed. */
1797 SVN_ERR(svn_branch__txn_new_eid(txn->priv->txn,
1800 return SVN_NO_ERROR;
1803 /* An #svn_branch__txn_t method. */
1804 static svn_error_t *
1805 compat_branch_txn_finalize_eids(svn_branch__txn_t *txn,
1806 apr_pool_t *scratch_pool)
1808 /* Just forwarding: nothing more is needed. */
1809 SVN_ERR(svn_branch__txn_finalize_eids(txn->priv->txn,
1811 return SVN_NO_ERROR;
1814 /* An #svn_branch__txn_t method. */
1815 static svn_error_t *
1816 compat_branch_txn_open_branch(svn_branch__txn_t *txn,
1817 svn_branch__state_t **new_branch_p,
1818 const char *new_branch_id,
1820 svn_branch__rev_bid_eid_t *tree_ref,
1821 apr_pool_t *result_pool,
1822 apr_pool_t *scratch_pool)
1824 /* Just forwarding: nothing more is needed. */
1825 SVN_ERR(svn_branch__txn_open_branch(txn->priv->txn,
1827 new_branch_id, root_eid, tree_ref,
1830 return SVN_NO_ERROR;
1833 /* An #svn_branch__txn_t method. */
1834 static svn_error_t *
1835 compat_branch_txn_serialize(svn_branch__txn_t *txn,
1836 svn_stream_t *stream,
1837 apr_pool_t *scratch_pool)
1839 /* Just forwarding: nothing more is needed. */
1840 SVN_ERR(svn_branch__txn_serialize(txn->priv->txn,
1843 return SVN_NO_ERROR;
1846 /* An #svn_branch__txn_t method. */
1847 static svn_error_t *
1848 compat_branch_txn_sequence_point(svn_branch__txn_t *txn,
1849 apr_pool_t *scratch_pool)
1851 /* Just forwarding: nothing more is needed. */
1852 SVN_ERR(svn_branch__txn_sequence_point(txn->priv->txn,
1854 return SVN_NO_ERROR;
1857 /* An #svn_branch__txn_t method. */
1858 static svn_error_t *
1859 compat_branch_txn_complete(svn_branch__txn_t *txn,
1860 apr_pool_t *scratch_pool)
1862 svn_branch__txn_priv_t *eb = txn->priv;
1865 /* Convert the transaction to a revision */
1866 SVN_ERR(svn_branch__txn_sequence_point(txn->priv->txn, scratch_pool));
1867 SVN_ERR(svn_branch__txn_finalize_eids(txn->priv->txn, scratch_pool));
1869 err = drive_changes(eb, scratch_pool);
1873 err = svn_error_compose_create(err, eb->deditor->close_edit(
1879 svn_error_clear(eb->deditor->abort_edit(eb->dedit_baton, scratch_pool));
1881 SVN_ERR(svn_branch__txn_complete(txn->priv->txn, scratch_pool));
1886 /* An #svn_branch__txn_t method. */
1887 static svn_error_t *
1888 compat_branch_txn_abort(svn_branch__txn_t *txn,
1889 apr_pool_t *scratch_pool)
1891 svn_branch__txn_priv_t *eb = txn->priv;
1893 SVN_ERR(eb->deditor->abort_edit(eb->dedit_baton, scratch_pool));
1895 SVN_ERR(svn_branch__txn_abort(txn->priv->txn,
1897 return SVN_NO_ERROR;
1900 /* Baton for wrap_fetch_func. */
1901 typedef struct wrap_fetch_baton_t
1903 /* Wrapped fetcher */
1904 svn_branch__compat_fetch_func_t fetch_func;
1906 } wrap_fetch_baton_t;
1908 /* The purpose of this fetcher-wrapper is to make it appear that B0
1909 * was created (as an empty dir) in r0.
1911 static svn_error_t *
1912 wrap_fetch_func(svn_node_kind_t *kind,
1914 svn_stringbuf_t **file_text,
1915 apr_hash_t **children_names,
1917 const char *repos_relpath,
1918 svn_revnum_t revision,
1919 apr_pool_t *result_pool,
1920 apr_pool_t *scratch_pool)
1922 wrap_fetch_baton_t *b = baton;
1924 if (revision == 0 && strcmp(repos_relpath, "top0") == 0)
1927 *kind = svn_node_dir;
1929 *props = apr_hash_make(result_pool);
1933 *children_names = apr_hash_make(result_pool);
1937 SVN_ERR(b->fetch_func(kind, props, file_text, children_names,
1939 repos_relpath, revision,
1940 result_pool, scratch_pool));
1943 return SVN_NO_ERROR;
1947 svn_branch__compat_txn_from_delta_for_commit(
1948 svn_branch__txn_t **txn_p,
1949 svn_branch__compat_shim_connector_t **shim_connector,
1950 const svn_delta_editor_t *deditor,
1952 svn_branch__txn_t *branching_txn,
1953 const char *repos_root_url,
1954 svn_branch__compat_fetch_func_t fetch_func,
1956 svn_cancel_func_t cancel_func,
1958 apr_pool_t *result_pool,
1959 apr_pool_t *scratch_pool)
1961 static const svn_branch__txn_vtable_t vtable = {
1963 compat_branch_txn_get_branches,
1964 compat_branch_txn_delete_branch,
1965 compat_branch_txn_get_num_new_eids,
1966 compat_branch_txn_new_eid,
1967 compat_branch_txn_open_branch,
1968 compat_branch_txn_finalize_eids,
1969 compat_branch_txn_serialize,
1970 compat_branch_txn_sequence_point,
1971 compat_branch_txn_complete,
1972 compat_branch_txn_abort
1974 svn_branch__txn_t *txn;
1975 svn_branch__txn_priv_t *eb = apr_pcalloc(result_pool, sizeof(*eb));
1976 wrap_fetch_baton_t *wb = apr_pcalloc(result_pool, sizeof(*wb));
1978 eb->deditor = deditor;
1979 eb->dedit_baton = dedit_baton;
1981 eb->repos_root_url = apr_pstrdup(result_pool, repos_root_url);
1983 eb->changes = apr_hash_make(result_pool);
1985 wb->fetch_func = fetch_func;
1986 wb->fetch_baton = fetch_baton;
1987 eb->fetch_func = wrap_fetch_func;
1988 eb->fetch_baton = wb;
1990 eb->edit_pool = result_pool;
1992 branching_txn = svn_branch__nested_txn_create(branching_txn, result_pool);
1994 eb->txn = branching_txn;
1996 txn = svn_branch__txn_create(&vtable, NULL, NULL, result_pool);
1998 txn->repos = branching_txn->repos;
1999 txn->rev = branching_txn->rev;
2000 txn->base_rev = branching_txn->base_rev;
2006 *shim_connector = apr_palloc(result_pool, sizeof(**shim_connector));
2007 #ifdef SHIM_WITH_ABS_PATHS
2008 (*shim_connector)->ev1_absolute_paths
2009 = apr_palloc(result_pool, sizeof(svn_boolean_t));
2010 eb->make_abs_paths = (*shim_connector)->ev1_absolute_paths;
2012 (*shim_connector)->target_revision_func = set_target_revision_ev3;
2013 (*shim_connector)->start_edit_func = open_root_ev3;
2014 #ifdef SHIM_WITH_UNLOCK
2015 (*shim_connector)->unlock_func = do_unlock;
2017 (*shim_connector)->baton = eb;
2021 return SVN_NO_ERROR;
2025 svn_branch__compat_txn_from_delta_for_update(
2026 svn_branch__compat_update_editor3_t **update_editor_p,
2027 const svn_delta_editor_t *deditor,
2029 svn_branch__txn_t *branching_txn,
2030 const char *repos_root_url,
2031 const char *base_repos_relpath,
2032 svn_branch__compat_fetch_func_t fetch_func,
2034 svn_cancel_func_t cancel_func,
2036 apr_pool_t *result_pool,
2037 apr_pool_t *scratch_pool)
2039 svn_branch__compat_update_editor3_t *update_editor
2040 = apr_pcalloc(result_pool, sizeof(*update_editor));
2041 svn_branch__compat_shim_connector_t *shim_connector;
2043 /*(("svn_delta__ev3_from_delta_for_update(base='%s')...",
2044 base_repos_relpath));*/
2046 /*SVN_ERR(svn_delta__get_debug_editor(&deditor, &dedit_baton,
2047 deditor, dedit_baton,
2048 "[1>UP] ", result_pool));*/
2049 SVN_ERR(svn_branch__compat_txn_from_delta_for_commit(
2050 &update_editor->edit_txn,
2052 deditor, dedit_baton,
2053 branching_txn, repos_root_url,
2054 fetch_func, fetch_baton,
2055 cancel_func, cancel_baton,
2056 result_pool, scratch_pool));
2058 update_editor->set_target_revision_func = shim_connector->target_revision_func;
2059 update_editor->set_target_revision_baton = shim_connector->baton;
2060 /* shim_connector->start_edit_func = open_root_ev3; */
2061 #ifdef SHIM_WITH_ABS_PATHS
2062 update_editor->ev1_absolute_paths /*...*/;
2064 #ifdef SHIM_WITH_UNLOCK
2065 update_editor->unlock_func = do_unlock;
2068 *update_editor_p = update_editor;
2069 return SVN_NO_ERROR;