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"
46 /* The year was 2003. Subversion usage was rampant in the world, and
47 there was a rapidly growing issues database to prove it. To make
48 matters worse, svn_repos_dir_delta() had simply outgrown itself.
49 No longer content to simply describe the differences between two
50 trees, the function had been slowly bearing the added
51 responsibility of representing the actions that had been taken to
52 cause those differences -- a burden it was never meant to bear.
53 Now grown into a twisted mess of razor-sharp metal and glass, and
54 trembling with a sort of momentarily stayed spring force,
55 svn_repos_dir_delta was a timebomb poised for total annihilation of
58 Subversion needed a change.
60 Changes, in fact. And not just in the literary segue sense. What
61 Subversion desperately needed was a new mechanism solely
62 responsible for replaying repository actions back to some
63 interested party -- to translate and retransmit the contents of the
64 Berkeley 'changes' database file. */
68 /* The filesystem keeps a record of high-level actions that affect the
69 files and directories in itself. The 'changes' table records
70 additions, deletions, textual and property modifications, and so
71 on. The goal of the functions in this file is to examine those
72 change records, and use them to drive an editor interface in such a
73 way as to effectively replay those actions.
75 This is critically different than what svn_repos_dir_delta() was
76 designed to do. That function describes, in the simplest way it
77 can, how to transform one tree into another. It doesn't care
78 whether or not this was the same way a user might have done this
79 transformation. More to the point, it doesn't care if this is how
80 those differences *did* come into being. And it is for this reason
81 that it cannot be relied upon for tasks such as the repository
82 dumpfile-generation code, which is supposed to represent not
83 changes, but actions that cause changes.
85 So, what's the plan here?
87 First, we fetch the changes for a particular revision or
88 transaction. We get these as an array, sorted chronologically.
89 From this array we will build a hash, keyed on the path associated
90 with each change item, and whose values are arrays of changes made
91 to that path, again preserving the chronological ordering.
93 Once our hash is built, we then sort all the keys of the hash (the
94 paths) using a depth-first directory sort routine.
96 Finally, we drive an editor, moving down our list of sorted paths,
97 and manufacturing any intermediate editor calls (directory openings
98 and closures) needed to navigate between each successive path. For
99 each path, we replay the sorted actions that occurred at that path.
101 When we've finished the editor drive, we should have fully replayed
102 the filesystem events that occurred in that revision or transaction
103 (though not necessarily in the same order in which they
106 /* #define USE_EV2_IMPL */
109 /*** Helper functions. ***/
112 /* Information for an active copy, that is a directory which we are currently
113 working on and which was added with history. */
116 /* Destination relpath (relative to the root of the . */
119 /* Copy source path (expressed as an absolute FS path) or revision.
120 NULL and SVN_INVALID_REVNUM if this is an add without history,
121 nested inside an add with history. */
122 const char *copyfrom_path;
123 svn_revnum_t copyfrom_rev;
126 struct path_driver_cb_baton
128 const svn_delta_editor_t *editor;
131 /* The root of the revision we're replaying. */
134 /* The root of the previous revision. If this is non-NULL it means that
135 we are supposed to generate props and text deltas relative to it. */
136 svn_fs_root_t *compare_root;
138 apr_hash_t *changed_paths;
140 svn_repos_authz_func_t authz_read_func;
141 void *authz_read_baton;
143 const char *base_path; /* relpath */
145 svn_revnum_t low_water_mark;
146 /* Stack of active copy operations. */
147 apr_array_header_t *copies;
149 /* The global pool for this replay operation. */
153 /* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
154 the appropriate editor calls to add it and its children without any
155 history. This is meant to be used when either a subset of the tree
156 has been ignored and we need to copy something from that subset to
157 the part of the tree we do care about, or if a subset of the tree is
158 unavailable because of authz and we need to use it as the source of
161 add_subdir(svn_fs_root_t *source_root,
162 svn_fs_root_t *target_root,
163 const svn_delta_editor_t *editor,
165 const char *edit_path,
167 const char *source_fspath,
168 svn_repos_authz_func_t authz_read_func,
169 void *authz_read_baton,
170 apr_hash_t *changed_paths,
174 apr_pool_t *subpool = svn_pool_create(pool);
175 apr_hash_index_t *hi, *phi;
179 SVN_ERR(editor->add_directory(edit_path, parent_baton, NULL,
180 SVN_INVALID_REVNUM, pool, dir_baton));
182 SVN_ERR(svn_fs_node_proplist(&props, target_root, edit_path, pool));
184 for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
189 svn_pool_clear(subpool);
190 apr_hash_this(phi, &key, NULL, &val);
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;
204 const char *copyfrom_path = NULL;
205 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
206 const char *new_edit_path;
209 svn_pool_clear(subpool);
211 apr_hash_this(hi, NULL, NULL, &val);
215 new_edit_path = svn_relpath_join(edit_path, dent->name, subpool);
217 /* If a file or subdirectory of the copied directory is listed as a
218 changed path (because it was modified after the copy but before the
219 commit), we remove it from the changed_paths hash so that future
220 calls to path_driver_cb_func will ignore it. */
221 change = svn_hash_gets(changed_paths, new_edit_path);
224 svn_hash_sets(changed_paths, new_edit_path, NULL);
226 /* If it's a delete, skip this entry. */
227 if (change->change_kind == svn_fs_path_change_delete)
230 /* If it's a replacement, check for copyfrom info (if we
231 don't have it already. */
232 if (change->change_kind == svn_fs_path_change_replace)
234 if (! change->copyfrom_known)
236 SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
237 &change->copyfrom_path,
238 target_root, new_edit_path, pool));
239 change->copyfrom_known = TRUE;
241 copyfrom_path = change->copyfrom_path;
242 copyfrom_rev = change->copyfrom_rev;
247 SVN_ERR(authz_read_func(&readable, target_root, new_edit_path,
248 authz_read_baton, pool));
253 if (dent->kind == svn_node_dir)
255 svn_fs_root_t *new_source_root;
256 const char *new_source_fspath;
261 svn_fs_t *fs = svn_fs_root_fs(source_root);
262 SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
263 copyfrom_rev, pool));
264 new_source_fspath = copyfrom_path;
268 new_source_root = source_root;
269 new_source_fspath = svn_fspath__join(source_fspath, dent->name,
273 /* ### authz considerations?
275 * I think not; when path_driver_cb_func() calls add_subdir(), it
276 * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
278 if (change && change->change_kind == svn_fs_path_change_replace
279 && copyfrom_path == NULL)
281 SVN_ERR(editor->add_directory(new_edit_path, *dir_baton,
282 NULL, SVN_INVALID_REVNUM,
283 subpool, &new_dir_baton));
287 SVN_ERR(add_subdir(new_source_root, target_root,
288 editor, edit_baton, new_edit_path,
289 *dir_baton, new_source_fspath,
290 authz_read_func, authz_read_baton,
291 changed_paths, subpool, &new_dir_baton));
294 SVN_ERR(editor->close_directory(new_dir_baton, subpool));
296 else if (dent->kind == svn_node_file)
298 svn_txdelta_window_handler_t delta_handler;
299 void *delta_handler_baton, *file_baton;
300 svn_txdelta_stream_t *delta_stream;
301 svn_checksum_t *checksum;
303 SVN_ERR(editor->add_file(new_edit_path, *dir_baton, NULL,
304 SVN_INVALID_REVNUM, pool, &file_baton));
306 SVN_ERR(svn_fs_node_proplist(&props, target_root,
307 new_edit_path, subpool));
309 for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
313 apr_hash_this(phi, &key, NULL, &val);
314 SVN_ERR(editor->change_file_prop(file_baton, key, val, subpool));
317 SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool,
319 &delta_handler_baton));
321 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, NULL, NULL,
322 target_root, new_edit_path,
325 SVN_ERR(svn_txdelta_send_txstream(delta_stream,
330 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, target_root,
331 new_edit_path, TRUE, pool));
332 SVN_ERR(editor->close_file(file_baton,
333 svn_checksum_to_cstring(checksum, pool),
337 SVN_ERR_MALFUNCTION();
340 svn_pool_destroy(subpool);
345 /* Given PATH deleted under ROOT, return in READABLE whether the path was
346 readable prior to the deletion. Consult COPIES (a stack of 'struct
347 copy_info') and AUTHZ_READ_FUNC. */
349 was_readable(svn_boolean_t *readable,
352 apr_array_header_t *copies,
353 svn_repos_authz_func_t authz_read_func,
354 void *authz_read_baton,
355 apr_pool_t *result_pool,
356 apr_pool_t *scratch_pool)
358 svn_fs_root_t *inquire_root;
359 const char *inquire_path;
360 struct copy_info *info = NULL;
364 if (! authz_read_func)
370 if (copies->nelts != 0)
371 info = APR_ARRAY_IDX(copies, copies->nelts - 1, struct copy_info *);
373 /* Are we under a copy? */
374 if (info && (relpath = svn_relpath_skip_ancestor(info->path, path)))
376 SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root),
377 info->copyfrom_rev, scratch_pool));
378 inquire_path = svn_fspath__join(info->copyfrom_path, relpath,
383 /* Compute the revision that ROOT is based on. (Note that ROOT is not
384 r0's root, since this function is only called for deletions.)
385 ### Need a more succinct way to express this */
386 svn_revnum_t inquire_rev = SVN_INVALID_REVNUM;
387 if (svn_fs_is_txn_root(root))
388 inquire_rev = svn_fs_txn_root_base_revision(root);
389 if (svn_fs_is_revision_root(root))
390 inquire_rev = svn_fs_revision_root_revision(root)-1;
391 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(inquire_rev));
393 SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root),
394 inquire_rev, scratch_pool));
398 SVN_ERR(authz_read_func(readable, inquire_root, inquire_path,
399 authz_read_baton, result_pool));
404 /* Initialize COPYFROM_ROOT, COPYFROM_PATH, and COPYFROM_REV with the
405 revision root, fspath, and revnum of the copyfrom of CHANGE, which
406 corresponds to PATH under ROOT. If the copyfrom info is valid
407 (i.e., is not (NULL, SVN_INVALID_REVNUM)), then initialize SRC_READABLE
408 too, consulting AUTHZ_READ_FUNC and AUTHZ_READ_BATON if provided.
410 NOTE: If the copyfrom information in CHANGE is marked as unknown
411 (meaning, its ->copyfrom_rev and ->copyfrom_path cannot be
412 trusted), this function will also update those members of the
413 CHANGE structure to carry accurate copyfrom information. */
415 fill_copyfrom(svn_fs_root_t **copyfrom_root,
416 const char **copyfrom_path,
417 svn_revnum_t *copyfrom_rev,
418 svn_boolean_t *src_readable,
420 svn_fs_path_change2_t *change,
421 svn_repos_authz_func_t authz_read_func,
422 void *authz_read_baton,
424 apr_pool_t *result_pool,
425 apr_pool_t *scratch_pool)
427 if (! change->copyfrom_known)
429 SVN_ERR(svn_fs_copied_from(&(change->copyfrom_rev),
430 &(change->copyfrom_path),
431 root, path, result_pool));
432 change->copyfrom_known = TRUE;
434 *copyfrom_rev = change->copyfrom_rev;
435 *copyfrom_path = change->copyfrom_path;
437 if (*copyfrom_path && SVN_IS_VALID_REVNUM(*copyfrom_rev))
439 SVN_ERR(svn_fs_revision_root(copyfrom_root,
440 svn_fs_root_fs(root),
441 *copyfrom_rev, result_pool));
445 SVN_ERR(authz_read_func(src_readable, *copyfrom_root,
447 authz_read_baton, result_pool));
450 *src_readable = TRUE;
454 *copyfrom_root = NULL;
455 /* SRC_READABLE left uninitialized */
461 path_driver_cb_func(void **dir_baton,
463 void *callback_baton,
464 const char *edit_path,
467 struct path_driver_cb_baton *cb = callback_baton;
468 const svn_delta_editor_t *editor = cb->editor;
469 void *edit_baton = cb->edit_baton;
470 svn_fs_root_t *root = cb->root;
471 svn_fs_path_change2_t *change;
472 svn_boolean_t do_add = FALSE, do_delete = FALSE;
473 void *file_baton = NULL;
474 svn_revnum_t copyfrom_rev;
475 const char *copyfrom_path;
476 svn_fs_root_t *source_root = cb->compare_root;
477 const char *source_fspath = NULL;
478 const char *base_path = cb->base_path;
482 /* Initialize SOURCE_FSPATH. */
484 source_fspath = svn_fspath__canonicalize(edit_path, pool);
486 /* First, flush the copies stack so it only contains ancestors of path. */
487 while (cb->copies->nelts > 0
488 && ! svn_dirent_is_ancestor(APR_ARRAY_IDX(cb->copies,
489 cb->copies->nelts - 1,
490 struct copy_info *)->path,
492 apr_array_pop(cb->copies);
494 change = svn_hash_gets(cb->changed_paths, edit_path);
497 /* This can only happen if the path was removed from cb->changed_paths
498 by an earlier call to add_subdir, which means the path was already
499 handled and we should simply ignore it. */
502 switch (change->change_kind)
504 case svn_fs_path_change_add:
508 case svn_fs_path_change_delete:
512 case svn_fs_path_change_replace:
517 case svn_fs_path_change_modify:
523 /* Handle any deletions. */
526 svn_boolean_t readable;
528 /* Issue #4121: delete under under a copy, of a path that was unreadable
529 at its pre-copy location. */
530 SVN_ERR(was_readable(&readable, root, edit_path, cb->copies,
531 cb->authz_read_func, cb->authz_read_baton,
534 SVN_ERR(editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
535 parent_baton, pool));
538 /* Fetch the node kind if it makes sense to do so. */
539 if (! do_delete || do_add)
541 if (change->node_kind == svn_node_unknown)
542 SVN_ERR(svn_fs_check_path(&(change->node_kind), root, edit_path, pool));
543 if ((change->node_kind != svn_node_dir) &&
544 (change->node_kind != svn_node_file))
545 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
546 _("Filesystem path '%s' is neither a file "
547 "nor a directory"), edit_path);
550 /* Handle any adds/opens. */
553 svn_boolean_t src_readable;
554 svn_fs_root_t *copyfrom_root;
556 /* Was this node copied? */
557 SVN_ERR(fill_copyfrom(©from_root, ©from_path, ©from_rev,
558 &src_readable, root, change,
559 cb->authz_read_func, cb->authz_read_baton,
560 edit_path, pool, pool));
562 /* If we have a copyfrom path, and we can't read it or we're just
563 ignoring it, or the copyfrom rev is prior to the low water mark
564 then we just null them out and do a raw add with no history at
568 || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL)
569 || (cb->low_water_mark > copyfrom_rev)))
571 copyfrom_path = NULL;
572 copyfrom_rev = SVN_INVALID_REVNUM;
575 /* Do the right thing based on the path KIND. */
576 if (change->node_kind == svn_node_dir)
578 /* If this is a copy, but we can't represent it as such,
579 then we just do a recursive add of the source path
581 if (change->copyfrom_path && ! copyfrom_path)
583 SVN_ERR(add_subdir(copyfrom_root, root, editor, edit_baton,
584 edit_path, parent_baton, change->copyfrom_path,
585 cb->authz_read_func, cb->authz_read_baton,
586 cb->changed_paths, pool, dir_baton));
590 SVN_ERR(editor->add_directory(edit_path, parent_baton,
591 copyfrom_path, copyfrom_rev,
597 SVN_ERR(editor->add_file(edit_path, parent_baton, copyfrom_path,
598 copyfrom_rev, pool, &file_baton));
601 /* If we represent this as a copy... */
604 /* If it is a directory, make sure descendants get the correct
605 delta source by remembering that we are operating inside a
606 (possibly nested) copy operation. */
607 if (change->node_kind == svn_node_dir)
609 struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
611 info->path = apr_pstrdup(cb->pool, edit_path);
612 info->copyfrom_path = apr_pstrdup(cb->pool, copyfrom_path);
613 info->copyfrom_rev = copyfrom_rev;
615 APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
618 /* Save the source so that we can use it later, when we
619 need to generate text and prop deltas. */
620 source_root = copyfrom_root;
621 source_fspath = copyfrom_path;
624 /* Else, we are an add without history... */
626 /* If an ancestor is added with history, we need to forget about
627 that here, go on with life and repeat all the mistakes of our
629 if (change->node_kind == svn_node_dir && cb->copies->nelts > 0)
631 struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
633 info->path = apr_pstrdup(cb->pool, edit_path);
634 info->copyfrom_path = NULL;
635 info->copyfrom_rev = SVN_INVALID_REVNUM;
637 APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
640 source_fspath = NULL;
643 else if (! do_delete)
645 /* Do the right thing based on the path KIND (and the presence
646 of a PARENT_BATON). */
647 if (change->node_kind == svn_node_dir)
651 SVN_ERR(editor->open_directory(edit_path, parent_baton,
657 SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
663 SVN_ERR(editor->open_file(edit_path, parent_baton, SVN_INVALID_REVNUM,
666 /* If we are inside an add with history, we need to adjust the
668 if (cb->copies->nelts > 0)
670 struct copy_info *info = APR_ARRAY_IDX(cb->copies,
671 cb->copies->nelts - 1,
673 if (info->copyfrom_path)
675 const char *relpath = svn_relpath_skip_ancestor(info->path,
677 SVN_ERR_ASSERT(relpath && *relpath);
678 SVN_ERR(svn_fs_revision_root(&source_root,
679 svn_fs_root_fs(root),
680 info->copyfrom_rev, pool));
681 source_fspath = svn_fspath__join(info->copyfrom_path,
686 /* This is an add without history, nested inside an
687 add with history. We have no delta source in this case. */
689 source_fspath = NULL;
694 if (! do_delete || do_add)
696 /* Is this a copy that was downgraded to a raw add? (If so,
697 we'll need to transmit properties and file contents and such
698 for it regardless of what the CHANGE structure's text_mod
699 and prop_mod flags say.) */
700 svn_boolean_t downgraded_copy = (change->copyfrom_known
701 && change->copyfrom_path
702 && (! copyfrom_path));
704 /* Handle property modifications. */
705 if (change->prop_mod || downgraded_copy)
707 if (cb->compare_root)
709 apr_array_header_t *prop_diffs;
710 apr_hash_t *old_props;
711 apr_hash_t *new_props;
715 SVN_ERR(svn_fs_node_proplist(&old_props, source_root,
716 source_fspath, pool));
718 old_props = apr_hash_make(pool);
720 SVN_ERR(svn_fs_node_proplist(&new_props, root, edit_path, pool));
722 SVN_ERR(svn_prop_diffs(&prop_diffs, new_props, old_props,
725 for (i = 0; i < prop_diffs->nelts; ++i)
727 svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
728 if (change->node_kind == svn_node_dir)
729 SVN_ERR(editor->change_dir_prop(*dir_baton, pc->name,
731 else if (change->node_kind == svn_node_file)
732 SVN_ERR(editor->change_file_prop(file_baton, pc->name,
738 /* Just do a dummy prop change to signal that there are *any*
740 if (change->node_kind == svn_node_dir)
741 SVN_ERR(editor->change_dir_prop(*dir_baton, "", NULL,
743 else if (change->node_kind == svn_node_file)
744 SVN_ERR(editor->change_file_prop(file_baton, "", NULL,
749 /* Handle textual modifications. */
750 if (change->node_kind == svn_node_file
751 && (change->text_mod || downgraded_copy))
753 svn_txdelta_window_handler_t delta_handler;
754 void *delta_handler_baton;
755 const char *hex_digest = NULL;
757 if (cb->compare_root && source_root && source_fspath)
759 svn_checksum_t *checksum;
760 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
761 source_root, source_fspath, TRUE,
763 hex_digest = svn_checksum_to_cstring(checksum, pool);
766 SVN_ERR(editor->apply_textdelta(file_baton, hex_digest, pool,
768 &delta_handler_baton));
769 if (cb->compare_root)
771 svn_txdelta_stream_t *delta_stream;
773 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, source_root,
776 SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
777 delta_handler_baton, pool));
780 SVN_ERR(delta_handler(NULL, delta_handler_baton));
784 /* Close the file baton if we opened it. */
787 svn_checksum_t *checksum;
788 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, edit_path,
790 SVN_ERR(editor->close_file(file_baton,
791 svn_checksum_to_cstring(checksum, pool),
800 fetch_kind_func(svn_node_kind_t *kind,
803 svn_revnum_t base_revision,
804 apr_pool_t *scratch_pool)
806 svn_fs_root_t *root = baton;
807 svn_fs_root_t *prev_root;
808 svn_fs_t *fs = svn_fs_root_fs(root);
810 if (!SVN_IS_VALID_REVNUM(base_revision))
811 base_revision = svn_fs_revision_root_revision(root) - 1;
813 SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
814 SVN_ERR(svn_fs_check_path(kind, prev_root, path, scratch_pool));
820 fetch_props_func(apr_hash_t **props,
823 svn_revnum_t base_revision,
824 apr_pool_t *result_pool,
825 apr_pool_t *scratch_pool)
827 svn_fs_root_t *root = baton;
828 svn_fs_root_t *prev_root;
829 svn_fs_t *fs = svn_fs_root_fs(root);
831 if (!SVN_IS_VALID_REVNUM(base_revision))
832 base_revision = svn_fs_revision_root_revision(root) - 1;
834 SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
835 SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool));
845 svn_repos_replay2(svn_fs_root_t *root,
846 const char *base_path,
847 svn_revnum_t low_water_mark,
848 svn_boolean_t send_deltas,
849 const svn_delta_editor_t *editor,
851 svn_repos_authz_func_t authz_read_func,
852 void *authz_read_baton,
856 apr_hash_t *fs_changes;
857 apr_hash_t *changed_paths;
858 apr_hash_index_t *hi;
859 apr_array_header_t *paths;
860 struct path_driver_cb_baton cb_baton;
862 /* Special-case r0, which we know is an empty revision; if we don't
863 special-case it we might end up trying to compare it to "r-1". */
864 if (svn_fs_is_revision_root(root) && svn_fs_revision_root_revision(root) == 0)
866 SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
870 /* Fetch the paths changed under ROOT. */
871 SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, pool));
875 else if (base_path[0] == '/')
878 /* Make an array from the keys of our CHANGED_PATHS hash, and copy
879 the values into a new hash whose keys have no leading slashes. */
880 paths = apr_array_make(pool, apr_hash_count(fs_changes),
881 sizeof(const char *));
882 changed_paths = apr_hash_make(pool);
883 for (hi = apr_hash_first(pool, fs_changes); hi; hi = apr_hash_next(hi))
889 svn_fs_path_change2_t *change;
890 svn_boolean_t allowed = TRUE;
892 apr_hash_this(hi, &key, &keylen, &val);
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 = svn__apr_hash_index_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,
1460 SVN_INVALID_REVNUM, props, checksum,
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))
1521 svn_fs_path_change2_t *change;
1522 svn_boolean_t allowed = TRUE;
1524 apr_hash_this(hi, &key, &keylen, &val);
1528 if (authz_read_func)
1529 SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
1540 /* If the base_path doesn't match the top directory of this path
1541 we don't want anything to do with it... */
1542 if (svn_relpath_skip_ancestor(base_repos_relpath, path) != NULL)
1544 APR_ARRAY_PUSH(paths, const char *) = path;
1545 apr_hash_set(changed_paths, path, keylen, change);
1547 /* ...unless this was a change to one of the parent directories of
1549 else if (svn_relpath_skip_ancestor(path, base_repos_relpath) != NULL)
1551 APR_ARRAY_PUSH(paths, const char *) = path;
1552 apr_hash_set(changed_paths, path, keylen, change);
1557 /* If we were not given a low water mark, assume that everything is there,
1558 all the way back to revision 0. */
1559 if (! SVN_IS_VALID_REVNUM(low_water_mark))
1562 copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *));
1564 /* Sort the paths. Although not strictly required by the API, this has
1565 the pleasant side effect of maintaining a consistent ordering of
1566 dumpfile contents. */
1567 qsort(paths->elts, paths->nelts, paths->elt_size, svn_sort_compare_paths);
1569 /* Now actually handle the various paths. */
1570 iterpool = svn_pool_create(scratch_pool);
1571 for (i = 0; i < paths->nelts; i++)
1573 const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *);
1575 svn_pool_clear(iterpool);
1576 err = replay_node(root, repos_relpath, editor, low_water_mark,
1577 base_repos_relpath, copies, changed_paths,
1578 authz_read_func, authz_read_baton,
1579 scratch_pool, iterpool);
1585 return svn_error_compose_create(err, svn_editor_abort(editor));
1587 SVN_ERR(svn_editor_complete(editor));
1589 svn_pool_destroy(iterpool);
1590 return SVN_NO_ERROR;