2 * replay.c: an editor driver for changes made in a given revision
5 * ====================================================================
6 * Licensed to the Apache Software Foundation (ASF) under one
7 * or more contributor license agreements. See the NOTICE file
8 * distributed with this work for additional information
9 * regarding copyright ownership. The ASF licenses this file
10 * to you under the Apache License, Version 2.0 (the
11 * "License"); you may not use this file except in compliance
12 * with the License. You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing,
17 * software distributed under the License is distributed on an
18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 * KIND, either express or implied. See the License for the
20 * specific language governing permissions and limitations
22 * ====================================================================
28 #include "svn_types.h"
29 #include "svn_delta.h"
32 #include "svn_checksum.h"
33 #include "svn_repos.h"
34 #include "svn_sorts.h"
35 #include "svn_props.h"
36 #include "svn_pools.h"
38 #include "svn_private_config.h"
39 #include "private/svn_fspath.h"
40 #include "private/svn_repos_private.h"
41 #include "private/svn_delta_private.h"
42 #include "private/svn_sorts_private.h"
47 /* The year was 2003. Subversion usage was rampant in the world, and
48 there was a rapidly growing issues database to prove it. To make
49 matters worse, svn_repos_dir_delta() had simply outgrown itself.
50 No longer content to simply describe the differences between two
51 trees, the function had been slowly bearing the added
52 responsibility of representing the actions that had been taken to
53 cause those differences -- a burden it was never meant to bear.
54 Now grown into a twisted mess of razor-sharp metal and glass, and
55 trembling with a sort of momentarily stayed spring force,
56 svn_repos_dir_delta was a timebomb poised for total annihilation of
59 Subversion needed a change.
61 Changes, in fact. And not just in the literary segue sense. What
62 Subversion desperately needed was a new mechanism solely
63 responsible for replaying repository actions back to some
64 interested party -- to translate and retransmit the contents of the
65 Berkeley 'changes' database file. */
69 /* The filesystem keeps a record of high-level actions that affect the
70 files and directories in itself. The 'changes' table records
71 additions, deletions, textual and property modifications, and so
72 on. The goal of the functions in this file is to examine those
73 change records, and use them to drive an editor interface in such a
74 way as to effectively replay those actions.
76 This is critically different than what svn_repos_dir_delta() was
77 designed to do. That function describes, in the simplest way it
78 can, how to transform one tree into another. It doesn't care
79 whether or not this was the same way a user might have done this
80 transformation. More to the point, it doesn't care if this is how
81 those differences *did* come into being. And it is for this reason
82 that it cannot be relied upon for tasks such as the repository
83 dumpfile-generation code, which is supposed to represent not
84 changes, but actions that cause changes.
86 So, what's the plan here?
88 First, we fetch the changes for a particular revision or
89 transaction. We get these as an array, sorted chronologically.
90 From this array we will build a hash, keyed on the path associated
91 with each change item, and whose values are arrays of changes made
92 to that path, again preserving the chronological ordering.
94 Once our hash is built, we then sort all the keys of the hash (the
95 paths) using a depth-first directory sort routine.
97 Finally, we drive an editor, moving down our list of sorted paths,
98 and manufacturing any intermediate editor calls (directory openings
99 and closures) needed to navigate between each successive path. For
100 each path, we replay the sorted actions that occurred at that path.
102 When we've finished the editor drive, we should have fully replayed
103 the filesystem events that occurred in that revision or transaction
104 (though not necessarily in the same order in which they
107 /* #define USE_EV2_IMPL */
110 /*** Helper functions. ***/
113 /* Information for an active copy, that is a directory which we are currently
114 working on and which was added with history. */
117 /* Destination relpath (relative to the root of the . */
120 /* Copy source path (expressed as an absolute FS path) or revision.
121 NULL and SVN_INVALID_REVNUM if this is an add without history,
122 nested inside an add with history. */
123 const char *copyfrom_path;
124 svn_revnum_t copyfrom_rev;
127 struct path_driver_cb_baton
129 const svn_delta_editor_t *editor;
132 /* The root of the revision we're replaying. */
135 /* The root of the previous revision. If this is non-NULL it means that
136 we are supposed to generate props and text deltas relative to it. */
137 svn_fs_root_t *compare_root;
139 apr_hash_t *changed_paths;
141 svn_repos_authz_func_t authz_read_func;
142 void *authz_read_baton;
144 const char *base_path; /* relpath */
146 svn_revnum_t low_water_mark;
147 /* Stack of active copy operations. */
148 apr_array_header_t *copies;
150 /* The global pool for this replay operation. */
154 /* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
155 the appropriate editor calls to add it and its children without any
156 history. This is meant to be used when either a subset of the tree
157 has been ignored and we need to copy something from that subset to
158 the part of the tree we do care about, or if a subset of the tree is
159 unavailable because of authz and we need to use it as the source of
162 add_subdir(svn_fs_root_t *source_root,
163 svn_fs_root_t *target_root,
164 const svn_delta_editor_t *editor,
166 const char *edit_path,
168 const char *source_fspath,
169 svn_repos_authz_func_t authz_read_func,
170 void *authz_read_baton,
171 apr_hash_t *changed_paths,
175 apr_pool_t *subpool = svn_pool_create(pool);
176 apr_hash_index_t *hi, *phi;
180 SVN_ERR(editor->add_directory(edit_path, parent_baton, NULL,
181 SVN_INVALID_REVNUM, pool, dir_baton));
183 SVN_ERR(svn_fs_node_proplist(&props, target_root, edit_path, pool));
185 for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
187 const char *key = apr_hash_this_key(phi);
188 svn_string_t *val = apr_hash_this_val(phi);
190 svn_pool_clear(subpool);
191 SVN_ERR(editor->change_dir_prop(*dir_baton, key, val, subpool));
194 /* We have to get the dirents from the source path, not the target,
195 because we want nested copies from *readable* paths to be handled by
196 path_driver_cb_func, not add_subdir (in order to preserve history). */
197 SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath, pool));
199 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
201 svn_fs_path_change2_t *change;
202 svn_boolean_t readable = TRUE;
203 svn_fs_dirent_t *dent = apr_hash_this_val(hi);
204 const char *copyfrom_path = NULL;
205 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
206 const char *new_edit_path;
208 svn_pool_clear(subpool);
210 new_edit_path = svn_relpath_join(edit_path, dent->name, subpool);
212 /* If a file or subdirectory of the copied directory is listed as a
213 changed path (because it was modified after the copy but before the
214 commit), we remove it from the changed_paths hash so that future
215 calls to path_driver_cb_func will ignore it. */
216 change = svn_hash_gets(changed_paths, new_edit_path);
219 svn_hash_sets(changed_paths, new_edit_path, NULL);
221 /* If it's a delete, skip this entry. */
222 if (change->change_kind == svn_fs_path_change_delete)
225 /* If it's a replacement, check for copyfrom info (if we
226 don't have it already. */
227 if (change->change_kind == svn_fs_path_change_replace)
229 if (! change->copyfrom_known)
231 SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
232 &change->copyfrom_path,
233 target_root, new_edit_path, pool));
234 change->copyfrom_known = TRUE;
236 copyfrom_path = change->copyfrom_path;
237 copyfrom_rev = change->copyfrom_rev;
242 SVN_ERR(authz_read_func(&readable, target_root, new_edit_path,
243 authz_read_baton, pool));
248 if (dent->kind == svn_node_dir)
250 svn_fs_root_t *new_source_root;
251 const char *new_source_fspath;
256 svn_fs_t *fs = svn_fs_root_fs(source_root);
257 SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
258 copyfrom_rev, pool));
259 new_source_fspath = copyfrom_path;
263 new_source_root = source_root;
264 new_source_fspath = svn_fspath__join(source_fspath, dent->name,
268 /* ### authz considerations?
270 * I think not; when path_driver_cb_func() calls add_subdir(), it
271 * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
273 if (change && change->change_kind == svn_fs_path_change_replace
274 && copyfrom_path == NULL)
276 SVN_ERR(editor->add_directory(new_edit_path, *dir_baton,
277 NULL, SVN_INVALID_REVNUM,
278 subpool, &new_dir_baton));
282 SVN_ERR(add_subdir(new_source_root, target_root,
283 editor, edit_baton, new_edit_path,
284 *dir_baton, new_source_fspath,
285 authz_read_func, authz_read_baton,
286 changed_paths, subpool, &new_dir_baton));
289 SVN_ERR(editor->close_directory(new_dir_baton, subpool));
291 else if (dent->kind == svn_node_file)
293 svn_txdelta_window_handler_t delta_handler;
294 void *delta_handler_baton, *file_baton;
295 svn_txdelta_stream_t *delta_stream;
296 svn_checksum_t *checksum;
298 SVN_ERR(editor->add_file(new_edit_path, *dir_baton, NULL,
299 SVN_INVALID_REVNUM, pool, &file_baton));
301 SVN_ERR(svn_fs_node_proplist(&props, target_root,
302 new_edit_path, subpool));
304 for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
306 const char *key = apr_hash_this_key(phi);
307 svn_string_t *val = apr_hash_this_val(phi);
309 SVN_ERR(editor->change_file_prop(file_baton, key, val, subpool));
312 SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool,
314 &delta_handler_baton));
316 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, NULL, NULL,
317 target_root, new_edit_path,
320 SVN_ERR(svn_txdelta_send_txstream(delta_stream,
325 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, target_root,
326 new_edit_path, TRUE, pool));
327 SVN_ERR(editor->close_file(file_baton,
328 svn_checksum_to_cstring(checksum, pool),
332 SVN_ERR_MALFUNCTION();
335 svn_pool_destroy(subpool);
340 /* Given PATH deleted under ROOT, return in READABLE whether the path was
341 readable prior to the deletion. Consult COPIES (a stack of 'struct
342 copy_info') and AUTHZ_READ_FUNC. */
344 was_readable(svn_boolean_t *readable,
347 apr_array_header_t *copies,
348 svn_repos_authz_func_t authz_read_func,
349 void *authz_read_baton,
350 apr_pool_t *result_pool,
351 apr_pool_t *scratch_pool)
353 svn_fs_root_t *inquire_root;
354 const char *inquire_path;
355 struct copy_info *info = NULL;
359 if (! authz_read_func)
365 if (copies->nelts != 0)
366 info = APR_ARRAY_IDX(copies, copies->nelts - 1, struct copy_info *);
368 /* Are we under a copy? */
369 if (info && (relpath = svn_relpath_skip_ancestor(info->path, path)))
371 SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root),
372 info->copyfrom_rev, scratch_pool));
373 inquire_path = svn_fspath__join(info->copyfrom_path, relpath,
378 /* Compute the revision that ROOT is based on. (Note that ROOT is not
379 r0's root, since this function is only called for deletions.)
380 ### Need a more succinct way to express this */
381 svn_revnum_t inquire_rev = SVN_INVALID_REVNUM;
382 if (svn_fs_is_txn_root(root))
383 inquire_rev = svn_fs_txn_root_base_revision(root);
384 if (svn_fs_is_revision_root(root))
385 inquire_rev = svn_fs_revision_root_revision(root)-1;
386 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(inquire_rev));
388 SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root),
389 inquire_rev, scratch_pool));
393 SVN_ERR(authz_read_func(readable, inquire_root, inquire_path,
394 authz_read_baton, result_pool));
399 /* Initialize COPYFROM_ROOT, COPYFROM_PATH, and COPYFROM_REV with the
400 revision root, fspath, and revnum of the copyfrom of CHANGE, which
401 corresponds to PATH under ROOT. If the copyfrom info is valid
402 (i.e., is not (NULL, SVN_INVALID_REVNUM)), then initialize SRC_READABLE
403 too, consulting AUTHZ_READ_FUNC and AUTHZ_READ_BATON if provided.
405 NOTE: If the copyfrom information in CHANGE is marked as unknown
406 (meaning, its ->copyfrom_rev and ->copyfrom_path cannot be
407 trusted), this function will also update those members of the
408 CHANGE structure to carry accurate copyfrom information. */
410 fill_copyfrom(svn_fs_root_t **copyfrom_root,
411 const char **copyfrom_path,
412 svn_revnum_t *copyfrom_rev,
413 svn_boolean_t *src_readable,
415 svn_fs_path_change2_t *change,
416 svn_repos_authz_func_t authz_read_func,
417 void *authz_read_baton,
419 apr_pool_t *result_pool,
420 apr_pool_t *scratch_pool)
422 if (! change->copyfrom_known)
424 SVN_ERR(svn_fs_copied_from(&(change->copyfrom_rev),
425 &(change->copyfrom_path),
426 root, path, result_pool));
427 change->copyfrom_known = TRUE;
429 *copyfrom_rev = change->copyfrom_rev;
430 *copyfrom_path = change->copyfrom_path;
432 if (*copyfrom_path && SVN_IS_VALID_REVNUM(*copyfrom_rev))
434 SVN_ERR(svn_fs_revision_root(copyfrom_root,
435 svn_fs_root_fs(root),
436 *copyfrom_rev, result_pool));
440 SVN_ERR(authz_read_func(src_readable, *copyfrom_root,
442 authz_read_baton, result_pool));
445 *src_readable = TRUE;
449 *copyfrom_root = NULL;
450 /* SRC_READABLE left uninitialized */
456 path_driver_cb_func(void **dir_baton,
458 void *callback_baton,
459 const char *edit_path,
462 struct path_driver_cb_baton *cb = callback_baton;
463 const svn_delta_editor_t *editor = cb->editor;
464 void *edit_baton = cb->edit_baton;
465 svn_fs_root_t *root = cb->root;
466 svn_fs_path_change2_t *change;
467 svn_boolean_t do_add = FALSE, do_delete = FALSE;
468 void *file_baton = NULL;
469 svn_revnum_t copyfrom_rev;
470 const char *copyfrom_path;
471 svn_fs_root_t *source_root = cb->compare_root;
472 const char *source_fspath = NULL;
473 const char *base_path = cb->base_path;
477 /* Initialize SOURCE_FSPATH. */
479 source_fspath = svn_fspath__canonicalize(edit_path, pool);
481 /* First, flush the copies stack so it only contains ancestors of path. */
482 while (cb->copies->nelts > 0
483 && ! svn_dirent_is_ancestor(APR_ARRAY_IDX(cb->copies,
484 cb->copies->nelts - 1,
485 struct copy_info *)->path,
487 apr_array_pop(cb->copies);
489 change = svn_hash_gets(cb->changed_paths, edit_path);
492 /* This can only happen if the path was removed from cb->changed_paths
493 by an earlier call to add_subdir, which means the path was already
494 handled and we should simply ignore it. */
497 switch (change->change_kind)
499 case svn_fs_path_change_add:
503 case svn_fs_path_change_delete:
507 case svn_fs_path_change_replace:
512 case svn_fs_path_change_modify:
518 /* Handle any deletions. */
521 svn_boolean_t readable;
523 /* Issue #4121: delete under under a copy, of a path that was unreadable
524 at its pre-copy location. */
525 SVN_ERR(was_readable(&readable, root, edit_path, cb->copies,
526 cb->authz_read_func, cb->authz_read_baton,
529 SVN_ERR(editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
530 parent_baton, pool));
533 /* Fetch the node kind if it makes sense to do so. */
534 if (! do_delete || do_add)
536 if (change->node_kind == svn_node_unknown)
537 SVN_ERR(svn_fs_check_path(&(change->node_kind), root, edit_path, pool));
538 if ((change->node_kind != svn_node_dir) &&
539 (change->node_kind != svn_node_file))
540 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
541 _("Filesystem path '%s' is neither a file "
542 "nor a directory"), edit_path);
545 /* Handle any adds/opens. */
548 svn_boolean_t src_readable;
549 svn_fs_root_t *copyfrom_root;
551 /* E.g. when verifying corrupted repositories, their changed path
552 lists may contain an ADD for "/". The delta path driver will
553 call us with a NULL parent in that case. */
555 return svn_error_create(SVN_ERR_FS_ALREADY_EXISTS, NULL,
556 _("Root directory already exists."));
558 /* A NULL parent_baton will cause a segfault. It should never be
559 NULL for non-root paths. */
560 SVN_ERR_ASSERT(parent_baton);
562 /* Was this node copied? */
563 SVN_ERR(fill_copyfrom(©from_root, ©from_path, ©from_rev,
564 &src_readable, root, change,
565 cb->authz_read_func, cb->authz_read_baton,
566 edit_path, pool, pool));
568 /* If we have a copyfrom path, and we can't read it or we're just
569 ignoring it, or the copyfrom rev is prior to the low water mark
570 then we just null them out and do a raw add with no history at
574 || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL)
575 || (cb->low_water_mark > copyfrom_rev)))
577 copyfrom_path = NULL;
578 copyfrom_rev = SVN_INVALID_REVNUM;
581 /* Do the right thing based on the path KIND. */
582 if (change->node_kind == svn_node_dir)
584 /* If this is a copy, but we can't represent it as such,
585 then we just do a recursive add of the source path
587 if (change->copyfrom_path && ! copyfrom_path)
589 SVN_ERR(add_subdir(copyfrom_root, root, editor, edit_baton,
590 edit_path, parent_baton, change->copyfrom_path,
591 cb->authz_read_func, cb->authz_read_baton,
592 cb->changed_paths, pool, dir_baton));
596 SVN_ERR(editor->add_directory(edit_path, parent_baton,
597 copyfrom_path, copyfrom_rev,
603 SVN_ERR(editor->add_file(edit_path, parent_baton, copyfrom_path,
604 copyfrom_rev, pool, &file_baton));
607 /* If we represent this as a copy... */
610 /* If it is a directory, make sure descendants get the correct
611 delta source by remembering that we are operating inside a
612 (possibly nested) copy operation. */
613 if (change->node_kind == svn_node_dir)
615 struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
617 info->path = apr_pstrdup(cb->pool, edit_path);
618 info->copyfrom_path = apr_pstrdup(cb->pool, copyfrom_path);
619 info->copyfrom_rev = copyfrom_rev;
621 APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
624 /* Save the source so that we can use it later, when we
625 need to generate text and prop deltas. */
626 source_root = copyfrom_root;
627 source_fspath = copyfrom_path;
630 /* Else, we are an add without history... */
632 /* If an ancestor is added with history, we need to forget about
633 that here, go on with life and repeat all the mistakes of our
635 if (change->node_kind == svn_node_dir && cb->copies->nelts > 0)
637 struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
639 info->path = apr_pstrdup(cb->pool, edit_path);
640 info->copyfrom_path = NULL;
641 info->copyfrom_rev = SVN_INVALID_REVNUM;
643 APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
646 source_fspath = NULL;
649 else if (! do_delete)
651 /* Do the right thing based on the path KIND (and the presence
652 of a PARENT_BATON). */
653 if (change->node_kind == svn_node_dir)
657 SVN_ERR(editor->open_directory(edit_path, parent_baton,
663 SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
669 SVN_ERR(editor->open_file(edit_path, parent_baton, SVN_INVALID_REVNUM,
672 /* If we are inside an add with history, we need to adjust the
674 if (cb->copies->nelts > 0)
676 struct copy_info *info = APR_ARRAY_IDX(cb->copies,
677 cb->copies->nelts - 1,
679 if (info->copyfrom_path)
681 const char *relpath = svn_relpath_skip_ancestor(info->path,
683 SVN_ERR_ASSERT(relpath && *relpath);
684 SVN_ERR(svn_fs_revision_root(&source_root,
685 svn_fs_root_fs(root),
686 info->copyfrom_rev, pool));
687 source_fspath = svn_fspath__join(info->copyfrom_path,
692 /* This is an add without history, nested inside an
693 add with history. We have no delta source in this case. */
695 source_fspath = NULL;
700 if (! do_delete || do_add)
702 /* Is this a copy that was downgraded to a raw add? (If so,
703 we'll need to transmit properties and file contents and such
704 for it regardless of what the CHANGE structure's text_mod
705 and prop_mod flags say.) */
706 svn_boolean_t downgraded_copy = (change->copyfrom_known
707 && change->copyfrom_path
708 && (! copyfrom_path));
710 /* Handle property modifications. */
711 if (change->prop_mod || downgraded_copy)
713 if (cb->compare_root)
715 apr_array_header_t *prop_diffs;
716 apr_hash_t *old_props;
717 apr_hash_t *new_props;
721 SVN_ERR(svn_fs_node_proplist(&old_props, source_root,
722 source_fspath, pool));
724 old_props = apr_hash_make(pool);
726 SVN_ERR(svn_fs_node_proplist(&new_props, root, edit_path, pool));
728 SVN_ERR(svn_prop_diffs(&prop_diffs, new_props, old_props,
731 for (i = 0; i < prop_diffs->nelts; ++i)
733 svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
734 if (change->node_kind == svn_node_dir)
735 SVN_ERR(editor->change_dir_prop(*dir_baton, pc->name,
737 else if (change->node_kind == svn_node_file)
738 SVN_ERR(editor->change_file_prop(file_baton, pc->name,
744 /* Just do a dummy prop change to signal that there are *any*
746 if (change->node_kind == svn_node_dir)
747 SVN_ERR(editor->change_dir_prop(*dir_baton, "", NULL,
749 else if (change->node_kind == svn_node_file)
750 SVN_ERR(editor->change_file_prop(file_baton, "", NULL,
755 /* Handle textual modifications. */
756 if (change->node_kind == svn_node_file
757 && (change->text_mod || downgraded_copy))
759 svn_txdelta_window_handler_t delta_handler;
760 void *delta_handler_baton;
761 const char *hex_digest = NULL;
763 if (cb->compare_root && source_root && source_fspath)
765 svn_checksum_t *checksum;
766 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
767 source_root, source_fspath, TRUE,
769 hex_digest = svn_checksum_to_cstring(checksum, pool);
772 SVN_ERR(editor->apply_textdelta(file_baton, hex_digest, pool,
774 &delta_handler_baton));
775 if (cb->compare_root)
777 svn_txdelta_stream_t *delta_stream;
779 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, source_root,
782 SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
783 delta_handler_baton, pool));
786 SVN_ERR(delta_handler(NULL, delta_handler_baton));
790 /* Close the file baton if we opened it. */
793 svn_checksum_t *checksum;
794 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, edit_path,
796 SVN_ERR(editor->close_file(file_baton,
797 svn_checksum_to_cstring(checksum, pool),
806 fetch_kind_func(svn_node_kind_t *kind,
809 svn_revnum_t base_revision,
810 apr_pool_t *scratch_pool)
812 svn_fs_root_t *root = baton;
813 svn_fs_root_t *prev_root;
814 svn_fs_t *fs = svn_fs_root_fs(root);
816 if (!SVN_IS_VALID_REVNUM(base_revision))
817 base_revision = svn_fs_revision_root_revision(root) - 1;
819 SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
820 SVN_ERR(svn_fs_check_path(kind, prev_root, path, scratch_pool));
826 fetch_props_func(apr_hash_t **props,
829 svn_revnum_t base_revision,
830 apr_pool_t *result_pool,
831 apr_pool_t *scratch_pool)
833 svn_fs_root_t *root = baton;
834 svn_fs_root_t *prev_root;
835 svn_fs_t *fs = svn_fs_root_fs(root);
837 if (!SVN_IS_VALID_REVNUM(base_revision))
838 base_revision = svn_fs_revision_root_revision(root) - 1;
840 SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
841 SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool));
851 svn_repos_replay2(svn_fs_root_t *root,
852 const char *base_path,
853 svn_revnum_t low_water_mark,
854 svn_boolean_t send_deltas,
855 const svn_delta_editor_t *editor,
857 svn_repos_authz_func_t authz_read_func,
858 void *authz_read_baton,
862 apr_hash_t *fs_changes;
863 apr_hash_t *changed_paths;
864 apr_hash_index_t *hi;
865 apr_array_header_t *paths;
866 struct path_driver_cb_baton cb_baton;
868 /* Special-case r0, which we know is an empty revision; if we don't
869 special-case it we might end up trying to compare it to "r-1". */
870 if (svn_fs_is_revision_root(root) && svn_fs_revision_root_revision(root) == 0)
872 SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
876 /* Fetch the paths changed under ROOT. */
877 SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, pool));
881 else if (base_path[0] == '/')
884 /* Make an array from the keys of our CHANGED_PATHS hash, and copy
885 the values into a new hash whose keys have no leading slashes. */
886 paths = apr_array_make(pool, apr_hash_count(fs_changes),
887 sizeof(const char *));
888 changed_paths = apr_hash_make(pool);
889 for (hi = apr_hash_first(pool, fs_changes); hi; hi = apr_hash_next(hi))
891 const char *path = apr_hash_this_key(hi);
892 apr_ssize_t keylen = apr_hash_this_key_len(hi);
893 svn_fs_path_change2_t *change = apr_hash_this_val(hi);
894 svn_boolean_t allowed = TRUE;
897 SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
908 /* If the base_path doesn't match the top directory of this path
909 we don't want anything to do with it... */
910 if (svn_relpath_skip_ancestor(base_path, path) != NULL)
912 APR_ARRAY_PUSH(paths, const char *) = path;
913 apr_hash_set(changed_paths, path, keylen, change);
915 /* ...unless this was a change to one of the parent directories of
917 else if (svn_relpath_skip_ancestor(path, base_path) != NULL)
919 APR_ARRAY_PUSH(paths, const char *) = path;
920 apr_hash_set(changed_paths, path, keylen, change);
925 /* If we were not given a low water mark, assume that everything is there,
926 all the way back to revision 0. */
927 if (! SVN_IS_VALID_REVNUM(low_water_mark))
930 /* Initialize our callback baton. */
931 cb_baton.editor = editor;
932 cb_baton.edit_baton = edit_baton;
933 cb_baton.root = root;
934 cb_baton.changed_paths = changed_paths;
935 cb_baton.authz_read_func = authz_read_func;
936 cb_baton.authz_read_baton = authz_read_baton;
937 cb_baton.base_path = base_path;
938 cb_baton.low_water_mark = low_water_mark;
939 cb_baton.compare_root = NULL;
943 SVN_ERR(svn_fs_revision_root(&cb_baton.compare_root,
944 svn_fs_root_fs(root),
945 svn_fs_is_revision_root(root)
946 ? svn_fs_revision_root_revision(root) - 1
947 : svn_fs_txn_root_base_revision(root),
951 cb_baton.copies = apr_array_make(pool, 4, sizeof(struct copy_info *));
952 cb_baton.pool = pool;
954 /* Determine the revision to use throughout the edit, and call
955 EDITOR's set_target_revision() function. */
956 if (svn_fs_is_revision_root(root))
958 svn_revnum_t revision = svn_fs_revision_root_revision(root);
959 SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
962 /* Call the path-based editor driver. */
963 return svn_delta_path_driver2(editor, edit_baton,
965 path_driver_cb_func, &cb_baton, pool);
967 svn_editor_t *editorv2;
968 struct svn_delta__extra_baton *exb;
969 svn_delta__unlock_func_t unlock_func;
970 svn_boolean_t send_abs_paths;
971 const char *repos_root = "";
974 /* Special-case r0, which we know is an empty revision; if we don't
975 special-case it we might end up trying to compare it to "r-1". */
976 if (svn_fs_is_revision_root(root)
977 && svn_fs_revision_root_revision(root) == 0)
979 SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
983 /* Determine the revision to use throughout the edit, and call
984 EDITOR's set_target_revision() function. */
985 if (svn_fs_is_revision_root(root))
987 svn_revnum_t revision = svn_fs_revision_root_revision(root);
988 SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
993 else if (base_path[0] == '/')
996 /* Use the shim to convert our editor to an Ev2 editor, and pass it down
998 SVN_ERR(svn_delta__editor_from_delta(&editorv2, &exb,
999 &unlock_func, &unlock_baton,
1004 fetch_kind_func, root,
1005 fetch_props_func, root,
1008 /* Tell the shim that we're starting the process. */
1009 SVN_ERR(exb->start_edit(exb->baton, svn_fs_revision_root_revision(root)));
1011 /* ### We're ignoring SEND_DELTAS here. */
1012 SVN_ERR(svn_repos__replay_ev2(root, base_path, low_water_mark,
1013 editorv2, authz_read_func, authz_read_baton,
1016 return SVN_NO_ERROR;
1021 /*****************************************************************
1022 * Ev2 Implementation *
1023 *****************************************************************/
1025 /* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
1026 the appropriate editor calls to add it and its children without any
1027 history. This is meant to be used when either a subset of the tree
1028 has been ignored and we need to copy something from that subset to
1029 the part of the tree we do care about, or if a subset of the tree is
1030 unavailable because of authz and we need to use it as the source of
1032 static svn_error_t *
1033 add_subdir_ev2(svn_fs_root_t *source_root,
1034 svn_fs_root_t *target_root,
1035 svn_editor_t *editor,
1036 const char *repos_relpath,
1037 const char *source_fspath,
1038 svn_repos_authz_func_t authz_read_func,
1039 void *authz_read_baton,
1040 apr_hash_t *changed_paths,
1041 apr_pool_t *result_pool,
1042 apr_pool_t *scratch_pool)
1044 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1045 apr_hash_index_t *hi;
1046 apr_hash_t *dirents;
1047 apr_hash_t *props = NULL;
1048 apr_array_header_t *children = NULL;
1050 SVN_ERR(svn_fs_node_proplist(&props, target_root, repos_relpath,
1053 SVN_ERR(svn_editor_add_directory(editor, repos_relpath, children,
1054 props, SVN_INVALID_REVNUM));
1056 /* We have to get the dirents from the source path, not the target,
1057 because we want nested copies from *readable* paths to be handled by
1058 path_driver_cb_func, not add_subdir (in order to preserve history). */
1059 SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath,
1062 for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
1064 svn_fs_path_change2_t *change;
1065 svn_boolean_t readable = TRUE;
1066 svn_fs_dirent_t *dent = apr_hash_this_val(hi);
1067 const char *copyfrom_path = NULL;
1068 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
1069 const char *child_relpath;
1071 svn_pool_clear(iterpool);
1073 child_relpath = svn_relpath_join(repos_relpath, dent->name, iterpool);
1075 /* If a file or subdirectory of the copied directory is listed as a
1076 changed path (because it was modified after the copy but before the
1077 commit), we remove it from the changed_paths hash so that future
1078 calls to path_driver_cb_func will ignore it. */
1079 change = svn_hash_gets(changed_paths, child_relpath);
1082 svn_hash_sets(changed_paths, child_relpath, NULL);
1084 /* If it's a delete, skip this entry. */
1085 if (change->change_kind == svn_fs_path_change_delete)
1088 /* If it's a replacement, check for copyfrom info (if we
1089 don't have it already. */
1090 if (change->change_kind == svn_fs_path_change_replace)
1092 if (! change->copyfrom_known)
1094 SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
1095 &change->copyfrom_path,
1096 target_root, child_relpath,
1098 change->copyfrom_known = TRUE;
1100 copyfrom_path = change->copyfrom_path;
1101 copyfrom_rev = change->copyfrom_rev;
1105 if (authz_read_func)
1106 SVN_ERR(authz_read_func(&readable, target_root, child_relpath,
1107 authz_read_baton, iterpool));
1112 if (dent->kind == svn_node_dir)
1114 svn_fs_root_t *new_source_root;
1115 const char *new_source_fspath;
1119 svn_fs_t *fs = svn_fs_root_fs(source_root);
1120 SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
1121 copyfrom_rev, result_pool));
1122 new_source_fspath = copyfrom_path;
1126 new_source_root = source_root;
1127 new_source_fspath = svn_fspath__join(source_fspath, dent->name,
1131 /* ### authz considerations?
1133 * I think not; when path_driver_cb_func() calls add_subdir(), it
1134 * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
1136 if (change && change->change_kind == svn_fs_path_change_replace
1137 && copyfrom_path == NULL)
1139 SVN_ERR(svn_editor_add_directory(editor, child_relpath,
1141 SVN_INVALID_REVNUM));
1145 SVN_ERR(add_subdir_ev2(new_source_root, target_root,
1146 editor, child_relpath,
1148 authz_read_func, authz_read_baton,
1149 changed_paths, result_pool, iterpool));
1152 else if (dent->kind == svn_node_file)
1154 svn_checksum_t *checksum;
1155 svn_stream_t *contents;
1157 SVN_ERR(svn_fs_node_proplist(&props, target_root,
1158 child_relpath, iterpool));
1160 SVN_ERR(svn_fs_file_contents(&contents, target_root,
1161 child_relpath, iterpool));
1163 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1165 child_relpath, TRUE, iterpool));
1167 SVN_ERR(svn_editor_add_file(editor, child_relpath, checksum,
1168 contents, props, SVN_INVALID_REVNUM));
1171 SVN_ERR_MALFUNCTION();
1174 svn_pool_destroy(iterpool);
1176 return SVN_NO_ERROR;
1179 static svn_error_t *
1180 replay_node(svn_fs_root_t *root,
1181 const char *repos_relpath,
1182 svn_editor_t *editor,
1183 svn_revnum_t low_water_mark,
1184 const char *base_repos_relpath,
1185 apr_array_header_t *copies,
1186 apr_hash_t *changed_paths,
1187 svn_repos_authz_func_t authz_read_func,
1188 void *authz_read_baton,
1189 apr_pool_t *result_pool,
1190 apr_pool_t *scratch_pool)
1192 svn_fs_path_change2_t *change;
1193 svn_boolean_t do_add = FALSE;
1194 svn_boolean_t do_delete = FALSE;
1195 svn_revnum_t copyfrom_rev;
1196 const char *copyfrom_path;
1197 svn_revnum_t replaces_rev;
1199 /* First, flush the copies stack so it only contains ancestors of path. */
1200 while (copies->nelts > 0
1201 && (svn_relpath_skip_ancestor(APR_ARRAY_IDX(copies,
1203 struct copy_info *)->path,
1204 repos_relpath) == NULL) )
1205 apr_array_pop(copies);
1207 change = svn_hash_gets(changed_paths, repos_relpath);
1210 /* This can only happen if the path was removed from changed_paths
1211 by an earlier call to add_subdir, which means the path was already
1212 handled and we should simply ignore it. */
1213 return SVN_NO_ERROR;
1215 switch (change->change_kind)
1217 case svn_fs_path_change_add:
1221 case svn_fs_path_change_delete:
1225 case svn_fs_path_change_replace:
1230 case svn_fs_path_change_modify:
1236 /* Handle any deletions. */
1237 if (do_delete && ! do_add)
1239 svn_boolean_t readable;
1241 /* Issue #4121: delete under under a copy, of a path that was unreadable
1242 at its pre-copy location. */
1243 SVN_ERR(was_readable(&readable, root, repos_relpath, copies,
1244 authz_read_func, authz_read_baton,
1245 scratch_pool, scratch_pool));
1247 SVN_ERR(svn_editor_delete(editor, repos_relpath, SVN_INVALID_REVNUM));
1249 return SVN_NO_ERROR;
1252 /* Handle replacements. */
1253 if (do_delete && do_add)
1254 replaces_rev = svn_fs_revision_root_revision(root);
1256 replaces_rev = SVN_INVALID_REVNUM;
1258 /* Fetch the node kind if it makes sense to do so. */
1259 if (! do_delete || do_add)
1261 if (change->node_kind == svn_node_unknown)
1262 SVN_ERR(svn_fs_check_path(&(change->node_kind), root, repos_relpath,
1264 if ((change->node_kind != svn_node_dir) &&
1265 (change->node_kind != svn_node_file))
1266 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1267 _("Filesystem path '%s' is neither a file "
1268 "nor a directory"), repos_relpath);
1271 /* Handle any adds/opens. */
1274 svn_boolean_t src_readable;
1275 svn_fs_root_t *copyfrom_root;
1277 /* Was this node copied? */
1278 SVN_ERR(fill_copyfrom(©from_root, ©from_path, ©from_rev,
1279 &src_readable, root, change,
1280 authz_read_func, authz_read_baton,
1281 repos_relpath, scratch_pool, scratch_pool));
1283 /* If we have a copyfrom path, and we can't read it or we're just
1284 ignoring it, or the copyfrom rev is prior to the low water mark
1285 then we just null them out and do a raw add with no history at
1288 && ((! src_readable)
1289 || (svn_relpath_skip_ancestor(base_repos_relpath,
1290 copyfrom_path + 1) == NULL)
1291 || (low_water_mark > copyfrom_rev)))
1293 copyfrom_path = NULL;
1294 copyfrom_rev = SVN_INVALID_REVNUM;
1297 /* Do the right thing based on the path KIND. */
1298 if (change->node_kind == svn_node_dir)
1300 /* If this is a copy, but we can't represent it as such,
1301 then we just do a recursive add of the source path
1303 if (change->copyfrom_path && ! copyfrom_path)
1305 SVN_ERR(add_subdir_ev2(copyfrom_root, root, editor,
1306 repos_relpath, change->copyfrom_path,
1307 authz_read_func, authz_read_baton,
1308 changed_paths, result_pool,
1315 if (copyfrom_path[0] == '/')
1317 SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
1318 repos_relpath, replaces_rev));
1322 apr_array_header_t *children;
1324 apr_hash_t *dirents;
1326 SVN_ERR(svn_fs_dir_entries(&dirents, root, repos_relpath,
1328 SVN_ERR(svn_hash_keys(&children, dirents, scratch_pool));
1330 SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1333 SVN_ERR(svn_editor_add_directory(editor, repos_relpath,
1343 if (copyfrom_path[0] == '/')
1345 SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
1346 repos_relpath, replaces_rev));
1351 svn_checksum_t *checksum;
1352 svn_stream_t *contents;
1354 SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1357 SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
1360 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root,
1361 repos_relpath, TRUE, scratch_pool));
1363 SVN_ERR(svn_editor_add_file(editor, repos_relpath, checksum,
1364 contents, props, replaces_rev));
1368 /* If we represent this as a copy... */
1371 /* If it is a directory, make sure descendants get the correct
1372 delta source by remembering that we are operating inside a
1373 (possibly nested) copy operation. */
1374 if (change->node_kind == svn_node_dir)
1376 struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
1378 info->path = apr_pstrdup(result_pool, repos_relpath);
1379 info->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
1380 info->copyfrom_rev = copyfrom_rev;
1382 APR_ARRAY_PUSH(copies, struct copy_info *) = info;
1386 /* Else, we are an add without history... */
1388 /* If an ancestor is added with history, we need to forget about
1389 that here, go on with life and repeat all the mistakes of our
1391 if (change->node_kind == svn_node_dir && copies->nelts > 0)
1393 struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
1395 info->path = apr_pstrdup(result_pool, repos_relpath);
1396 info->copyfrom_path = NULL;
1397 info->copyfrom_rev = SVN_INVALID_REVNUM;
1399 APR_ARRAY_PUSH(copies, struct copy_info *) = info;
1403 else if (! do_delete)
1405 /* If we are inside an add with history, we need to adjust the
1407 if (copies->nelts > 0)
1409 struct copy_info *info = APR_ARRAY_IDX(copies,
1411 struct copy_info *);
1412 if (info->copyfrom_path)
1414 const char *relpath = svn_relpath_skip_ancestor(info->path,
1416 SVN_ERR_ASSERT(relpath && *relpath);
1417 repos_relpath = svn_relpath_join(info->copyfrom_path,
1418 relpath, scratch_pool);
1423 if (! do_delete && !do_add)
1425 apr_hash_t *props = NULL;
1427 /* Is this a copy that was downgraded to a raw add? (If so,
1428 we'll need to transmit properties and file contents and such
1429 for it regardless of what the CHANGE structure's text_mod
1430 and prop_mod flags say.) */
1431 svn_boolean_t downgraded_copy = (change->copyfrom_known
1432 && change->copyfrom_path
1433 && (! copyfrom_path));
1435 /* Handle property modifications. */
1436 if (change->prop_mod || downgraded_copy)
1438 SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1442 /* Handle textual modifications. */
1443 if (change->node_kind == svn_node_file
1444 && (change->text_mod || change->prop_mod || downgraded_copy))
1446 svn_checksum_t *checksum = NULL;
1447 svn_stream_t *contents = NULL;
1449 if (change->text_mod)
1451 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1452 root, repos_relpath, TRUE,
1455 SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
1459 SVN_ERR(svn_editor_alter_file(editor, repos_relpath,
1461 checksum, contents, props));
1464 if (change->node_kind == svn_node_dir
1465 && (change->prop_mod || downgraded_copy))
1467 apr_array_header_t *children = NULL;
1469 SVN_ERR(svn_editor_alter_directory(editor, repos_relpath,
1470 SVN_INVALID_REVNUM, children,
1475 return SVN_NO_ERROR;
1479 svn_repos__replay_ev2(svn_fs_root_t *root,
1480 const char *base_repos_relpath,
1481 svn_revnum_t low_water_mark,
1482 svn_editor_t *editor,
1483 svn_repos_authz_func_t authz_read_func,
1484 void *authz_read_baton,
1485 apr_pool_t *scratch_pool)
1487 apr_hash_t *fs_changes;
1488 apr_hash_t *changed_paths;
1489 apr_hash_index_t *hi;
1490 apr_array_header_t *paths;
1491 apr_array_header_t *copies;
1492 apr_pool_t *iterpool;
1493 svn_error_t *err = SVN_NO_ERROR;
1496 SVN_ERR_ASSERT(!svn_dirent_is_absolute(base_repos_relpath));
1498 /* Special-case r0, which we know is an empty revision; if we don't
1499 special-case it we might end up trying to compare it to "r-1". */
1500 if (svn_fs_is_revision_root(root)
1501 && svn_fs_revision_root_revision(root) == 0)
1503 return SVN_NO_ERROR;
1506 /* Fetch the paths changed under ROOT. */
1507 SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, scratch_pool));
1509 /* Make an array from the keys of our CHANGED_PATHS hash, and copy
1510 the values into a new hash whose keys have no leading slashes. */
1511 paths = apr_array_make(scratch_pool, apr_hash_count(fs_changes),
1512 sizeof(const char *));
1513 changed_paths = apr_hash_make(scratch_pool);
1514 for (hi = apr_hash_first(scratch_pool, fs_changes); hi;
1515 hi = apr_hash_next(hi))
1517 const char *path = apr_hash_this_key(hi);
1518 apr_ssize_t keylen = apr_hash_this_key_len(hi);
1519 svn_fs_path_change2_t *change = apr_hash_this_val(hi);
1520 svn_boolean_t allowed = TRUE;
1522 if (authz_read_func)
1523 SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
1534 /* If the base_path doesn't match the top directory of this path
1535 we don't want anything to do with it... */
1536 if (svn_relpath_skip_ancestor(base_repos_relpath, path) != NULL)
1538 APR_ARRAY_PUSH(paths, const char *) = path;
1539 apr_hash_set(changed_paths, path, keylen, change);
1541 /* ...unless this was a change to one of the parent directories of
1543 else if (svn_relpath_skip_ancestor(path, base_repos_relpath) != NULL)
1545 APR_ARRAY_PUSH(paths, const char *) = path;
1546 apr_hash_set(changed_paths, path, keylen, change);
1551 /* If we were not given a low water mark, assume that everything is there,
1552 all the way back to revision 0. */
1553 if (! SVN_IS_VALID_REVNUM(low_water_mark))
1556 copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *));
1558 /* Sort the paths. Although not strictly required by the API, this has
1559 the pleasant side effect of maintaining a consistent ordering of
1560 dumpfile contents. */
1561 svn_sort__array(paths, svn_sort_compare_paths);
1563 /* Now actually handle the various paths. */
1564 iterpool = svn_pool_create(scratch_pool);
1565 for (i = 0; i < paths->nelts; i++)
1567 const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *);
1569 svn_pool_clear(iterpool);
1570 err = replay_node(root, repos_relpath, editor, low_water_mark,
1571 base_repos_relpath, copies, changed_paths,
1572 authz_read_func, authz_read_baton,
1573 scratch_pool, iterpool);
1579 return svn_error_compose_create(err, svn_editor_abort(editor));
1581 SVN_ERR(svn_editor_complete(editor));
1583 svn_pool_destroy(iterpool);
1584 return SVN_NO_ERROR;