]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_repos/replay.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.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
43 \f
44 /*** Backstory ***/
45
46 /* The year was 2003.  Subversion usage was rampant in the world, and
47    there was a rapidly growing issues database to prove it.  To make
48    matters worse, svn_repos_dir_delta() had simply outgrown itself.
49    No longer content to simply describe the differences between two
50    trees, the function had been slowly bearing the added
51    responsibility of representing the actions that had been taken to
52    cause those differences -- a burden it was never meant to bear.
53    Now grown into a twisted mess of razor-sharp metal and glass, and
54    trembling with a sort of momentarily stayed spring force,
55    svn_repos_dir_delta was a timebomb poised for total annihilation of
56    the American Midwest.
57
58    Subversion needed a change.
59
60    Changes, in fact.  And not just in the literary segue sense.  What
61    Subversion desperately needed was a new mechanism solely
62    responsible for replaying repository actions back to some
63    interested party -- to translate and retransmit the contents of the
64    Berkeley 'changes' database file. */
65
66 /*** Overview ***/
67
68 /* The filesystem keeps a record of high-level actions that affect the
69    files and directories in itself.  The 'changes' table records
70    additions, deletions, textual and property modifications, and so
71    on.  The goal of the functions in this file is to examine those
72    change records, and use them to drive an editor interface in such a
73    way as to effectively replay those actions.
74
75    This is critically different than what svn_repos_dir_delta() was
76    designed to do.  That function describes, in the simplest way it
77    can, how to transform one tree into another.  It doesn't care
78    whether or not this was the same way a user might have done this
79    transformation.  More to the point, it doesn't care if this is how
80    those differences *did* come into being.  And it is for this reason
81    that it cannot be relied upon for tasks such as the repository
82    dumpfile-generation code, which is supposed to represent not
83    changes, but actions that cause changes.
84
85    So, what's the plan here?
86
87    First, we fetch the changes for a particular revision or
88    transaction.  We get these as an array, sorted chronologically.
89    From this array we will build a hash, keyed on the path associated
90    with each change item, and whose values are arrays of changes made
91    to that path, again preserving the chronological ordering.
92
93    Once our hash is built, we then sort all the keys of the hash (the
94    paths) using a depth-first directory sort routine.
95
96    Finally, we drive an editor, moving down our list of sorted paths,
97    and manufacturing any intermediate editor calls (directory openings
98    and closures) needed to navigate between each successive path.  For
99    each path, we replay the sorted actions that occurred at that path.
100
101    When we've finished the editor drive, we should have fully replayed
102    the filesystem events that occurred in that revision or transaction
103    (though not necessarily in the same order in which they
104    occurred). */
105
106 /* #define USE_EV2_IMPL */
107
108 \f
109 /*** Helper functions. ***/
110
111
112 /* Information for an active copy, that is a directory which we are currently
113    working on and which was added with history. */
114 struct copy_info
115 {
116   /* Destination relpath (relative to the root of the  . */
117   const char *path;
118
119   /* Copy source path (expressed as an absolute FS path) or revision.
120      NULL and SVN_INVALID_REVNUM if this is an add without history,
121      nested inside an add with history. */
122   const char *copyfrom_path;
123   svn_revnum_t copyfrom_rev;
124 };
125
126 struct path_driver_cb_baton
127 {
128   const svn_delta_editor_t *editor;
129   void *edit_baton;
130
131   /* The root of the revision we're replaying. */
132   svn_fs_root_t *root;
133
134   /* The root of the previous revision.  If this is non-NULL it means that
135      we are supposed to generate props and text deltas relative to it. */
136   svn_fs_root_t *compare_root;
137
138   apr_hash_t *changed_paths;
139
140   svn_repos_authz_func_t authz_read_func;
141   void *authz_read_baton;
142
143   const char *base_path; /* relpath */
144
145   svn_revnum_t low_water_mark;
146   /* Stack of active copy operations. */
147   apr_array_header_t *copies;
148
149   /* The global pool for this replay operation. */
150   apr_pool_t *pool;
151 };
152
153 /* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
154    the appropriate editor calls to add it and its children without any
155    history.  This is meant to be used when either a subset of the tree
156    has been ignored and we need to copy something from that subset to
157    the part of the tree we do care about, or if a subset of the tree is
158    unavailable because of authz and we need to use it as the source of
159    a copy. */
160 static svn_error_t *
161 add_subdir(svn_fs_root_t *source_root,
162            svn_fs_root_t *target_root,
163            const svn_delta_editor_t *editor,
164            void *edit_baton,
165            const char *edit_path,
166            void *parent_baton,
167            const char *source_fspath,
168            svn_repos_authz_func_t authz_read_func,
169            void *authz_read_baton,
170            apr_hash_t *changed_paths,
171            apr_pool_t *pool,
172            void **dir_baton)
173 {
174   apr_pool_t *subpool = svn_pool_create(pool);
175   apr_hash_index_t *hi, *phi;
176   apr_hash_t *dirents;
177   apr_hash_t *props;
178
179   SVN_ERR(editor->add_directory(edit_path, parent_baton, NULL,
180                                 SVN_INVALID_REVNUM, pool, dir_baton));
181
182   SVN_ERR(svn_fs_node_proplist(&props, target_root, edit_path, pool));
183
184   for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
185     {
186       const void *key;
187       void *val;
188
189       svn_pool_clear(subpool);
190       apr_hash_this(phi, &key, NULL, &val);
191       SVN_ERR(editor->change_dir_prop(*dir_baton, key, val, subpool));
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;
204       const char *copyfrom_path = NULL;
205       svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
206       const char *new_edit_path;
207       void *val;
208
209       svn_pool_clear(subpool);
210
211       apr_hash_this(hi, NULL, NULL, &val);
212
213       dent = val;
214
215       new_edit_path = svn_relpath_join(edit_path, dent->name, subpool);
216
217       /* If a file or subdirectory of the copied directory is listed as a
218          changed path (because it was modified after the copy but before the
219          commit), we remove it from the changed_paths hash so that future
220          calls to path_driver_cb_func will ignore it. */
221       change = svn_hash_gets(changed_paths, new_edit_path);
222       if (change)
223         {
224           svn_hash_sets(changed_paths, new_edit_path, NULL);
225
226           /* If it's a delete, skip this entry. */
227           if (change->change_kind == svn_fs_path_change_delete)
228             continue;
229
230           /* If it's a replacement, check for copyfrom info (if we
231              don't have it already. */
232           if (change->change_kind == svn_fs_path_change_replace)
233             {
234               if (! change->copyfrom_known)
235                 {
236                   SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
237                                              &change->copyfrom_path,
238                                              target_root, new_edit_path, pool));
239                   change->copyfrom_known = TRUE;
240                 }
241               copyfrom_path = change->copyfrom_path;
242               copyfrom_rev = change->copyfrom_rev;
243             }
244         }
245
246       if (authz_read_func)
247         SVN_ERR(authz_read_func(&readable, target_root, new_edit_path,
248                                 authz_read_baton, pool));
249
250       if (! readable)
251         continue;
252
253       if (dent->kind == svn_node_dir)
254         {
255           svn_fs_root_t *new_source_root;
256           const char *new_source_fspath;
257           void *new_dir_baton;
258
259           if (copyfrom_path)
260             {
261               svn_fs_t *fs = svn_fs_root_fs(source_root);
262               SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
263                                            copyfrom_rev, pool));
264               new_source_fspath = copyfrom_path;
265             }
266           else
267             {
268               new_source_root = source_root;
269               new_source_fspath = svn_fspath__join(source_fspath, dent->name,
270                                                    subpool);
271             }
272
273           /* ### authz considerations?
274            *
275            * I think not; when path_driver_cb_func() calls add_subdir(), it
276            * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
277            */
278           if (change && change->change_kind == svn_fs_path_change_replace
279               && copyfrom_path == NULL)
280             {
281               SVN_ERR(editor->add_directory(new_edit_path, *dir_baton,
282                                             NULL, SVN_INVALID_REVNUM,
283                                             subpool, &new_dir_baton));
284             }
285           else
286             {
287               SVN_ERR(add_subdir(new_source_root, target_root,
288                                  editor, edit_baton, new_edit_path,
289                                  *dir_baton, new_source_fspath,
290                                  authz_read_func, authz_read_baton,
291                                  changed_paths, subpool, &new_dir_baton));
292             }
293
294           SVN_ERR(editor->close_directory(new_dir_baton, subpool));
295         }
296       else if (dent->kind == svn_node_file)
297         {
298           svn_txdelta_window_handler_t delta_handler;
299           void *delta_handler_baton, *file_baton;
300           svn_txdelta_stream_t *delta_stream;
301           svn_checksum_t *checksum;
302
303           SVN_ERR(editor->add_file(new_edit_path, *dir_baton, NULL,
304                                    SVN_INVALID_REVNUM, pool, &file_baton));
305
306           SVN_ERR(svn_fs_node_proplist(&props, target_root,
307                                        new_edit_path, subpool));
308
309           for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
310             {
311               const void *key;
312
313               apr_hash_this(phi, &key, NULL, &val);
314               SVN_ERR(editor->change_file_prop(file_baton, key, val, subpool));
315             }
316
317           SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool,
318                                           &delta_handler,
319                                           &delta_handler_baton));
320
321           SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, NULL, NULL,
322                                                target_root, new_edit_path,
323                                                pool));
324
325           SVN_ERR(svn_txdelta_send_txstream(delta_stream,
326                                             delta_handler,
327                                             delta_handler_baton,
328                                             pool));
329
330           SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, target_root,
331                                        new_edit_path, TRUE, pool));
332           SVN_ERR(editor->close_file(file_baton,
333                                      svn_checksum_to_cstring(checksum, pool),
334                                      pool));
335         }
336       else
337         SVN_ERR_MALFUNCTION();
338     }
339
340   svn_pool_destroy(subpool);
341
342   return SVN_NO_ERROR;
343 }
344
345 /* Given PATH deleted under ROOT, return in READABLE whether the path was
346    readable prior to the deletion.  Consult COPIES (a stack of 'struct
347    copy_info') and AUTHZ_READ_FUNC. */
348 static svn_error_t *
349 was_readable(svn_boolean_t *readable,
350              svn_fs_root_t *root,
351              const char *path,
352              apr_array_header_t *copies,
353              svn_repos_authz_func_t authz_read_func,
354              void *authz_read_baton,
355              apr_pool_t *result_pool,
356              apr_pool_t *scratch_pool)
357 {
358   svn_fs_root_t *inquire_root;
359   const char *inquire_path;
360   struct copy_info *info = NULL;
361   const char *relpath;
362
363   /* Short circuit. */
364   if (! authz_read_func)
365     {
366       *readable = TRUE;
367       return SVN_NO_ERROR;
368     }
369
370   if (copies->nelts != 0)
371     info = APR_ARRAY_IDX(copies, copies->nelts - 1, struct copy_info *);
372
373   /* Are we under a copy? */
374   if (info && (relpath = svn_relpath_skip_ancestor(info->path, path)))
375     {
376       SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root),
377                                    info->copyfrom_rev, scratch_pool));
378       inquire_path = svn_fspath__join(info->copyfrom_path, relpath,
379                                       scratch_pool);
380     }
381   else
382     {
383       /* Compute the revision that ROOT is based on.  (Note that ROOT is not
384          r0's root, since this function is only called for deletions.)
385          ### Need a more succinct way to express this */
386       svn_revnum_t inquire_rev = SVN_INVALID_REVNUM;
387       if (svn_fs_is_txn_root(root))
388         inquire_rev = svn_fs_txn_root_base_revision(root);
389       if (svn_fs_is_revision_root(root))
390         inquire_rev =  svn_fs_revision_root_revision(root)-1;
391       SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(inquire_rev));
392
393       SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root),
394                                    inquire_rev, scratch_pool));
395       inquire_path = path;
396     }
397
398   SVN_ERR(authz_read_func(readable, inquire_root, inquire_path,
399                           authz_read_baton, result_pool));
400
401   return SVN_NO_ERROR;
402 }
403
404 /* Initialize COPYFROM_ROOT, COPYFROM_PATH, and COPYFROM_REV with the
405    revision root, fspath, and revnum of the copyfrom of CHANGE, which
406    corresponds to PATH under ROOT.  If the copyfrom info is valid
407    (i.e., is not (NULL, SVN_INVALID_REVNUM)), then initialize SRC_READABLE
408    too, consulting AUTHZ_READ_FUNC and AUTHZ_READ_BATON if provided.
409
410    NOTE: If the copyfrom information in CHANGE is marked as unknown
411    (meaning, its ->copyfrom_rev and ->copyfrom_path cannot be
412    trusted), this function will also update those members of the
413    CHANGE structure to carry accurate copyfrom information.  */
414 static svn_error_t *
415 fill_copyfrom(svn_fs_root_t **copyfrom_root,
416               const char **copyfrom_path,
417               svn_revnum_t *copyfrom_rev,
418               svn_boolean_t *src_readable,
419               svn_fs_root_t *root,
420               svn_fs_path_change2_t *change,
421               svn_repos_authz_func_t authz_read_func,
422               void *authz_read_baton,
423               const char *path,
424               apr_pool_t *result_pool,
425               apr_pool_t *scratch_pool)
426 {
427   if (! change->copyfrom_known)
428     {
429       SVN_ERR(svn_fs_copied_from(&(change->copyfrom_rev),
430                                  &(change->copyfrom_path),
431                                  root, path, result_pool));
432       change->copyfrom_known = TRUE;
433     }
434   *copyfrom_rev = change->copyfrom_rev;
435   *copyfrom_path = change->copyfrom_path;
436
437   if (*copyfrom_path && SVN_IS_VALID_REVNUM(*copyfrom_rev))
438     {
439       SVN_ERR(svn_fs_revision_root(copyfrom_root,
440                                    svn_fs_root_fs(root),
441                                    *copyfrom_rev, result_pool));
442
443       if (authz_read_func)
444         {
445           SVN_ERR(authz_read_func(src_readable, *copyfrom_root,
446                                   *copyfrom_path,
447                                   authz_read_baton, result_pool));
448         }
449       else
450         *src_readable = TRUE;
451     }
452   else
453     {
454       *copyfrom_root = NULL;
455       /* SRC_READABLE left uninitialized */
456     }
457   return SVN_NO_ERROR;
458 }
459
460 static svn_error_t *
461 path_driver_cb_func(void **dir_baton,
462                     void *parent_baton,
463                     void *callback_baton,
464                     const char *edit_path,
465                     apr_pool_t *pool)
466 {
467   struct path_driver_cb_baton *cb = callback_baton;
468   const svn_delta_editor_t *editor = cb->editor;
469   void *edit_baton = cb->edit_baton;
470   svn_fs_root_t *root = cb->root;
471   svn_fs_path_change2_t *change;
472   svn_boolean_t do_add = FALSE, do_delete = FALSE;
473   void *file_baton = NULL;
474   svn_revnum_t copyfrom_rev;
475   const char *copyfrom_path;
476   svn_fs_root_t *source_root = cb->compare_root;
477   const char *source_fspath = NULL;
478   const char *base_path = cb->base_path;
479
480   *dir_baton = NULL;
481
482   /* Initialize SOURCE_FSPATH. */
483   if (source_root)
484     source_fspath = svn_fspath__canonicalize(edit_path, pool);
485
486   /* First, flush the copies stack so it only contains ancestors of path. */
487   while (cb->copies->nelts > 0
488          && ! svn_dirent_is_ancestor(APR_ARRAY_IDX(cb->copies,
489                                                    cb->copies->nelts - 1,
490                                                    struct copy_info *)->path,
491                                      edit_path))
492     apr_array_pop(cb->copies);
493
494   change = svn_hash_gets(cb->changed_paths, edit_path);
495   if (! change)
496     {
497       /* This can only happen if the path was removed from cb->changed_paths
498          by an earlier call to add_subdir, which means the path was already
499          handled and we should simply ignore it. */
500       return SVN_NO_ERROR;
501     }
502   switch (change->change_kind)
503     {
504     case svn_fs_path_change_add:
505       do_add = TRUE;
506       break;
507
508     case svn_fs_path_change_delete:
509       do_delete = TRUE;
510       break;
511
512     case svn_fs_path_change_replace:
513       do_add = TRUE;
514       do_delete = TRUE;
515       break;
516
517     case svn_fs_path_change_modify:
518     default:
519       /* do nothing */
520       break;
521     }
522
523   /* Handle any deletions. */
524   if (do_delete)
525     {
526       svn_boolean_t readable;
527
528       /* Issue #4121: delete under under a copy, of a path that was unreadable
529          at its pre-copy location. */
530       SVN_ERR(was_readable(&readable, root, edit_path, cb->copies,
531                             cb->authz_read_func, cb->authz_read_baton,
532                             pool, pool));
533       if (readable)
534         SVN_ERR(editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
535                                      parent_baton, pool));
536     }
537
538   /* Fetch the node kind if it makes sense to do so. */
539   if (! do_delete || do_add)
540     {
541       if (change->node_kind == svn_node_unknown)
542         SVN_ERR(svn_fs_check_path(&(change->node_kind), root, edit_path, pool));
543       if ((change->node_kind != svn_node_dir) &&
544           (change->node_kind != svn_node_file))
545         return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
546                                  _("Filesystem path '%s' is neither a file "
547                                    "nor a directory"), edit_path);
548     }
549
550   /* Handle any adds/opens. */
551   if (do_add)
552     {
553       svn_boolean_t src_readable;
554       svn_fs_root_t *copyfrom_root;
555
556       /* Was this node copied? */
557       SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
558                             &src_readable, root, change,
559                             cb->authz_read_func, cb->authz_read_baton,
560                             edit_path, pool, pool));
561
562       /* If we have a copyfrom path, and we can't read it or we're just
563          ignoring it, or the copyfrom rev is prior to the low water mark
564          then we just null them out and do a raw add with no history at
565          all. */
566       if (copyfrom_path
567           && ((! src_readable)
568               || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL)
569               || (cb->low_water_mark > copyfrom_rev)))
570         {
571           copyfrom_path = NULL;
572           copyfrom_rev = SVN_INVALID_REVNUM;
573         }
574
575       /* Do the right thing based on the path KIND. */
576       if (change->node_kind == svn_node_dir)
577         {
578           /* If this is a copy, but we can't represent it as such,
579              then we just do a recursive add of the source path
580              contents. */
581           if (change->copyfrom_path && ! copyfrom_path)
582             {
583               SVN_ERR(add_subdir(copyfrom_root, root, editor, edit_baton,
584                                  edit_path, parent_baton, change->copyfrom_path,
585                                  cb->authz_read_func, cb->authz_read_baton,
586                                  cb->changed_paths, pool, dir_baton));
587             }
588           else
589             {
590               SVN_ERR(editor->add_directory(edit_path, parent_baton,
591                                             copyfrom_path, copyfrom_rev,
592                                             pool, dir_baton));
593             }
594         }
595       else
596         {
597           SVN_ERR(editor->add_file(edit_path, parent_baton, copyfrom_path,
598                                    copyfrom_rev, pool, &file_baton));
599         }
600
601       /* If we represent this as a copy... */
602       if (copyfrom_path)
603         {
604           /* If it is a directory, make sure descendants get the correct
605              delta source by remembering that we are operating inside a
606              (possibly nested) copy operation. */
607           if (change->node_kind == svn_node_dir)
608             {
609               struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
610
611               info->path = apr_pstrdup(cb->pool, edit_path);
612               info->copyfrom_path = apr_pstrdup(cb->pool, copyfrom_path);
613               info->copyfrom_rev = copyfrom_rev;
614
615               APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
616             }
617
618           /* Save the source so that we can use it later, when we
619              need to generate text and prop deltas. */
620           source_root = copyfrom_root;
621           source_fspath = copyfrom_path;
622         }
623       else
624         /* Else, we are an add without history... */
625         {
626           /* If an ancestor is added with history, we need to forget about
627              that here, go on with life and repeat all the mistakes of our
628              past... */
629           if (change->node_kind == svn_node_dir && cb->copies->nelts > 0)
630             {
631               struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
632
633               info->path = apr_pstrdup(cb->pool, edit_path);
634               info->copyfrom_path = NULL;
635               info->copyfrom_rev = SVN_INVALID_REVNUM;
636
637               APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
638             }
639           source_root = NULL;
640           source_fspath = NULL;
641         }
642     }
643   else if (! do_delete)
644     {
645       /* Do the right thing based on the path KIND (and the presence
646          of a PARENT_BATON). */
647       if (change->node_kind == svn_node_dir)
648         {
649           if (parent_baton)
650             {
651               SVN_ERR(editor->open_directory(edit_path, parent_baton,
652                                              SVN_INVALID_REVNUM,
653                                              pool, dir_baton));
654             }
655           else
656             {
657               SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
658                                         pool, dir_baton));
659             }
660         }
661       else
662         {
663           SVN_ERR(editor->open_file(edit_path, parent_baton, SVN_INVALID_REVNUM,
664                                     pool, &file_baton));
665         }
666       /* If we are inside an add with history, we need to adjust the
667          delta source. */
668       if (cb->copies->nelts > 0)
669         {
670           struct copy_info *info = APR_ARRAY_IDX(cb->copies,
671                                                  cb->copies->nelts - 1,
672                                                  struct copy_info *);
673           if (info->copyfrom_path)
674             {
675               const char *relpath = svn_relpath_skip_ancestor(info->path,
676                                                               edit_path);
677               SVN_ERR_ASSERT(relpath && *relpath);
678               SVN_ERR(svn_fs_revision_root(&source_root,
679                                            svn_fs_root_fs(root),
680                                            info->copyfrom_rev, pool));
681               source_fspath = svn_fspath__join(info->copyfrom_path,
682                                                relpath, pool);
683             }
684           else
685             {
686               /* This is an add without history, nested inside an
687                  add with history.  We have no delta source in this case. */
688               source_root = NULL;
689               source_fspath = NULL;
690             }
691         }
692     }
693
694   if (! do_delete || do_add)
695     {
696       /* Is this a copy that was downgraded to a raw add?  (If so,
697          we'll need to transmit properties and file contents and such
698          for it regardless of what the CHANGE structure's text_mod
699          and prop_mod flags say.)  */
700       svn_boolean_t downgraded_copy = (change->copyfrom_known
701                                        && change->copyfrom_path
702                                        && (! copyfrom_path));
703
704       /* Handle property modifications. */
705       if (change->prop_mod || downgraded_copy)
706         {
707           if (cb->compare_root)
708             {
709               apr_array_header_t *prop_diffs;
710               apr_hash_t *old_props;
711               apr_hash_t *new_props;
712               int i;
713
714               if (source_root)
715                 SVN_ERR(svn_fs_node_proplist(&old_props, source_root,
716                                              source_fspath, pool));
717               else
718                 old_props = apr_hash_make(pool);
719
720               SVN_ERR(svn_fs_node_proplist(&new_props, root, edit_path, pool));
721
722               SVN_ERR(svn_prop_diffs(&prop_diffs, new_props, old_props,
723                                      pool));
724
725               for (i = 0; i < prop_diffs->nelts; ++i)
726                 {
727                   svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
728                    if (change->node_kind == svn_node_dir)
729                      SVN_ERR(editor->change_dir_prop(*dir_baton, pc->name,
730                                                      pc->value, pool));
731                    else if (change->node_kind == svn_node_file)
732                      SVN_ERR(editor->change_file_prop(file_baton, pc->name,
733                                                       pc->value, pool));
734                 }
735             }
736           else
737             {
738               /* Just do a dummy prop change to signal that there are *any*
739                  propmods. */
740               if (change->node_kind == svn_node_dir)
741                 SVN_ERR(editor->change_dir_prop(*dir_baton, "", NULL,
742                                                 pool));
743               else if (change->node_kind == svn_node_file)
744                 SVN_ERR(editor->change_file_prop(file_baton, "", NULL,
745                                                  pool));
746             }
747         }
748
749       /* Handle textual modifications. */
750       if (change->node_kind == svn_node_file
751           && (change->text_mod || downgraded_copy))
752         {
753           svn_txdelta_window_handler_t delta_handler;
754           void *delta_handler_baton;
755           const char *hex_digest = NULL;
756
757           if (cb->compare_root && source_root && source_fspath)
758             {
759               svn_checksum_t *checksum;
760               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
761                                            source_root, source_fspath, TRUE,
762                                            pool));
763               hex_digest = svn_checksum_to_cstring(checksum, pool);
764             }
765
766           SVN_ERR(editor->apply_textdelta(file_baton, hex_digest, pool,
767                                           &delta_handler,
768                                           &delta_handler_baton));
769           if (cb->compare_root)
770             {
771               svn_txdelta_stream_t *delta_stream;
772
773               SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, source_root,
774                                                    source_fspath, root,
775                                                    edit_path, pool));
776               SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
777                                                 delta_handler_baton, pool));
778             }
779           else
780             SVN_ERR(delta_handler(NULL, delta_handler_baton));
781         }
782     }
783
784   /* Close the file baton if we opened it. */
785   if (file_baton)
786     {
787       svn_checksum_t *checksum;
788       SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, edit_path,
789                                    TRUE, pool));
790       SVN_ERR(editor->close_file(file_baton,
791                                  svn_checksum_to_cstring(checksum, pool),
792                                  pool));
793     }
794
795   return SVN_NO_ERROR;
796 }
797
798 #ifdef USE_EV2_IMPL
799 static svn_error_t *
800 fetch_kind_func(svn_node_kind_t *kind,
801                 void *baton,
802                 const char *path,
803                 svn_revnum_t base_revision,
804                 apr_pool_t *scratch_pool)
805 {
806   svn_fs_root_t *root = baton;
807   svn_fs_root_t *prev_root;
808   svn_fs_t *fs = svn_fs_root_fs(root);
809
810   if (!SVN_IS_VALID_REVNUM(base_revision))
811     base_revision = svn_fs_revision_root_revision(root) - 1;
812
813   SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
814   SVN_ERR(svn_fs_check_path(kind, prev_root, path, scratch_pool));
815
816   return SVN_NO_ERROR;
817 }
818
819 static svn_error_t *
820 fetch_props_func(apr_hash_t **props,
821                  void *baton,
822                  const char *path,
823                  svn_revnum_t base_revision,
824                  apr_pool_t *result_pool,
825                  apr_pool_t *scratch_pool)
826 {
827   svn_fs_root_t *root = baton;
828   svn_fs_root_t *prev_root;
829   svn_fs_t *fs = svn_fs_root_fs(root);
830
831   if (!SVN_IS_VALID_REVNUM(base_revision))
832     base_revision = svn_fs_revision_root_revision(root) - 1;
833
834   SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
835   SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool));
836
837   return SVN_NO_ERROR;
838 }
839 #endif
840
841
842 \f
843
844 svn_error_t *
845 svn_repos_replay2(svn_fs_root_t *root,
846                   const char *base_path,
847                   svn_revnum_t low_water_mark,
848                   svn_boolean_t send_deltas,
849                   const svn_delta_editor_t *editor,
850                   void *edit_baton,
851                   svn_repos_authz_func_t authz_read_func,
852                   void *authz_read_baton,
853                   apr_pool_t *pool)
854 {
855 #ifndef USE_EV2_IMPL
856   apr_hash_t *fs_changes;
857   apr_hash_t *changed_paths;
858   apr_hash_index_t *hi;
859   apr_array_header_t *paths;
860   struct path_driver_cb_baton cb_baton;
861
862   /* Special-case r0, which we know is an empty revision; if we don't
863      special-case it we might end up trying to compare it to "r-1". */
864   if (svn_fs_is_revision_root(root) && svn_fs_revision_root_revision(root) == 0)
865     {
866       SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
867       return SVN_NO_ERROR;
868     }
869
870   /* Fetch the paths changed under ROOT. */
871   SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, pool));
872
873   if (! base_path)
874     base_path = "";
875   else if (base_path[0] == '/')
876     ++base_path;
877
878   /* Make an array from the keys of our CHANGED_PATHS hash, and copy
879      the values into a new hash whose keys have no leading slashes. */
880   paths = apr_array_make(pool, apr_hash_count(fs_changes),
881                          sizeof(const char *));
882   changed_paths = apr_hash_make(pool);
883   for (hi = apr_hash_first(pool, fs_changes); hi; hi = apr_hash_next(hi))
884     {
885       const void *key;
886       void *val;
887       apr_ssize_t keylen;
888       const char *path;
889       svn_fs_path_change2_t *change;
890       svn_boolean_t allowed = TRUE;
891
892       apr_hash_this(hi, &key, &keylen, &val);
893       path = key;
894       change = val;
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 = svn__apr_hash_index_val(hi);
1067       const char *copyfrom_path = NULL;
1068       svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
1069       const char *child_relpath;
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, props, checksum,
1461                                         contents));
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 void *key;
1518       void *val;
1519       apr_ssize_t keylen;
1520       const char *path;
1521       svn_fs_path_change2_t *change;
1522       svn_boolean_t allowed = TRUE;
1523
1524       apr_hash_this(hi, &key, &keylen, &val);
1525       path = key;
1526       change = val;
1527
1528       if (authz_read_func)
1529         SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
1530                                 scratch_pool));
1531
1532       if (allowed)
1533         {
1534           if (path[0] == '/')
1535             {
1536               path++;
1537               keylen--;
1538             }
1539
1540           /* If the base_path doesn't match the top directory of this path
1541              we don't want anything to do with it... */
1542           if (svn_relpath_skip_ancestor(base_repos_relpath, path) != NULL)
1543             {
1544               APR_ARRAY_PUSH(paths, const char *) = path;
1545               apr_hash_set(changed_paths, path, keylen, change);
1546             }
1547           /* ...unless this was a change to one of the parent directories of
1548              base_path. */
1549           else if (svn_relpath_skip_ancestor(path, base_repos_relpath) != NULL)
1550             {
1551               APR_ARRAY_PUSH(paths, const char *) = path;
1552               apr_hash_set(changed_paths, path, keylen, change);
1553             }
1554         }
1555     }
1556
1557   /* If we were not given a low water mark, assume that everything is there,
1558      all the way back to revision 0. */
1559   if (! SVN_IS_VALID_REVNUM(low_water_mark))
1560     low_water_mark = 0;
1561
1562   copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *));
1563
1564   /* Sort the paths.  Although not strictly required by the API, this has
1565      the pleasant side effect of maintaining a consistent ordering of
1566      dumpfile contents. */
1567   qsort(paths->elts, paths->nelts, paths->elt_size, svn_sort_compare_paths);
1568
1569   /* Now actually handle the various paths. */
1570   iterpool = svn_pool_create(scratch_pool);
1571   for (i = 0; i < paths->nelts; i++)
1572     {
1573       const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *);
1574
1575       svn_pool_clear(iterpool);
1576       err = replay_node(root, repos_relpath, editor, low_water_mark,
1577                         base_repos_relpath, copies, changed_paths,
1578                         authz_read_func, authz_read_baton,
1579                         scratch_pool, iterpool);
1580       if (err)
1581         break;
1582     }
1583
1584   if (err)
1585     return svn_error_compose_create(err, svn_editor_abort(editor));
1586   else
1587     SVN_ERR(svn_editor_complete(editor));
1588
1589   svn_pool_destroy(iterpool);
1590   return SVN_NO_ERROR;
1591 }