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_change3_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_change3_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_change3_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 /* Was this node copied? */
559 SVN_ERR(fill_copyfrom(©from_root, ©from_path, ©from_rev,
560 &src_readable, root, change,
561 cb->authz_read_func, cb->authz_read_baton,
562 edit_path, pool, pool));
564 /* If we have a copyfrom path, and we can't read it or we're just
565 ignoring it, or the copyfrom rev is prior to the low water mark
566 then we just null them out and do a raw add with no history at
570 || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL)
571 || (cb->low_water_mark > copyfrom_rev)))
573 copyfrom_path = NULL;
574 copyfrom_rev = SVN_INVALID_REVNUM;
577 /* Do the right thing based on the path KIND. */
578 if (change->node_kind == svn_node_dir)
580 /* If this is a copy, but we can't represent it as such,
581 then we just do a recursive add of the source path
583 if (change->copyfrom_path && ! copyfrom_path)
585 SVN_ERR(add_subdir(copyfrom_root, root, editor, edit_baton,
586 edit_path, parent_baton, change->copyfrom_path,
587 cb->authz_read_func, cb->authz_read_baton,
588 cb->changed_paths, pool, dir_baton));
592 SVN_ERR(editor->add_directory(edit_path, parent_baton,
593 copyfrom_path, copyfrom_rev,
599 SVN_ERR(editor->add_file(edit_path, parent_baton, copyfrom_path,
600 copyfrom_rev, pool, &file_baton));
603 /* If we represent this as a copy... */
606 /* If it is a directory, make sure descendants get the correct
607 delta source by remembering that we are operating inside a
608 (possibly nested) copy operation. */
609 if (change->node_kind == svn_node_dir)
611 struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
613 info->path = apr_pstrdup(cb->pool, edit_path);
614 info->copyfrom_path = apr_pstrdup(cb->pool, copyfrom_path);
615 info->copyfrom_rev = copyfrom_rev;
617 APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
620 /* Save the source so that we can use it later, when we
621 need to generate text and prop deltas. */
622 source_root = copyfrom_root;
623 source_fspath = copyfrom_path;
626 /* Else, we are an add without history... */
628 /* If an ancestor is added with history, we need to forget about
629 that here, go on with life and repeat all the mistakes of our
631 if (change->node_kind == svn_node_dir && cb->copies->nelts > 0)
633 struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
635 info->path = apr_pstrdup(cb->pool, edit_path);
636 info->copyfrom_path = NULL;
637 info->copyfrom_rev = SVN_INVALID_REVNUM;
639 APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
642 source_fspath = NULL;
645 else if (! do_delete)
647 /* Do the right thing based on the path KIND (and the presence
648 of a PARENT_BATON). */
649 if (change->node_kind == svn_node_dir)
653 SVN_ERR(editor->open_directory(edit_path, parent_baton,
659 SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
665 SVN_ERR(editor->open_file(edit_path, parent_baton, SVN_INVALID_REVNUM,
668 /* If we are inside an add with history, we need to adjust the
670 if (cb->copies->nelts > 0)
672 struct copy_info *info = APR_ARRAY_IDX(cb->copies,
673 cb->copies->nelts - 1,
675 if (info->copyfrom_path)
677 const char *relpath = svn_relpath_skip_ancestor(info->path,
679 SVN_ERR_ASSERT(relpath && *relpath);
680 SVN_ERR(svn_fs_revision_root(&source_root,
681 svn_fs_root_fs(root),
682 info->copyfrom_rev, pool));
683 source_fspath = svn_fspath__join(info->copyfrom_path,
688 /* This is an add without history, nested inside an
689 add with history. We have no delta source in this case. */
691 source_fspath = NULL;
696 if (! do_delete || do_add)
698 /* Is this a copy that was downgraded to a raw add? (If so,
699 we'll need to transmit properties and file contents and such
700 for it regardless of what the CHANGE structure's text_mod
701 and prop_mod flags say.) */
702 svn_boolean_t downgraded_copy = (change->copyfrom_known
703 && change->copyfrom_path
704 && (! copyfrom_path));
706 /* Handle property modifications. */
707 if (change->prop_mod || downgraded_copy)
709 if (cb->compare_root)
711 apr_array_header_t *prop_diffs;
712 apr_hash_t *old_props;
713 apr_hash_t *new_props;
717 SVN_ERR(svn_fs_node_proplist(&old_props, source_root,
718 source_fspath, pool));
720 old_props = apr_hash_make(pool);
722 SVN_ERR(svn_fs_node_proplist(&new_props, root, edit_path, pool));
724 SVN_ERR(svn_prop_diffs(&prop_diffs, new_props, old_props,
727 for (i = 0; i < prop_diffs->nelts; ++i)
729 svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
730 if (change->node_kind == svn_node_dir)
731 SVN_ERR(editor->change_dir_prop(*dir_baton, pc->name,
733 else if (change->node_kind == svn_node_file)
734 SVN_ERR(editor->change_file_prop(file_baton, pc->name,
740 /* Just do a dummy prop change to signal that there are *any*
742 if (change->node_kind == svn_node_dir)
743 SVN_ERR(editor->change_dir_prop(*dir_baton, "", NULL,
745 else if (change->node_kind == svn_node_file)
746 SVN_ERR(editor->change_file_prop(file_baton, "", NULL,
751 /* Handle textual modifications. */
752 if (change->node_kind == svn_node_file
753 && (change->text_mod || downgraded_copy))
755 svn_txdelta_window_handler_t delta_handler;
756 void *delta_handler_baton;
757 const char *hex_digest = NULL;
759 if (cb->compare_root && source_root && source_fspath)
761 svn_checksum_t *checksum;
762 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
763 source_root, source_fspath, TRUE,
765 hex_digest = svn_checksum_to_cstring(checksum, pool);
768 SVN_ERR(editor->apply_textdelta(file_baton, hex_digest, pool,
770 &delta_handler_baton));
771 if (cb->compare_root)
773 svn_txdelta_stream_t *delta_stream;
775 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, source_root,
778 SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
779 delta_handler_baton, pool));
782 SVN_ERR(delta_handler(NULL, delta_handler_baton));
786 /* Close the file baton if we opened it. */
789 svn_checksum_t *checksum;
790 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, edit_path,
792 SVN_ERR(editor->close_file(file_baton,
793 svn_checksum_to_cstring(checksum, pool),
802 fetch_kind_func(svn_node_kind_t *kind,
805 svn_revnum_t base_revision,
806 apr_pool_t *scratch_pool)
808 svn_fs_root_t *root = baton;
809 svn_fs_root_t *prev_root;
810 svn_fs_t *fs = svn_fs_root_fs(root);
812 if (!SVN_IS_VALID_REVNUM(base_revision))
813 base_revision = svn_fs_revision_root_revision(root) - 1;
815 SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
816 SVN_ERR(svn_fs_check_path(kind, prev_root, path, scratch_pool));
822 fetch_props_func(apr_hash_t **props,
825 svn_revnum_t base_revision,
826 apr_pool_t *result_pool,
827 apr_pool_t *scratch_pool)
829 svn_fs_root_t *root = baton;
830 svn_fs_root_t *prev_root;
831 svn_fs_t *fs = svn_fs_root_fs(root);
833 if (!SVN_IS_VALID_REVNUM(base_revision))
834 base_revision = svn_fs_revision_root_revision(root) - 1;
836 SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
837 SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool));
846 /* Retrieve the path changes under ROOT, filter them with AUTHZ_READ_FUNC
847 and AUTHZ_READ_BATON and return those that intersect with BASE_RELPATH.
849 The svn_fs_path_change3_t* will be returned in *CHANGED_PATHS, keyed by
850 their path. The paths themselves are additionally returned in *PATHS.
852 Allocate the returned data in RESULT_POOL and use SCRATCH_POOL for
853 temporary allocations.
856 get_relevant_changes(apr_hash_t **changed_paths,
857 apr_array_header_t **paths,
859 const char *base_relpath,
860 svn_repos_authz_func_t authz_read_func,
861 void *authz_read_baton,
862 apr_pool_t *result_pool,
863 apr_pool_t *scratch_pool)
865 svn_fs_path_change_iterator_t *iterator;
866 svn_fs_path_change3_t *change;
867 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
869 /* Fetch the paths changed under ROOT. */
870 SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool));
871 SVN_ERR(svn_fs_path_change_get(&change, iterator));
873 /* Make an array from the keys of our CHANGED_PATHS hash, and copy
874 the values into a new hash whose keys have no leading slashes. */
875 *paths = apr_array_make(result_pool, 16, sizeof(const char *));
876 *changed_paths = apr_hash_make(result_pool);
879 const char *path = change->path.data;
880 apr_ssize_t keylen = change->path.len;
881 svn_boolean_t allowed = TRUE;
883 svn_pool_clear(iterpool);
885 SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
896 /* If the base_path doesn't match the top directory of this path
897 we don't want anything to do with it...
898 ...unless this was a change to one of the parent directories of
900 if ( svn_relpath_skip_ancestor(base_relpath, path)
901 || svn_relpath_skip_ancestor(path, base_relpath))
903 change = svn_fs_path_change3_dup(change, result_pool);
904 path = change->path.data;
908 APR_ARRAY_PUSH(*paths, const char *) = path;
909 apr_hash_set(*changed_paths, path, keylen, change);
913 SVN_ERR(svn_fs_path_change_get(&change, iterator));
916 svn_pool_destroy(iterpool);
921 svn_repos_replay2(svn_fs_root_t *root,
922 const char *base_path,
923 svn_revnum_t low_water_mark,
924 svn_boolean_t send_deltas,
925 const svn_delta_editor_t *editor,
927 svn_repos_authz_func_t authz_read_func,
928 void *authz_read_baton,
932 apr_hash_t *changed_paths;
933 apr_array_header_t *paths;
934 struct path_driver_cb_baton cb_baton;
936 /* Special-case r0, which we know is an empty revision; if we don't
937 special-case it we might end up trying to compare it to "r-1". */
938 if (svn_fs_is_revision_root(root) && svn_fs_revision_root_revision(root) == 0)
940 SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
946 else if (base_path[0] == '/')
949 /* Fetch the paths changed under ROOT. */
950 SVN_ERR(get_relevant_changes(&changed_paths, &paths, root, base_path,
951 authz_read_func, authz_read_baton,
954 /* If we were not given a low water mark, assume that everything is there,
955 all the way back to revision 0. */
956 if (! SVN_IS_VALID_REVNUM(low_water_mark))
959 /* Initialize our callback baton. */
960 cb_baton.editor = editor;
961 cb_baton.edit_baton = edit_baton;
962 cb_baton.root = root;
963 cb_baton.changed_paths = changed_paths;
964 cb_baton.authz_read_func = authz_read_func;
965 cb_baton.authz_read_baton = authz_read_baton;
966 cb_baton.base_path = base_path;
967 cb_baton.low_water_mark = low_water_mark;
968 cb_baton.compare_root = NULL;
972 SVN_ERR(svn_fs_revision_root(&cb_baton.compare_root,
973 svn_fs_root_fs(root),
974 svn_fs_is_revision_root(root)
975 ? svn_fs_revision_root_revision(root) - 1
976 : svn_fs_txn_root_base_revision(root),
980 cb_baton.copies = apr_array_make(pool, 4, sizeof(struct copy_info *));
981 cb_baton.pool = 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));
991 /* Call the path-based editor driver. */
992 return svn_delta_path_driver2(editor, edit_baton,
994 path_driver_cb_func, &cb_baton, pool);
996 svn_editor_t *editorv2;
997 struct svn_delta__extra_baton *exb;
998 svn_delta__unlock_func_t unlock_func;
999 svn_boolean_t send_abs_paths;
1000 const char *repos_root = "";
1003 /* If we were not given a low water mark, assume that everything is there,
1004 all the way back to revision 0. */
1005 if (! SVN_IS_VALID_REVNUM(low_water_mark))
1008 /* Special-case r0, which we know is an empty revision; if we don't
1009 special-case it we might end up trying to compare it to "r-1". */
1010 if (svn_fs_is_revision_root(root)
1011 && svn_fs_revision_root_revision(root) == 0)
1013 SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
1014 return SVN_NO_ERROR;
1017 /* Determine the revision to use throughout the edit, and call
1018 EDITOR's set_target_revision() function. */
1019 if (svn_fs_is_revision_root(root))
1021 svn_revnum_t revision = svn_fs_revision_root_revision(root);
1022 SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
1027 else if (base_path[0] == '/')
1030 /* Use the shim to convert our editor to an Ev2 editor, and pass it down
1032 SVN_ERR(svn_delta__editor_from_delta(&editorv2, &exb,
1033 &unlock_func, &unlock_baton,
1038 fetch_kind_func, root,
1039 fetch_props_func, root,
1042 /* Tell the shim that we're starting the process. */
1043 SVN_ERR(exb->start_edit(exb->baton, svn_fs_revision_root_revision(root)));
1045 /* ### We're ignoring SEND_DELTAS here. */
1046 SVN_ERR(svn_repos__replay_ev2(root, base_path, low_water_mark,
1047 editorv2, authz_read_func, authz_read_baton,
1050 return SVN_NO_ERROR;
1055 /*****************************************************************
1056 * Ev2 Implementation *
1057 *****************************************************************/
1059 /* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
1060 the appropriate editor calls to add it and its children without any
1061 history. This is meant to be used when either a subset of the tree
1062 has been ignored and we need to copy something from that subset to
1063 the part of the tree we do care about, or if a subset of the tree is
1064 unavailable because of authz and we need to use it as the source of
1066 static svn_error_t *
1067 add_subdir_ev2(svn_fs_root_t *source_root,
1068 svn_fs_root_t *target_root,
1069 svn_editor_t *editor,
1070 const char *repos_relpath,
1071 const char *source_fspath,
1072 svn_repos_authz_func_t authz_read_func,
1073 void *authz_read_baton,
1074 apr_hash_t *changed_paths,
1075 apr_pool_t *result_pool,
1076 apr_pool_t *scratch_pool)
1078 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1079 apr_hash_index_t *hi;
1080 apr_hash_t *dirents;
1081 apr_hash_t *props = NULL;
1082 apr_array_header_t *children = NULL;
1084 SVN_ERR(svn_fs_node_proplist(&props, target_root, repos_relpath,
1087 SVN_ERR(svn_editor_add_directory(editor, repos_relpath, children,
1088 props, SVN_INVALID_REVNUM));
1090 /* We have to get the dirents from the source path, not the target,
1091 because we want nested copies from *readable* paths to be handled by
1092 path_driver_cb_func, not add_subdir (in order to preserve history). */
1093 SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath,
1096 for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
1098 svn_fs_path_change3_t *change;
1099 svn_boolean_t readable = TRUE;
1100 svn_fs_dirent_t *dent = apr_hash_this_val(hi);
1101 const char *copyfrom_path = NULL;
1102 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
1103 const char *child_relpath;
1105 svn_pool_clear(iterpool);
1107 child_relpath = svn_relpath_join(repos_relpath, dent->name, iterpool);
1109 /* If a file or subdirectory of the copied directory is listed as a
1110 changed path (because it was modified after the copy but before the
1111 commit), we remove it from the changed_paths hash so that future
1112 calls to path_driver_cb_func will ignore it. */
1113 change = svn_hash_gets(changed_paths, child_relpath);
1116 svn_hash_sets(changed_paths, child_relpath, NULL);
1118 /* If it's a delete, skip this entry. */
1119 if (change->change_kind == svn_fs_path_change_delete)
1122 /* If it's a replacement, check for copyfrom info (if we
1123 don't have it already. */
1124 if (change->change_kind == svn_fs_path_change_replace)
1126 if (! change->copyfrom_known)
1128 SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
1129 &change->copyfrom_path,
1130 target_root, child_relpath,
1132 change->copyfrom_known = TRUE;
1134 copyfrom_path = change->copyfrom_path;
1135 copyfrom_rev = change->copyfrom_rev;
1139 if (authz_read_func)
1140 SVN_ERR(authz_read_func(&readable, target_root, child_relpath,
1141 authz_read_baton, iterpool));
1146 if (dent->kind == svn_node_dir)
1148 svn_fs_root_t *new_source_root;
1149 const char *new_source_fspath;
1153 svn_fs_t *fs = svn_fs_root_fs(source_root);
1154 SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
1155 copyfrom_rev, result_pool));
1156 new_source_fspath = copyfrom_path;
1160 new_source_root = source_root;
1161 new_source_fspath = svn_fspath__join(source_fspath, dent->name,
1165 /* ### authz considerations?
1167 * I think not; when path_driver_cb_func() calls add_subdir(), it
1168 * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
1170 if (change && change->change_kind == svn_fs_path_change_replace
1171 && copyfrom_path == NULL)
1173 SVN_ERR(svn_editor_add_directory(editor, child_relpath,
1175 SVN_INVALID_REVNUM));
1179 SVN_ERR(add_subdir_ev2(new_source_root, target_root,
1180 editor, child_relpath,
1182 authz_read_func, authz_read_baton,
1183 changed_paths, result_pool, iterpool));
1186 else if (dent->kind == svn_node_file)
1188 svn_checksum_t *checksum;
1189 svn_stream_t *contents;
1191 SVN_ERR(svn_fs_node_proplist(&props, target_root,
1192 child_relpath, iterpool));
1194 SVN_ERR(svn_fs_file_contents(&contents, target_root,
1195 child_relpath, iterpool));
1197 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1199 child_relpath, TRUE, iterpool));
1201 SVN_ERR(svn_editor_add_file(editor, child_relpath, checksum,
1202 contents, props, SVN_INVALID_REVNUM));
1205 SVN_ERR_MALFUNCTION();
1208 svn_pool_destroy(iterpool);
1210 return SVN_NO_ERROR;
1213 static svn_error_t *
1214 replay_node(svn_fs_root_t *root,
1215 const char *repos_relpath,
1216 svn_editor_t *editor,
1217 svn_revnum_t low_water_mark,
1218 const char *base_repos_relpath,
1219 apr_array_header_t *copies,
1220 apr_hash_t *changed_paths,
1221 svn_repos_authz_func_t authz_read_func,
1222 void *authz_read_baton,
1223 apr_pool_t *result_pool,
1224 apr_pool_t *scratch_pool)
1226 svn_fs_path_change3_t *change;
1227 svn_boolean_t do_add = FALSE;
1228 svn_boolean_t do_delete = FALSE;
1229 svn_revnum_t copyfrom_rev;
1230 const char *copyfrom_path;
1231 svn_revnum_t replaces_rev;
1233 /* First, flush the copies stack so it only contains ancestors of path. */
1234 while (copies->nelts > 0
1235 && (svn_relpath_skip_ancestor(APR_ARRAY_IDX(copies,
1237 struct copy_info *)->path,
1238 repos_relpath) == NULL) )
1239 apr_array_pop(copies);
1241 change = svn_hash_gets(changed_paths, repos_relpath);
1244 /* This can only happen if the path was removed from changed_paths
1245 by an earlier call to add_subdir, which means the path was already
1246 handled and we should simply ignore it. */
1247 return SVN_NO_ERROR;
1249 switch (change->change_kind)
1251 case svn_fs_path_change_add:
1255 case svn_fs_path_change_delete:
1259 case svn_fs_path_change_replace:
1264 case svn_fs_path_change_modify:
1270 /* Handle any deletions. */
1271 if (do_delete && ! do_add)
1273 svn_boolean_t readable;
1275 /* Issue #4121: delete under under a copy, of a path that was unreadable
1276 at its pre-copy location. */
1277 SVN_ERR(was_readable(&readable, root, repos_relpath, copies,
1278 authz_read_func, authz_read_baton,
1279 scratch_pool, scratch_pool));
1281 SVN_ERR(svn_editor_delete(editor, repos_relpath, SVN_INVALID_REVNUM));
1283 return SVN_NO_ERROR;
1286 /* Handle replacements. */
1287 if (do_delete && do_add)
1288 replaces_rev = svn_fs_revision_root_revision(root);
1290 replaces_rev = SVN_INVALID_REVNUM;
1292 /* Fetch the node kind if it makes sense to do so. */
1293 if (! do_delete || do_add)
1295 if (change->node_kind == svn_node_unknown)
1296 SVN_ERR(svn_fs_check_path(&(change->node_kind), root, repos_relpath,
1298 if ((change->node_kind != svn_node_dir) &&
1299 (change->node_kind != svn_node_file))
1300 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1301 _("Filesystem path '%s' is neither a file "
1302 "nor a directory"), repos_relpath);
1305 /* Handle any adds/opens. */
1308 svn_boolean_t src_readable;
1309 svn_fs_root_t *copyfrom_root;
1311 /* Was this node copied? */
1312 SVN_ERR(fill_copyfrom(©from_root, ©from_path, ©from_rev,
1313 &src_readable, root, change,
1314 authz_read_func, authz_read_baton,
1315 repos_relpath, scratch_pool, scratch_pool));
1317 /* If we have a copyfrom path, and we can't read it or we're just
1318 ignoring it, or the copyfrom rev is prior to the low water mark
1319 then we just null them out and do a raw add with no history at
1322 && ((! src_readable)
1323 || (svn_relpath_skip_ancestor(base_repos_relpath,
1324 copyfrom_path + 1) == NULL)
1325 || (low_water_mark > copyfrom_rev)))
1327 copyfrom_path = NULL;
1328 copyfrom_rev = SVN_INVALID_REVNUM;
1331 /* Do the right thing based on the path KIND. */
1332 if (change->node_kind == svn_node_dir)
1334 /* If this is a copy, but we can't represent it as such,
1335 then we just do a recursive add of the source path
1337 if (change->copyfrom_path && ! copyfrom_path)
1339 SVN_ERR(add_subdir_ev2(copyfrom_root, root, editor,
1340 repos_relpath, change->copyfrom_path,
1341 authz_read_func, authz_read_baton,
1342 changed_paths, result_pool,
1349 if (copyfrom_path[0] == '/')
1351 SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
1352 repos_relpath, replaces_rev));
1356 apr_array_header_t *children;
1358 apr_hash_t *dirents;
1360 SVN_ERR(svn_fs_dir_entries(&dirents, root, repos_relpath,
1362 SVN_ERR(svn_hash_keys(&children, dirents, scratch_pool));
1364 SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1367 SVN_ERR(svn_editor_add_directory(editor, repos_relpath,
1377 if (copyfrom_path[0] == '/')
1379 SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
1380 repos_relpath, replaces_rev));
1385 svn_checksum_t *checksum;
1386 svn_stream_t *contents;
1388 SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1391 SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
1394 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root,
1395 repos_relpath, TRUE, scratch_pool));
1397 SVN_ERR(svn_editor_add_file(editor, repos_relpath, checksum,
1398 contents, props, replaces_rev));
1402 /* If we represent this as a copy... */
1405 /* If it is a directory, make sure descendants get the correct
1406 delta source by remembering that we are operating inside a
1407 (possibly nested) copy operation. */
1408 if (change->node_kind == svn_node_dir)
1410 struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
1412 info->path = apr_pstrdup(result_pool, repos_relpath);
1413 info->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
1414 info->copyfrom_rev = copyfrom_rev;
1416 APR_ARRAY_PUSH(copies, struct copy_info *) = info;
1420 /* Else, we are an add without history... */
1422 /* If an ancestor is added with history, we need to forget about
1423 that here, go on with life and repeat all the mistakes of our
1425 if (change->node_kind == svn_node_dir && copies->nelts > 0)
1427 struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
1429 info->path = apr_pstrdup(result_pool, repos_relpath);
1430 info->copyfrom_path = NULL;
1431 info->copyfrom_rev = SVN_INVALID_REVNUM;
1433 APR_ARRAY_PUSH(copies, struct copy_info *) = info;
1437 else if (! do_delete)
1439 /* If we are inside an add with history, we need to adjust the
1441 if (copies->nelts > 0)
1443 struct copy_info *info = APR_ARRAY_IDX(copies,
1445 struct copy_info *);
1446 if (info->copyfrom_path)
1448 const char *relpath = svn_relpath_skip_ancestor(info->path,
1450 SVN_ERR_ASSERT(relpath && *relpath);
1451 repos_relpath = svn_relpath_join(info->copyfrom_path,
1452 relpath, scratch_pool);
1457 if (! do_delete && !do_add)
1459 apr_hash_t *props = NULL;
1461 /* Is this a copy that was downgraded to a raw add? (If so,
1462 we'll need to transmit properties and file contents and such
1463 for it regardless of what the CHANGE structure's text_mod
1464 and prop_mod flags say.) */
1465 svn_boolean_t downgraded_copy = (change->copyfrom_known
1466 && change->copyfrom_path
1467 && (! copyfrom_path));
1469 /* Handle property modifications. */
1470 if (change->prop_mod || downgraded_copy)
1472 SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1476 /* Handle textual modifications. */
1477 if (change->node_kind == svn_node_file
1478 && (change->text_mod || change->prop_mod || downgraded_copy))
1480 svn_checksum_t *checksum = NULL;
1481 svn_stream_t *contents = NULL;
1483 if (change->text_mod)
1485 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1486 root, repos_relpath, TRUE,
1489 SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
1493 SVN_ERR(svn_editor_alter_file(editor, repos_relpath,
1495 checksum, contents, props));
1498 if (change->node_kind == svn_node_dir
1499 && (change->prop_mod || downgraded_copy))
1501 apr_array_header_t *children = NULL;
1503 SVN_ERR(svn_editor_alter_directory(editor, repos_relpath,
1504 SVN_INVALID_REVNUM, children,
1509 return SVN_NO_ERROR;
1513 svn_repos__replay_ev2(svn_fs_root_t *root,
1514 const char *base_repos_relpath,
1515 svn_revnum_t low_water_mark,
1516 svn_editor_t *editor,
1517 svn_repos_authz_func_t authz_read_func,
1518 void *authz_read_baton,
1519 apr_pool_t *scratch_pool)
1521 apr_hash_t *changed_paths;
1522 apr_array_header_t *paths;
1523 apr_array_header_t *copies;
1524 apr_pool_t *iterpool;
1525 svn_error_t *err = SVN_NO_ERROR;
1528 SVN_ERR_ASSERT(svn_relpath_is_canonical(base_repos_relpath));
1530 /* Special-case r0, which we know is an empty revision; if we don't
1531 special-case it we might end up trying to compare it to "r-1". */
1532 if (svn_fs_is_revision_root(root)
1533 && svn_fs_revision_root_revision(root) == 0)
1535 return SVN_NO_ERROR;
1538 /* Fetch the paths changed under ROOT. */
1539 SVN_ERR(get_relevant_changes(&changed_paths, &paths, root,
1541 authz_read_func, authz_read_baton,
1542 scratch_pool, scratch_pool));
1544 /* If we were not given a low water mark, assume that everything is there,
1545 all the way back to revision 0. */
1546 if (! SVN_IS_VALID_REVNUM(low_water_mark))
1549 copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *));
1551 /* Sort the paths. Although not strictly required by the API, this has
1552 the pleasant side effect of maintaining a consistent ordering of
1553 dumpfile contents. */
1554 svn_sort__array(paths, svn_sort_compare_paths);
1556 /* Now actually handle the various paths. */
1557 iterpool = svn_pool_create(scratch_pool);
1558 for (i = 0; i < paths->nelts; i++)
1560 const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *);
1562 svn_pool_clear(iterpool);
1563 err = replay_node(root, repos_relpath, editor, low_water_mark,
1564 base_repos_relpath, copies, changed_paths,
1565 authz_read_func, authz_read_baton,
1566 scratch_pool, iterpool);
1572 return svn_error_compose_create(err, svn_editor_abort(editor));
1574 SVN_ERR(svn_editor_complete(editor));
1576 svn_pool_destroy(iterpool);
1577 return SVN_NO_ERROR;