]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_repos/replay.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_repos / replay.c
1 /*
2  * replay.c:   an editor driver for changes made in a given revision
3  *             or transaction
4  *
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
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
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
21  *    under the License.
22  * ====================================================================
23  */
24
25 \f
26 #include <apr_hash.h>
27
28 #include "svn_types.h"
29 #include "svn_delta.h"
30 #include "svn_hash.h"
31 #include "svn_fs.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"
37 #include "svn_path.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"
43
44 \f
45 /*** Backstory ***/
46
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
57    the American Midwest.
58
59    Subversion needed a change.
60
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. */
66
67 /*** Overview ***/
68
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.
75
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.
85
86    So, what's the plan here?
87
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.
93
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.
96
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.
101
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
105    occurred). */
106
107 /* #define USE_EV2_IMPL */
108
109 \f
110 /*** Helper functions. ***/
111
112
113 /* Information for an active copy, that is a directory which we are currently
114    working on and which was added with history. */
115 struct copy_info
116 {
117   /* Destination relpath (relative to the root of the  . */
118   const char *path;
119
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;
125 };
126
127 struct path_driver_cb_baton
128 {
129   const svn_delta_editor_t *editor;
130   void *edit_baton;
131
132   /* The root of the revision we're replaying. */
133   svn_fs_root_t *root;
134
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;
138
139   apr_hash_t *changed_paths;
140
141   svn_repos_authz_func_t authz_read_func;
142   void *authz_read_baton;
143
144   const char *base_path; /* relpath */
145
146   svn_revnum_t low_water_mark;
147   /* Stack of active copy operations. */
148   apr_array_header_t *copies;
149
150   /* The global pool for this replay operation. */
151   apr_pool_t *pool;
152 };
153
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
160    a copy. */
161 static svn_error_t *
162 add_subdir(svn_fs_root_t *source_root,
163            svn_fs_root_t *target_root,
164            const svn_delta_editor_t *editor,
165            void *edit_baton,
166            const char *edit_path,
167            void *parent_baton,
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,
172            apr_pool_t *pool,
173            void **dir_baton)
174 {
175   apr_pool_t *subpool = svn_pool_create(pool);
176   apr_hash_index_t *hi, *phi;
177   apr_hash_t *dirents;
178   apr_hash_t *props;
179
180   SVN_ERR(editor->add_directory(edit_path, parent_baton, NULL,
181                                 SVN_INVALID_REVNUM, pool, dir_baton));
182
183   SVN_ERR(svn_fs_node_proplist(&props, target_root, edit_path, pool));
184
185   for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
186     {
187       const char *key = apr_hash_this_key(phi);
188       svn_string_t *val = apr_hash_this_val(phi);
189
190       svn_pool_clear(subpool);
191       SVN_ERR(editor->change_dir_prop(*dir_baton, key, val, subpool));
192     }
193
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));
198
199   for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
200     {
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;
207
208       svn_pool_clear(subpool);
209
210       new_edit_path = svn_relpath_join(edit_path, dent->name, subpool);
211
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);
217       if (change)
218         {
219           svn_hash_sets(changed_paths, new_edit_path, NULL);
220
221           /* If it's a delete, skip this entry. */
222           if (change->change_kind == svn_fs_path_change_delete)
223             continue;
224
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)
228             {
229               if (! change->copyfrom_known)
230                 {
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;
235                 }
236               copyfrom_path = change->copyfrom_path;
237               copyfrom_rev = change->copyfrom_rev;
238             }
239         }
240
241       if (authz_read_func)
242         SVN_ERR(authz_read_func(&readable, target_root, new_edit_path,
243                                 authz_read_baton, pool));
244
245       if (! readable)
246         continue;
247
248       if (dent->kind == svn_node_dir)
249         {
250           svn_fs_root_t *new_source_root;
251           const char *new_source_fspath;
252           void *new_dir_baton;
253
254           if (copyfrom_path)
255             {
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;
260             }
261           else
262             {
263               new_source_root = source_root;
264               new_source_fspath = svn_fspath__join(source_fspath, dent->name,
265                                                    subpool);
266             }
267
268           /* ### authz considerations?
269            *
270            * I think not; when path_driver_cb_func() calls add_subdir(), it
271            * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
272            */
273           if (change && change->change_kind == svn_fs_path_change_replace
274               && copyfrom_path == NULL)
275             {
276               SVN_ERR(editor->add_directory(new_edit_path, *dir_baton,
277                                             NULL, SVN_INVALID_REVNUM,
278                                             subpool, &new_dir_baton));
279             }
280           else
281             {
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));
287             }
288
289           SVN_ERR(editor->close_directory(new_dir_baton, subpool));
290         }
291       else if (dent->kind == svn_node_file)
292         {
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;
297
298           SVN_ERR(editor->add_file(new_edit_path, *dir_baton, NULL,
299                                    SVN_INVALID_REVNUM, pool, &file_baton));
300
301           SVN_ERR(svn_fs_node_proplist(&props, target_root,
302                                        new_edit_path, subpool));
303
304           for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
305             {
306               const char *key = apr_hash_this_key(phi);
307               svn_string_t *val = apr_hash_this_val(phi);
308
309               SVN_ERR(editor->change_file_prop(file_baton, key, val, subpool));
310             }
311
312           SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool,
313                                           &delta_handler,
314                                           &delta_handler_baton));
315
316           SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, NULL, NULL,
317                                                target_root, new_edit_path,
318                                                pool));
319
320           SVN_ERR(svn_txdelta_send_txstream(delta_stream,
321                                             delta_handler,
322                                             delta_handler_baton,
323                                             pool));
324
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),
329                                      pool));
330         }
331       else
332         SVN_ERR_MALFUNCTION();
333     }
334
335   svn_pool_destroy(subpool);
336
337   return SVN_NO_ERROR;
338 }
339
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. */
343 static svn_error_t *
344 was_readable(svn_boolean_t *readable,
345              svn_fs_root_t *root,
346              const char *path,
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)
352 {
353   svn_fs_root_t *inquire_root;
354   const char *inquire_path;
355   struct copy_info *info = NULL;
356   const char *relpath;
357
358   /* Short circuit. */
359   if (! authz_read_func)
360     {
361       *readable = TRUE;
362       return SVN_NO_ERROR;
363     }
364
365   if (copies->nelts != 0)
366     info = APR_ARRAY_IDX(copies, copies->nelts - 1, struct copy_info *);
367
368   /* Are we under a copy? */
369   if (info && (relpath = svn_relpath_skip_ancestor(info->path, path)))
370     {
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,
374                                       scratch_pool);
375     }
376   else
377     {
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));
387
388       SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root),
389                                    inquire_rev, scratch_pool));
390       inquire_path = path;
391     }
392
393   SVN_ERR(authz_read_func(readable, inquire_root, inquire_path,
394                           authz_read_baton, result_pool));
395
396   return SVN_NO_ERROR;
397 }
398
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.
404
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.  */
409 static svn_error_t *
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,
414               svn_fs_root_t *root,
415               svn_fs_path_change2_t *change,
416               svn_repos_authz_func_t authz_read_func,
417               void *authz_read_baton,
418               const char *path,
419               apr_pool_t *result_pool,
420               apr_pool_t *scratch_pool)
421 {
422   if (! change->copyfrom_known)
423     {
424       SVN_ERR(svn_fs_copied_from(&(change->copyfrom_rev),
425                                  &(change->copyfrom_path),
426                                  root, path, result_pool));
427       change->copyfrom_known = TRUE;
428     }
429   *copyfrom_rev = change->copyfrom_rev;
430   *copyfrom_path = change->copyfrom_path;
431
432   if (*copyfrom_path && SVN_IS_VALID_REVNUM(*copyfrom_rev))
433     {
434       SVN_ERR(svn_fs_revision_root(copyfrom_root,
435                                    svn_fs_root_fs(root),
436                                    *copyfrom_rev, result_pool));
437
438       if (authz_read_func)
439         {
440           SVN_ERR(authz_read_func(src_readable, *copyfrom_root,
441                                   *copyfrom_path,
442                                   authz_read_baton, result_pool));
443         }
444       else
445         *src_readable = TRUE;
446     }
447   else
448     {
449       *copyfrom_root = NULL;
450       /* SRC_READABLE left uninitialized */
451     }
452   return SVN_NO_ERROR;
453 }
454
455 static svn_error_t *
456 path_driver_cb_func(void **dir_baton,
457                     void *parent_baton,
458                     void *callback_baton,
459                     const char *edit_path,
460                     apr_pool_t *pool)
461 {
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;
474
475   *dir_baton = NULL;
476
477   /* Initialize SOURCE_FSPATH. */
478   if (source_root)
479     source_fspath = svn_fspath__canonicalize(edit_path, pool);
480
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,
486                                      edit_path))
487     apr_array_pop(cb->copies);
488
489   change = svn_hash_gets(cb->changed_paths, edit_path);
490   if (! change)
491     {
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. */
495       return SVN_NO_ERROR;
496     }
497   switch (change->change_kind)
498     {
499     case svn_fs_path_change_add:
500       do_add = TRUE;
501       break;
502
503     case svn_fs_path_change_delete:
504       do_delete = TRUE;
505       break;
506
507     case svn_fs_path_change_replace:
508       do_add = TRUE;
509       do_delete = TRUE;
510       break;
511
512     case svn_fs_path_change_modify:
513     default:
514       /* do nothing */
515       break;
516     }
517
518   /* Handle any deletions. */
519   if (do_delete)
520     {
521       svn_boolean_t readable;
522
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,
527                             pool, pool));
528       if (readable)
529         SVN_ERR(editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
530                                      parent_baton, pool));
531     }
532
533   /* Fetch the node kind if it makes sense to do so. */
534   if (! do_delete || do_add)
535     {
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);
543     }
544
545   /* Handle any adds/opens. */
546   if (do_add)
547     {
548       svn_boolean_t src_readable;
549       svn_fs_root_t *copyfrom_root;
550
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. */
554       if (*edit_path == 0)
555         return svn_error_create(SVN_ERR_FS_ALREADY_EXISTS, NULL,
556                                 _("Root directory already exists."));
557
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);
561
562       /* Was this node copied? */
563       SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
564                             &src_readable, root, change,
565                             cb->authz_read_func, cb->authz_read_baton,
566                             edit_path, pool, pool));
567
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
571          all. */
572       if (copyfrom_path
573           && ((! src_readable)
574               || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL)
575               || (cb->low_water_mark > copyfrom_rev)))
576         {
577           copyfrom_path = NULL;
578           copyfrom_rev = SVN_INVALID_REVNUM;
579         }
580
581       /* Do the right thing based on the path KIND. */
582       if (change->node_kind == svn_node_dir)
583         {
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
586              contents. */
587           if (change->copyfrom_path && ! copyfrom_path)
588             {
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));
593             }
594           else
595             {
596               SVN_ERR(editor->add_directory(edit_path, parent_baton,
597                                             copyfrom_path, copyfrom_rev,
598                                             pool, dir_baton));
599             }
600         }
601       else
602         {
603           SVN_ERR(editor->add_file(edit_path, parent_baton, copyfrom_path,
604                                    copyfrom_rev, pool, &file_baton));
605         }
606
607       /* If we represent this as a copy... */
608       if (copyfrom_path)
609         {
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)
614             {
615               struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
616
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;
620
621               APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
622             }
623
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;
628         }
629       else
630         /* Else, we are an add without history... */
631         {
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
634              past... */
635           if (change->node_kind == svn_node_dir && cb->copies->nelts > 0)
636             {
637               struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
638
639               info->path = apr_pstrdup(cb->pool, edit_path);
640               info->copyfrom_path = NULL;
641               info->copyfrom_rev = SVN_INVALID_REVNUM;
642
643               APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
644             }
645           source_root = NULL;
646           source_fspath = NULL;
647         }
648     }
649   else if (! do_delete)
650     {
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)
654         {
655           if (parent_baton)
656             {
657               SVN_ERR(editor->open_directory(edit_path, parent_baton,
658                                              SVN_INVALID_REVNUM,
659                                              pool, dir_baton));
660             }
661           else
662             {
663               SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
664                                         pool, dir_baton));
665             }
666         }
667       else
668         {
669           SVN_ERR(editor->open_file(edit_path, parent_baton, SVN_INVALID_REVNUM,
670                                     pool, &file_baton));
671         }
672       /* If we are inside an add with history, we need to adjust the
673          delta source. */
674       if (cb->copies->nelts > 0)
675         {
676           struct copy_info *info = APR_ARRAY_IDX(cb->copies,
677                                                  cb->copies->nelts - 1,
678                                                  struct copy_info *);
679           if (info->copyfrom_path)
680             {
681               const char *relpath = svn_relpath_skip_ancestor(info->path,
682                                                               edit_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,
688                                                relpath, pool);
689             }
690           else
691             {
692               /* This is an add without history, nested inside an
693                  add with history.  We have no delta source in this case. */
694               source_root = NULL;
695               source_fspath = NULL;
696             }
697         }
698     }
699
700   if (! do_delete || do_add)
701     {
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));
709
710       /* Handle property modifications. */
711       if (change->prop_mod || downgraded_copy)
712         {
713           if (cb->compare_root)
714             {
715               apr_array_header_t *prop_diffs;
716               apr_hash_t *old_props;
717               apr_hash_t *new_props;
718               int i;
719
720               if (source_root)
721                 SVN_ERR(svn_fs_node_proplist(&old_props, source_root,
722                                              source_fspath, pool));
723               else
724                 old_props = apr_hash_make(pool);
725
726               SVN_ERR(svn_fs_node_proplist(&new_props, root, edit_path, pool));
727
728               SVN_ERR(svn_prop_diffs(&prop_diffs, new_props, old_props,
729                                      pool));
730
731               for (i = 0; i < prop_diffs->nelts; ++i)
732                 {
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,
736                                                      pc->value, pool));
737                    else if (change->node_kind == svn_node_file)
738                      SVN_ERR(editor->change_file_prop(file_baton, pc->name,
739                                                       pc->value, pool));
740                 }
741             }
742           else
743             {
744               /* Just do a dummy prop change to signal that there are *any*
745                  propmods. */
746               if (change->node_kind == svn_node_dir)
747                 SVN_ERR(editor->change_dir_prop(*dir_baton, "", NULL,
748                                                 pool));
749               else if (change->node_kind == svn_node_file)
750                 SVN_ERR(editor->change_file_prop(file_baton, "", NULL,
751                                                  pool));
752             }
753         }
754
755       /* Handle textual modifications. */
756       if (change->node_kind == svn_node_file
757           && (change->text_mod || downgraded_copy))
758         {
759           svn_txdelta_window_handler_t delta_handler;
760           void *delta_handler_baton;
761           const char *hex_digest = NULL;
762
763           if (cb->compare_root && source_root && source_fspath)
764             {
765               svn_checksum_t *checksum;
766               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
767                                            source_root, source_fspath, TRUE,
768                                            pool));
769               hex_digest = svn_checksum_to_cstring(checksum, pool);
770             }
771
772           SVN_ERR(editor->apply_textdelta(file_baton, hex_digest, pool,
773                                           &delta_handler,
774                                           &delta_handler_baton));
775           if (cb->compare_root)
776             {
777               svn_txdelta_stream_t *delta_stream;
778
779               SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, source_root,
780                                                    source_fspath, root,
781                                                    edit_path, pool));
782               SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
783                                                 delta_handler_baton, pool));
784             }
785           else
786             SVN_ERR(delta_handler(NULL, delta_handler_baton));
787         }
788     }
789
790   /* Close the file baton if we opened it. */
791   if (file_baton)
792     {
793       svn_checksum_t *checksum;
794       SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, edit_path,
795                                    TRUE, pool));
796       SVN_ERR(editor->close_file(file_baton,
797                                  svn_checksum_to_cstring(checksum, pool),
798                                  pool));
799     }
800
801   return SVN_NO_ERROR;
802 }
803
804 #ifdef USE_EV2_IMPL
805 static svn_error_t *
806 fetch_kind_func(svn_node_kind_t *kind,
807                 void *baton,
808                 const char *path,
809                 svn_revnum_t base_revision,
810                 apr_pool_t *scratch_pool)
811 {
812   svn_fs_root_t *root = baton;
813   svn_fs_root_t *prev_root;
814   svn_fs_t *fs = svn_fs_root_fs(root);
815
816   if (!SVN_IS_VALID_REVNUM(base_revision))
817     base_revision = svn_fs_revision_root_revision(root) - 1;
818
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));
821
822   return SVN_NO_ERROR;
823 }
824
825 static svn_error_t *
826 fetch_props_func(apr_hash_t **props,
827                  void *baton,
828                  const char *path,
829                  svn_revnum_t base_revision,
830                  apr_pool_t *result_pool,
831                  apr_pool_t *scratch_pool)
832 {
833   svn_fs_root_t *root = baton;
834   svn_fs_root_t *prev_root;
835   svn_fs_t *fs = svn_fs_root_fs(root);
836
837   if (!SVN_IS_VALID_REVNUM(base_revision))
838     base_revision = svn_fs_revision_root_revision(root) - 1;
839
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));
842
843   return SVN_NO_ERROR;
844 }
845 #endif
846
847
848 \f
849
850 svn_error_t *
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,
856                   void *edit_baton,
857                   svn_repos_authz_func_t authz_read_func,
858                   void *authz_read_baton,
859                   apr_pool_t *pool)
860 {
861 #ifndef USE_EV2_IMPL
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;
867
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)
871     {
872       SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
873       return SVN_NO_ERROR;
874     }
875
876   /* Fetch the paths changed under ROOT. */
877   SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, pool));
878
879   if (! base_path)
880     base_path = "";
881   else if (base_path[0] == '/')
882     ++base_path;
883
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))
890     {
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;
895
896       if (authz_read_func)
897         SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
898                                 pool));
899
900       if (allowed)
901         {
902           if (path[0] == '/')
903             {
904               path++;
905               keylen--;
906             }
907
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)
911             {
912               APR_ARRAY_PUSH(paths, const char *) = path;
913               apr_hash_set(changed_paths, path, keylen, change);
914             }
915           /* ...unless this was a change to one of the parent directories of
916              base_path. */
917           else if (svn_relpath_skip_ancestor(path, base_path) != NULL)
918             {
919               APR_ARRAY_PUSH(paths, const char *) = path;
920               apr_hash_set(changed_paths, path, keylen, change);
921             }
922         }
923     }
924
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))
928     low_water_mark = 0;
929
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;
940
941   if (send_deltas)
942     {
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),
948                                    pool));
949     }
950
951   cb_baton.copies = apr_array_make(pool, 4, sizeof(struct copy_info *));
952   cb_baton.pool = pool;
953
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))
957     {
958       svn_revnum_t revision = svn_fs_revision_root_revision(root);
959       SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
960     }
961
962   /* Call the path-based editor driver. */
963   return svn_delta_path_driver2(editor, edit_baton,
964                                 paths, TRUE,
965                                 path_driver_cb_func, &cb_baton, pool);
966 #else
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 = "";
972   void *unlock_baton;
973
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)
978     {
979       SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
980       return SVN_NO_ERROR;
981     }
982
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))
986     {
987       svn_revnum_t revision = svn_fs_revision_root_revision(root);
988       SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
989     }
990
991   if (! base_path)
992     base_path = "";
993   else if (base_path[0] == '/')
994     ++base_path;
995
996   /* Use the shim to convert our editor to an Ev2 editor, and pass it down
997      the stack. */
998   SVN_ERR(svn_delta__editor_from_delta(&editorv2, &exb,
999                                        &unlock_func, &unlock_baton,
1000                                        editor, edit_baton,
1001                                        &send_abs_paths,
1002                                        repos_root, "",
1003                                        NULL, NULL,
1004                                        fetch_kind_func, root,
1005                                        fetch_props_func, root,
1006                                        pool, pool));
1007
1008   /* Tell the shim that we're starting the process. */
1009   SVN_ERR(exb->start_edit(exb->baton, svn_fs_revision_root_revision(root)));
1010
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,
1014                                 pool));
1015
1016   return SVN_NO_ERROR;
1017 #endif
1018 }
1019
1020 \f
1021 /*****************************************************************
1022  *                      Ev2 Implementation                       *
1023  *****************************************************************/
1024
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
1031    a copy. */
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)
1043 {
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;
1049
1050   SVN_ERR(svn_fs_node_proplist(&props, target_root, repos_relpath,
1051                                scratch_pool));
1052
1053   SVN_ERR(svn_editor_add_directory(editor, repos_relpath, children,
1054                                    props, SVN_INVALID_REVNUM));
1055
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,
1060                              scratch_pool));
1061
1062   for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
1063     {
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;
1070
1071       svn_pool_clear(iterpool);
1072
1073       child_relpath = svn_relpath_join(repos_relpath, dent->name, iterpool);
1074
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);
1080       if (change)
1081         {
1082           svn_hash_sets(changed_paths, child_relpath, NULL);
1083
1084           /* If it's a delete, skip this entry. */
1085           if (change->change_kind == svn_fs_path_change_delete)
1086             continue;
1087
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)
1091             {
1092               if (! change->copyfrom_known)
1093                 {
1094                   SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
1095                                              &change->copyfrom_path,
1096                                              target_root, child_relpath,
1097                                              result_pool));
1098                   change->copyfrom_known = TRUE;
1099                 }
1100               copyfrom_path = change->copyfrom_path;
1101               copyfrom_rev = change->copyfrom_rev;
1102             }
1103         }
1104
1105       if (authz_read_func)
1106         SVN_ERR(authz_read_func(&readable, target_root, child_relpath,
1107                                 authz_read_baton, iterpool));
1108
1109       if (! readable)
1110         continue;
1111
1112       if (dent->kind == svn_node_dir)
1113         {
1114           svn_fs_root_t *new_source_root;
1115           const char *new_source_fspath;
1116
1117           if (copyfrom_path)
1118             {
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;
1123             }
1124           else
1125             {
1126               new_source_root = source_root;
1127               new_source_fspath = svn_fspath__join(source_fspath, dent->name,
1128                                                    iterpool);
1129             }
1130
1131           /* ### authz considerations?
1132            *
1133            * I think not; when path_driver_cb_func() calls add_subdir(), it
1134            * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
1135            */
1136           if (change && change->change_kind == svn_fs_path_change_replace
1137               && copyfrom_path == NULL)
1138             {
1139               SVN_ERR(svn_editor_add_directory(editor, child_relpath,
1140                                                children, props,
1141                                                SVN_INVALID_REVNUM));
1142             }
1143           else
1144             {
1145               SVN_ERR(add_subdir_ev2(new_source_root, target_root,
1146                                      editor, child_relpath,
1147                                      new_source_fspath,
1148                                      authz_read_func, authz_read_baton,
1149                                      changed_paths, result_pool, iterpool));
1150             }
1151         }
1152       else if (dent->kind == svn_node_file)
1153         {
1154           svn_checksum_t *checksum;
1155           svn_stream_t *contents;
1156
1157           SVN_ERR(svn_fs_node_proplist(&props, target_root,
1158                                        child_relpath, iterpool));
1159
1160           SVN_ERR(svn_fs_file_contents(&contents, target_root,
1161                                        child_relpath, iterpool));
1162
1163           SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1164                                        target_root,
1165                                        child_relpath, TRUE, iterpool));
1166
1167           SVN_ERR(svn_editor_add_file(editor, child_relpath, checksum,
1168                                       contents, props, SVN_INVALID_REVNUM));
1169         }
1170       else
1171         SVN_ERR_MALFUNCTION();
1172     }
1173
1174   svn_pool_destroy(iterpool);
1175
1176   return SVN_NO_ERROR;
1177 }
1178
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)
1191 {
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;
1198
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,
1202                                                     copies->nelts - 1,
1203                                                      struct copy_info *)->path,
1204                                        repos_relpath) == NULL) )
1205     apr_array_pop(copies);
1206
1207   change = svn_hash_gets(changed_paths, repos_relpath);
1208   if (! change)
1209     {
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;
1214     }
1215   switch (change->change_kind)
1216     {
1217     case svn_fs_path_change_add:
1218       do_add = TRUE;
1219       break;
1220
1221     case svn_fs_path_change_delete:
1222       do_delete = TRUE;
1223       break;
1224
1225     case svn_fs_path_change_replace:
1226       do_add = TRUE;
1227       do_delete = TRUE;
1228       break;
1229
1230     case svn_fs_path_change_modify:
1231     default:
1232       /* do nothing */
1233       break;
1234     }
1235
1236   /* Handle any deletions. */
1237   if (do_delete && ! do_add)
1238     {
1239       svn_boolean_t readable;
1240
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));
1246       if (readable)
1247         SVN_ERR(svn_editor_delete(editor, repos_relpath, SVN_INVALID_REVNUM));
1248
1249       return SVN_NO_ERROR;
1250     }
1251
1252   /* Handle replacements. */
1253   if (do_delete && do_add)
1254     replaces_rev = svn_fs_revision_root_revision(root);
1255   else
1256     replaces_rev = SVN_INVALID_REVNUM;
1257
1258   /* Fetch the node kind if it makes sense to do so. */
1259   if (! do_delete || do_add)
1260     {
1261       if (change->node_kind == svn_node_unknown)
1262         SVN_ERR(svn_fs_check_path(&(change->node_kind), root, repos_relpath,
1263                                   scratch_pool));
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);
1269     }
1270
1271   /* Handle any adds/opens. */
1272   if (do_add)
1273     {
1274       svn_boolean_t src_readable;
1275       svn_fs_root_t *copyfrom_root;
1276
1277       /* Was this node copied? */
1278       SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
1279                             &src_readable, root, change,
1280                             authz_read_func, authz_read_baton,
1281                             repos_relpath, scratch_pool, scratch_pool));
1282
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
1286          all. */
1287       if (copyfrom_path
1288           && ((! src_readable)
1289               || (svn_relpath_skip_ancestor(base_repos_relpath,
1290                                             copyfrom_path + 1) == NULL)
1291               || (low_water_mark > copyfrom_rev)))
1292         {
1293           copyfrom_path = NULL;
1294           copyfrom_rev = SVN_INVALID_REVNUM;
1295         }
1296
1297       /* Do the right thing based on the path KIND. */
1298       if (change->node_kind == svn_node_dir)
1299         {
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
1302              contents. */
1303           if (change->copyfrom_path && ! copyfrom_path)
1304             {
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,
1309                                      scratch_pool));
1310             }
1311           else
1312             {
1313               if (copyfrom_path)
1314                 {
1315                   if (copyfrom_path[0] == '/')
1316                     ++copyfrom_path;
1317                   SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
1318                                           repos_relpath, replaces_rev));
1319                 }
1320               else
1321                 {
1322                   apr_array_header_t *children;
1323                   apr_hash_t *props;
1324                   apr_hash_t *dirents;
1325
1326                   SVN_ERR(svn_fs_dir_entries(&dirents, root, repos_relpath,
1327                                              scratch_pool));
1328                   SVN_ERR(svn_hash_keys(&children, dirents, scratch_pool));
1329
1330                   SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1331                                                scratch_pool));
1332
1333                   SVN_ERR(svn_editor_add_directory(editor, repos_relpath,
1334                                                    children, props,
1335                                                    replaces_rev));
1336                 }
1337             }
1338         }
1339       else
1340         {
1341           if (copyfrom_path)
1342             {
1343               if (copyfrom_path[0] == '/')
1344                 ++copyfrom_path;
1345               SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
1346                                       repos_relpath, replaces_rev));
1347             }
1348           else
1349             {
1350               apr_hash_t *props;
1351               svn_checksum_t *checksum;
1352               svn_stream_t *contents;
1353
1354               SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1355                                            scratch_pool));
1356
1357               SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
1358                                            scratch_pool));
1359
1360               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root,
1361                                            repos_relpath, TRUE, scratch_pool));
1362
1363               SVN_ERR(svn_editor_add_file(editor, repos_relpath, checksum,
1364                                           contents, props, replaces_rev));
1365             }
1366         }
1367
1368       /* If we represent this as a copy... */
1369       if (copyfrom_path)
1370         {
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)
1375             {
1376               struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
1377
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;
1381
1382               APR_ARRAY_PUSH(copies, struct copy_info *) = info;
1383             }
1384         }
1385       else
1386         /* Else, we are an add without history... */
1387         {
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
1390              past... */
1391           if (change->node_kind == svn_node_dir && copies->nelts > 0)
1392             {
1393               struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
1394
1395               info->path = apr_pstrdup(result_pool, repos_relpath);
1396               info->copyfrom_path = NULL;
1397               info->copyfrom_rev = SVN_INVALID_REVNUM;
1398
1399               APR_ARRAY_PUSH(copies, struct copy_info *) = info;
1400             }
1401         }
1402     }
1403   else if (! do_delete)
1404     {
1405       /* If we are inside an add with history, we need to adjust the
1406          delta source. */
1407       if (copies->nelts > 0)
1408         {
1409           struct copy_info *info = APR_ARRAY_IDX(copies,
1410                                                  copies->nelts - 1,
1411                                                  struct copy_info *);
1412           if (info->copyfrom_path)
1413             {
1414               const char *relpath = svn_relpath_skip_ancestor(info->path,
1415                                                               repos_relpath);
1416               SVN_ERR_ASSERT(relpath && *relpath);
1417               repos_relpath = svn_relpath_join(info->copyfrom_path,
1418                                                relpath, scratch_pool);
1419             }
1420         }
1421     }
1422
1423   if (! do_delete && !do_add)
1424     {
1425       apr_hash_t *props = NULL;
1426
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));
1434
1435       /* Handle property modifications. */
1436       if (change->prop_mod || downgraded_copy)
1437         {
1438           SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1439                                        scratch_pool));
1440         }
1441
1442       /* Handle textual modifications. */
1443       if (change->node_kind == svn_node_file
1444           && (change->text_mod || change->prop_mod || downgraded_copy))
1445         {
1446           svn_checksum_t *checksum = NULL;
1447           svn_stream_t *contents = NULL;
1448
1449           if (change->text_mod)
1450             {
1451               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1452                                            root, repos_relpath, TRUE,
1453                                            scratch_pool));
1454
1455               SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
1456                                            scratch_pool));
1457             }
1458
1459           SVN_ERR(svn_editor_alter_file(editor, repos_relpath,
1460                                         SVN_INVALID_REVNUM,
1461                                         checksum, contents, props));
1462         }
1463
1464       if (change->node_kind == svn_node_dir
1465           && (change->prop_mod || downgraded_copy))
1466         {
1467           apr_array_header_t *children = NULL;
1468
1469           SVN_ERR(svn_editor_alter_directory(editor, repos_relpath,
1470                                              SVN_INVALID_REVNUM, children,
1471                                              props));
1472         }
1473     }
1474
1475   return SVN_NO_ERROR;
1476 }
1477
1478 svn_error_t *
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)
1486 {
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;
1494   int i;
1495
1496   SVN_ERR_ASSERT(!svn_dirent_is_absolute(base_repos_relpath));
1497
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)
1502     {
1503       return SVN_NO_ERROR;
1504     }
1505
1506   /* Fetch the paths changed under ROOT. */
1507   SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, scratch_pool));
1508
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))
1516     {
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;
1521
1522       if (authz_read_func)
1523         SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
1524                                 scratch_pool));
1525
1526       if (allowed)
1527         {
1528           if (path[0] == '/')
1529             {
1530               path++;
1531               keylen--;
1532             }
1533
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)
1537             {
1538               APR_ARRAY_PUSH(paths, const char *) = path;
1539               apr_hash_set(changed_paths, path, keylen, change);
1540             }
1541           /* ...unless this was a change to one of the parent directories of
1542              base_path. */
1543           else if (svn_relpath_skip_ancestor(path, base_repos_relpath) != NULL)
1544             {
1545               APR_ARRAY_PUSH(paths, const char *) = path;
1546               apr_hash_set(changed_paths, path, keylen, change);
1547             }
1548         }
1549     }
1550
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))
1554     low_water_mark = 0;
1555
1556   copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *));
1557
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);
1562
1563   /* Now actually handle the various paths. */
1564   iterpool = svn_pool_create(scratch_pool);
1565   for (i = 0; i < paths->nelts; i++)
1566     {
1567       const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *);
1568
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);
1574       if (err)
1575         break;
1576     }
1577
1578   if (err)
1579     return svn_error_compose_create(err, svn_editor_abort(editor));
1580   else
1581     SVN_ERR(svn_editor_complete(editor));
1582
1583   svn_pool_destroy(iterpool);
1584   return SVN_NO_ERROR;
1585 }