]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_repos/replay.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.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_change3_t *change;
202       svn_boolean_t readable = TRUE;
203       svn_fs_dirent_t *dent = apr_hash_this_val(hi);
204       const char *copyfrom_path = NULL;
205       svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
206       const char *new_edit_path;
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_change3_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_change3_t *change;
467   svn_boolean_t do_add = FALSE, do_delete = FALSE;
468   void *file_baton = NULL;
469   svn_revnum_t copyfrom_rev;
470   const char *copyfrom_path;
471   svn_fs_root_t *source_root = cb->compare_root;
472   const char *source_fspath = NULL;
473   const char *base_path = cb->base_path;
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       /* Was this node copied? */
559       SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
560                             &src_readable, root, change,
561                             cb->authz_read_func, cb->authz_read_baton,
562                             edit_path, pool, pool));
563
564       /* If we have a copyfrom path, and we can't read it or we're just
565          ignoring it, or the copyfrom rev is prior to the low water mark
566          then we just null them out and do a raw add with no history at
567          all. */
568       if (copyfrom_path
569           && ((! src_readable)
570               || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL)
571               || (cb->low_water_mark > copyfrom_rev)))
572         {
573           copyfrom_path = NULL;
574           copyfrom_rev = SVN_INVALID_REVNUM;
575         }
576
577       /* Do the right thing based on the path KIND. */
578       if (change->node_kind == svn_node_dir)
579         {
580           /* If this is a copy, but we can't represent it as such,
581              then we just do a recursive add of the source path
582              contents. */
583           if (change->copyfrom_path && ! copyfrom_path)
584             {
585               SVN_ERR(add_subdir(copyfrom_root, root, editor, edit_baton,
586                                  edit_path, parent_baton, change->copyfrom_path,
587                                  cb->authz_read_func, cb->authz_read_baton,
588                                  cb->changed_paths, pool, dir_baton));
589             }
590           else
591             {
592               SVN_ERR(editor->add_directory(edit_path, parent_baton,
593                                             copyfrom_path, copyfrom_rev,
594                                             pool, dir_baton));
595             }
596         }
597       else
598         {
599           SVN_ERR(editor->add_file(edit_path, parent_baton, copyfrom_path,
600                                    copyfrom_rev, pool, &file_baton));
601         }
602
603       /* If we represent this as a copy... */
604       if (copyfrom_path)
605         {
606           /* If it is a directory, make sure descendants get the correct
607              delta source by remembering that we are operating inside a
608              (possibly nested) copy operation. */
609           if (change->node_kind == svn_node_dir)
610             {
611               struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
612
613               info->path = apr_pstrdup(cb->pool, edit_path);
614               info->copyfrom_path = apr_pstrdup(cb->pool, copyfrom_path);
615               info->copyfrom_rev = copyfrom_rev;
616
617               APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
618             }
619
620           /* Save the source so that we can use it later, when we
621              need to generate text and prop deltas. */
622           source_root = copyfrom_root;
623           source_fspath = copyfrom_path;
624         }
625       else
626         /* Else, we are an add without history... */
627         {
628           /* If an ancestor is added with history, we need to forget about
629              that here, go on with life and repeat all the mistakes of our
630              past... */
631           if (change->node_kind == svn_node_dir && cb->copies->nelts > 0)
632             {
633               struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
634
635               info->path = apr_pstrdup(cb->pool, edit_path);
636               info->copyfrom_path = NULL;
637               info->copyfrom_rev = SVN_INVALID_REVNUM;
638
639               APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
640             }
641           source_root = NULL;
642           source_fspath = NULL;
643         }
644     }
645   else if (! do_delete)
646     {
647       /* Do the right thing based on the path KIND (and the presence
648          of a PARENT_BATON). */
649       if (change->node_kind == svn_node_dir)
650         {
651           if (parent_baton)
652             {
653               SVN_ERR(editor->open_directory(edit_path, parent_baton,
654                                              SVN_INVALID_REVNUM,
655                                              pool, dir_baton));
656             }
657           else
658             {
659               SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
660                                         pool, dir_baton));
661             }
662         }
663       else
664         {
665           SVN_ERR(editor->open_file(edit_path, parent_baton, SVN_INVALID_REVNUM,
666                                     pool, &file_baton));
667         }
668       /* If we are inside an add with history, we need to adjust the
669          delta source. */
670       if (cb->copies->nelts > 0)
671         {
672           struct copy_info *info = APR_ARRAY_IDX(cb->copies,
673                                                  cb->copies->nelts - 1,
674                                                  struct copy_info *);
675           if (info->copyfrom_path)
676             {
677               const char *relpath = svn_relpath_skip_ancestor(info->path,
678                                                               edit_path);
679               SVN_ERR_ASSERT(relpath && *relpath);
680               SVN_ERR(svn_fs_revision_root(&source_root,
681                                            svn_fs_root_fs(root),
682                                            info->copyfrom_rev, pool));
683               source_fspath = svn_fspath__join(info->copyfrom_path,
684                                                relpath, pool);
685             }
686           else
687             {
688               /* This is an add without history, nested inside an
689                  add with history.  We have no delta source in this case. */
690               source_root = NULL;
691               source_fspath = NULL;
692             }
693         }
694     }
695
696   if (! do_delete || do_add)
697     {
698       /* Is this a copy that was downgraded to a raw add?  (If so,
699          we'll need to transmit properties and file contents and such
700          for it regardless of what the CHANGE structure's text_mod
701          and prop_mod flags say.)  */
702       svn_boolean_t downgraded_copy = (change->copyfrom_known
703                                        && change->copyfrom_path
704                                        && (! copyfrom_path));
705
706       /* Handle property modifications. */
707       if (change->prop_mod || downgraded_copy)
708         {
709           if (cb->compare_root)
710             {
711               apr_array_header_t *prop_diffs;
712               apr_hash_t *old_props;
713               apr_hash_t *new_props;
714               int i;
715
716               if (source_root)
717                 SVN_ERR(svn_fs_node_proplist(&old_props, source_root,
718                                              source_fspath, pool));
719               else
720                 old_props = apr_hash_make(pool);
721
722               SVN_ERR(svn_fs_node_proplist(&new_props, root, edit_path, pool));
723
724               SVN_ERR(svn_prop_diffs(&prop_diffs, new_props, old_props,
725                                      pool));
726
727               for (i = 0; i < prop_diffs->nelts; ++i)
728                 {
729                   svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
730                    if (change->node_kind == svn_node_dir)
731                      SVN_ERR(editor->change_dir_prop(*dir_baton, pc->name,
732                                                      pc->value, pool));
733                    else if (change->node_kind == svn_node_file)
734                      SVN_ERR(editor->change_file_prop(file_baton, pc->name,
735                                                       pc->value, pool));
736                 }
737             }
738           else
739             {
740               /* Just do a dummy prop change to signal that there are *any*
741                  propmods. */
742               if (change->node_kind == svn_node_dir)
743                 SVN_ERR(editor->change_dir_prop(*dir_baton, "", NULL,
744                                                 pool));
745               else if (change->node_kind == svn_node_file)
746                 SVN_ERR(editor->change_file_prop(file_baton, "", NULL,
747                                                  pool));
748             }
749         }
750
751       /* Handle textual modifications. */
752       if (change->node_kind == svn_node_file
753           && (change->text_mod || downgraded_copy))
754         {
755           svn_txdelta_window_handler_t delta_handler;
756           void *delta_handler_baton;
757           const char *hex_digest = NULL;
758
759           if (cb->compare_root && source_root && source_fspath)
760             {
761               svn_checksum_t *checksum;
762               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
763                                            source_root, source_fspath, TRUE,
764                                            pool));
765               hex_digest = svn_checksum_to_cstring(checksum, pool);
766             }
767
768           SVN_ERR(editor->apply_textdelta(file_baton, hex_digest, pool,
769                                           &delta_handler,
770                                           &delta_handler_baton));
771           if (cb->compare_root)
772             {
773               svn_txdelta_stream_t *delta_stream;
774
775               SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, source_root,
776                                                    source_fspath, root,
777                                                    edit_path, pool));
778               SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
779                                                 delta_handler_baton, pool));
780             }
781           else
782             SVN_ERR(delta_handler(NULL, delta_handler_baton));
783         }
784     }
785
786   /* Close the file baton if we opened it. */
787   if (file_baton)
788     {
789       svn_checksum_t *checksum;
790       SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, edit_path,
791                                    TRUE, pool));
792       SVN_ERR(editor->close_file(file_baton,
793                                  svn_checksum_to_cstring(checksum, pool),
794                                  pool));
795     }
796
797   return SVN_NO_ERROR;
798 }
799
800 #ifdef USE_EV2_IMPL
801 static svn_error_t *
802 fetch_kind_func(svn_node_kind_t *kind,
803                 void *baton,
804                 const char *path,
805                 svn_revnum_t base_revision,
806                 apr_pool_t *scratch_pool)
807 {
808   svn_fs_root_t *root = baton;
809   svn_fs_root_t *prev_root;
810   svn_fs_t *fs = svn_fs_root_fs(root);
811
812   if (!SVN_IS_VALID_REVNUM(base_revision))
813     base_revision = svn_fs_revision_root_revision(root) - 1;
814
815   SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
816   SVN_ERR(svn_fs_check_path(kind, prev_root, path, scratch_pool));
817
818   return SVN_NO_ERROR;
819 }
820
821 static svn_error_t *
822 fetch_props_func(apr_hash_t **props,
823                  void *baton,
824                  const char *path,
825                  svn_revnum_t base_revision,
826                  apr_pool_t *result_pool,
827                  apr_pool_t *scratch_pool)
828 {
829   svn_fs_root_t *root = baton;
830   svn_fs_root_t *prev_root;
831   svn_fs_t *fs = svn_fs_root_fs(root);
832
833   if (!SVN_IS_VALID_REVNUM(base_revision))
834     base_revision = svn_fs_revision_root_revision(root) - 1;
835
836   SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
837   SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool));
838
839   return SVN_NO_ERROR;
840 }
841 #endif
842
843
844 \f
845
846 /* Retrieve the path changes under ROOT, filter them with AUTHZ_READ_FUNC
847    and AUTHZ_READ_BATON and return those that intersect with BASE_RELPATH.
848
849    The svn_fs_path_change3_t* will be returned in *CHANGED_PATHS, keyed by
850    their path.  The paths themselves are additionally returned in *PATHS.
851
852    Allocate the returned data in RESULT_POOL and use SCRATCH_POOL for
853    temporary allocations.
854  */
855 static svn_error_t *
856 get_relevant_changes(apr_hash_t **changed_paths,
857                      apr_array_header_t **paths,
858                      svn_fs_root_t *root,
859                      const char *base_relpath,
860                      svn_repos_authz_func_t authz_read_func,
861                      void *authz_read_baton,
862                      apr_pool_t *result_pool,
863                      apr_pool_t *scratch_pool)
864 {
865   svn_fs_path_change_iterator_t *iterator;
866   svn_fs_path_change3_t *change;
867   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
868
869   /* Fetch the paths changed under ROOT. */
870   SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool));
871   SVN_ERR(svn_fs_path_change_get(&change, iterator));
872
873   /* Make an array from the keys of our CHANGED_PATHS hash, and copy
874      the values into a new hash whose keys have no leading slashes. */
875   *paths = apr_array_make(result_pool, 16, sizeof(const char *));
876   *changed_paths = apr_hash_make(result_pool);
877   while (change)
878     {
879       const char *path = change->path.data;
880       apr_ssize_t keylen = change->path.len;
881       svn_boolean_t allowed = TRUE;
882
883       svn_pool_clear(iterpool);
884       if (authz_read_func)
885         SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
886                                 iterpool));
887
888       if (allowed)
889         {
890           if (path[0] == '/')
891             {
892               path++;
893               keylen--;
894             }
895
896           /* If the base_path doesn't match the top directory of this path
897              we don't want anything to do with it... 
898              ...unless this was a change to one of the parent directories of
899              base_path. */
900           if (   svn_relpath_skip_ancestor(base_relpath, path)
901               || svn_relpath_skip_ancestor(path, base_relpath))
902             {
903               change = svn_fs_path_change3_dup(change, result_pool);
904               path = change->path.data;
905               if (path[0] == '/')
906                 path++;
907
908               APR_ARRAY_PUSH(*paths, const char *) = path;
909               apr_hash_set(*changed_paths, path, keylen, change);
910             }
911         }
912
913       SVN_ERR(svn_fs_path_change_get(&change, iterator));
914     }
915
916   svn_pool_destroy(iterpool);
917   return SVN_NO_ERROR;
918 }
919
920 svn_error_t *
921 svn_repos_replay2(svn_fs_root_t *root,
922                   const char *base_path,
923                   svn_revnum_t low_water_mark,
924                   svn_boolean_t send_deltas,
925                   const svn_delta_editor_t *editor,
926                   void *edit_baton,
927                   svn_repos_authz_func_t authz_read_func,
928                   void *authz_read_baton,
929                   apr_pool_t *pool)
930 {
931 #ifndef USE_EV2_IMPL
932   apr_hash_t *changed_paths;
933   apr_array_header_t *paths;
934   struct path_driver_cb_baton cb_baton;
935
936   /* Special-case r0, which we know is an empty revision; if we don't
937      special-case it we might end up trying to compare it to "r-1". */
938   if (svn_fs_is_revision_root(root) && svn_fs_revision_root_revision(root) == 0)
939     {
940       SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
941       return SVN_NO_ERROR;
942     }
943
944   if (! base_path)
945     base_path = "";
946   else if (base_path[0] == '/')
947     ++base_path;
948
949   /* Fetch the paths changed under ROOT. */
950   SVN_ERR(get_relevant_changes(&changed_paths, &paths, root, base_path,
951                                authz_read_func, authz_read_baton,
952                                pool, pool));
953
954   /* If we were not given a low water mark, assume that everything is there,
955      all the way back to revision 0. */
956   if (! SVN_IS_VALID_REVNUM(low_water_mark))
957     low_water_mark = 0;
958
959   /* Initialize our callback baton. */
960   cb_baton.editor = editor;
961   cb_baton.edit_baton = edit_baton;
962   cb_baton.root = root;
963   cb_baton.changed_paths = changed_paths;
964   cb_baton.authz_read_func = authz_read_func;
965   cb_baton.authz_read_baton = authz_read_baton;
966   cb_baton.base_path = base_path;
967   cb_baton.low_water_mark = low_water_mark;
968   cb_baton.compare_root = NULL;
969
970   if (send_deltas)
971     {
972       SVN_ERR(svn_fs_revision_root(&cb_baton.compare_root,
973                                    svn_fs_root_fs(root),
974                                    svn_fs_is_revision_root(root)
975                                      ? svn_fs_revision_root_revision(root) - 1
976                                      : svn_fs_txn_root_base_revision(root),
977                                    pool));
978     }
979
980   cb_baton.copies = apr_array_make(pool, 4, sizeof(struct copy_info *));
981   cb_baton.pool = pool;
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   /* Call the path-based editor driver. */
992   return svn_delta_path_driver2(editor, edit_baton,
993                                 paths, TRUE,
994                                 path_driver_cb_func, &cb_baton, pool);
995 #else
996   svn_editor_t *editorv2;
997   struct svn_delta__extra_baton *exb;
998   svn_delta__unlock_func_t unlock_func;
999   svn_boolean_t send_abs_paths;
1000   const char *repos_root = "";
1001   void *unlock_baton;
1002
1003   /* If we were not given a low water mark, assume that everything is there,
1004      all the way back to revision 0. */
1005   if (! SVN_IS_VALID_REVNUM(low_water_mark))
1006     low_water_mark = 0;
1007
1008   /* Special-case r0, which we know is an empty revision; if we don't
1009      special-case it we might end up trying to compare it to "r-1". */
1010   if (svn_fs_is_revision_root(root)
1011         && svn_fs_revision_root_revision(root) == 0)
1012     {
1013       SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
1014       return SVN_NO_ERROR;
1015     }
1016
1017   /* Determine the revision to use throughout the edit, and call
1018      EDITOR's set_target_revision() function.  */
1019   if (svn_fs_is_revision_root(root))
1020     {
1021       svn_revnum_t revision = svn_fs_revision_root_revision(root);
1022       SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
1023     }
1024
1025   if (! base_path)
1026     base_path = "";
1027   else if (base_path[0] == '/')
1028     ++base_path;
1029
1030   /* Use the shim to convert our editor to an Ev2 editor, and pass it down
1031      the stack. */
1032   SVN_ERR(svn_delta__editor_from_delta(&editorv2, &exb,
1033                                        &unlock_func, &unlock_baton,
1034                                        editor, edit_baton,
1035                                        &send_abs_paths,
1036                                        repos_root, "",
1037                                        NULL, NULL,
1038                                        fetch_kind_func, root,
1039                                        fetch_props_func, root,
1040                                        pool, pool));
1041
1042   /* Tell the shim that we're starting the process. */
1043   SVN_ERR(exb->start_edit(exb->baton, svn_fs_revision_root_revision(root)));
1044
1045   /* ### We're ignoring SEND_DELTAS here. */
1046   SVN_ERR(svn_repos__replay_ev2(root, base_path, low_water_mark,
1047                                 editorv2, authz_read_func, authz_read_baton,
1048                                 pool));
1049
1050   return SVN_NO_ERROR;
1051 #endif
1052 }
1053
1054 \f
1055 /*****************************************************************
1056  *                      Ev2 Implementation                       *
1057  *****************************************************************/
1058
1059 /* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
1060    the appropriate editor calls to add it and its children without any
1061    history.  This is meant to be used when either a subset of the tree
1062    has been ignored and we need to copy something from that subset to
1063    the part of the tree we do care about, or if a subset of the tree is
1064    unavailable because of authz and we need to use it as the source of
1065    a copy. */
1066 static svn_error_t *
1067 add_subdir_ev2(svn_fs_root_t *source_root,
1068                svn_fs_root_t *target_root,
1069                svn_editor_t *editor,
1070                const char *repos_relpath,
1071                const char *source_fspath,
1072                svn_repos_authz_func_t authz_read_func,
1073                void *authz_read_baton,
1074                apr_hash_t *changed_paths,
1075                apr_pool_t *result_pool,
1076                apr_pool_t *scratch_pool)
1077 {
1078   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1079   apr_hash_index_t *hi;
1080   apr_hash_t *dirents;
1081   apr_hash_t *props = NULL;
1082   apr_array_header_t *children = NULL;
1083
1084   SVN_ERR(svn_fs_node_proplist(&props, target_root, repos_relpath,
1085                                scratch_pool));
1086
1087   SVN_ERR(svn_editor_add_directory(editor, repos_relpath, children,
1088                                    props, SVN_INVALID_REVNUM));
1089
1090   /* We have to get the dirents from the source path, not the target,
1091      because we want nested copies from *readable* paths to be handled by
1092      path_driver_cb_func, not add_subdir (in order to preserve history). */
1093   SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath,
1094                              scratch_pool));
1095
1096   for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
1097     {
1098       svn_fs_path_change3_t *change;
1099       svn_boolean_t readable = TRUE;
1100       svn_fs_dirent_t *dent = apr_hash_this_val(hi);
1101       const char *copyfrom_path = NULL;
1102       svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
1103       const char *child_relpath;
1104
1105       svn_pool_clear(iterpool);
1106
1107       child_relpath = svn_relpath_join(repos_relpath, dent->name, iterpool);
1108
1109       /* If a file or subdirectory of the copied directory is listed as a
1110          changed path (because it was modified after the copy but before the
1111          commit), we remove it from the changed_paths hash so that future
1112          calls to path_driver_cb_func will ignore it. */
1113       change = svn_hash_gets(changed_paths, child_relpath);
1114       if (change)
1115         {
1116           svn_hash_sets(changed_paths, child_relpath, NULL);
1117
1118           /* If it's a delete, skip this entry. */
1119           if (change->change_kind == svn_fs_path_change_delete)
1120             continue;
1121
1122           /* If it's a replacement, check for copyfrom info (if we
1123              don't have it already. */
1124           if (change->change_kind == svn_fs_path_change_replace)
1125             {
1126               if (! change->copyfrom_known)
1127                 {
1128                   SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
1129                                              &change->copyfrom_path,
1130                                              target_root, child_relpath,
1131                                              result_pool));
1132                   change->copyfrom_known = TRUE;
1133                 }
1134               copyfrom_path = change->copyfrom_path;
1135               copyfrom_rev = change->copyfrom_rev;
1136             }
1137         }
1138
1139       if (authz_read_func)
1140         SVN_ERR(authz_read_func(&readable, target_root, child_relpath,
1141                                 authz_read_baton, iterpool));
1142
1143       if (! readable)
1144         continue;
1145
1146       if (dent->kind == svn_node_dir)
1147         {
1148           svn_fs_root_t *new_source_root;
1149           const char *new_source_fspath;
1150
1151           if (copyfrom_path)
1152             {
1153               svn_fs_t *fs = svn_fs_root_fs(source_root);
1154               SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
1155                                            copyfrom_rev, result_pool));
1156               new_source_fspath = copyfrom_path;
1157             }
1158           else
1159             {
1160               new_source_root = source_root;
1161               new_source_fspath = svn_fspath__join(source_fspath, dent->name,
1162                                                    iterpool);
1163             }
1164
1165           /* ### authz considerations?
1166            *
1167            * I think not; when path_driver_cb_func() calls add_subdir(), it
1168            * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
1169            */
1170           if (change && change->change_kind == svn_fs_path_change_replace
1171               && copyfrom_path == NULL)
1172             {
1173               SVN_ERR(svn_editor_add_directory(editor, child_relpath,
1174                                                children, props,
1175                                                SVN_INVALID_REVNUM));
1176             }
1177           else
1178             {
1179               SVN_ERR(add_subdir_ev2(new_source_root, target_root,
1180                                      editor, child_relpath,
1181                                      new_source_fspath,
1182                                      authz_read_func, authz_read_baton,
1183                                      changed_paths, result_pool, iterpool));
1184             }
1185         }
1186       else if (dent->kind == svn_node_file)
1187         {
1188           svn_checksum_t *checksum;
1189           svn_stream_t *contents;
1190
1191           SVN_ERR(svn_fs_node_proplist(&props, target_root,
1192                                        child_relpath, iterpool));
1193
1194           SVN_ERR(svn_fs_file_contents(&contents, target_root,
1195                                        child_relpath, iterpool));
1196
1197           SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1198                                        target_root,
1199                                        child_relpath, TRUE, iterpool));
1200
1201           SVN_ERR(svn_editor_add_file(editor, child_relpath, checksum,
1202                                       contents, props, SVN_INVALID_REVNUM));
1203         }
1204       else
1205         SVN_ERR_MALFUNCTION();
1206     }
1207
1208   svn_pool_destroy(iterpool);
1209
1210   return SVN_NO_ERROR;
1211 }
1212
1213 static svn_error_t *
1214 replay_node(svn_fs_root_t *root,
1215             const char *repos_relpath,
1216             svn_editor_t *editor,
1217             svn_revnum_t low_water_mark,
1218             const char *base_repos_relpath,
1219             apr_array_header_t *copies,
1220             apr_hash_t *changed_paths,
1221             svn_repos_authz_func_t authz_read_func,
1222             void *authz_read_baton,
1223             apr_pool_t *result_pool,
1224             apr_pool_t *scratch_pool)
1225 {
1226   svn_fs_path_change3_t *change;
1227   svn_boolean_t do_add = FALSE;
1228   svn_boolean_t do_delete = FALSE;
1229   svn_revnum_t copyfrom_rev;
1230   const char *copyfrom_path;
1231   svn_revnum_t replaces_rev;
1232
1233   /* First, flush the copies stack so it only contains ancestors of path. */
1234   while (copies->nelts > 0
1235          && (svn_relpath_skip_ancestor(APR_ARRAY_IDX(copies,
1236                                                     copies->nelts - 1,
1237                                                      struct copy_info *)->path,
1238                                        repos_relpath) == NULL) )
1239     apr_array_pop(copies);
1240
1241   change = svn_hash_gets(changed_paths, repos_relpath);
1242   if (! change)
1243     {
1244       /* This can only happen if the path was removed from changed_paths
1245          by an earlier call to add_subdir, which means the path was already
1246          handled and we should simply ignore it. */
1247       return SVN_NO_ERROR;
1248     }
1249   switch (change->change_kind)
1250     {
1251     case svn_fs_path_change_add:
1252       do_add = TRUE;
1253       break;
1254
1255     case svn_fs_path_change_delete:
1256       do_delete = TRUE;
1257       break;
1258
1259     case svn_fs_path_change_replace:
1260       do_add = TRUE;
1261       do_delete = TRUE;
1262       break;
1263
1264     case svn_fs_path_change_modify:
1265     default:
1266       /* do nothing */
1267       break;
1268     }
1269
1270   /* Handle any deletions. */
1271   if (do_delete && ! do_add)
1272     {
1273       svn_boolean_t readable;
1274
1275       /* Issue #4121: delete under under a copy, of a path that was unreadable
1276          at its pre-copy location. */
1277       SVN_ERR(was_readable(&readable, root, repos_relpath, copies,
1278                             authz_read_func, authz_read_baton,
1279                             scratch_pool, scratch_pool));
1280       if (readable)
1281         SVN_ERR(svn_editor_delete(editor, repos_relpath, SVN_INVALID_REVNUM));
1282
1283       return SVN_NO_ERROR;
1284     }
1285
1286   /* Handle replacements. */
1287   if (do_delete && do_add)
1288     replaces_rev = svn_fs_revision_root_revision(root);
1289   else
1290     replaces_rev = SVN_INVALID_REVNUM;
1291
1292   /* Fetch the node kind if it makes sense to do so. */
1293   if (! do_delete || do_add)
1294     {
1295       if (change->node_kind == svn_node_unknown)
1296         SVN_ERR(svn_fs_check_path(&(change->node_kind), root, repos_relpath,
1297                                   scratch_pool));
1298       if ((change->node_kind != svn_node_dir) &&
1299           (change->node_kind != svn_node_file))
1300         return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1301                                  _("Filesystem path '%s' is neither a file "
1302                                    "nor a directory"), repos_relpath);
1303     }
1304
1305   /* Handle any adds/opens. */
1306   if (do_add)
1307     {
1308       svn_boolean_t src_readable;
1309       svn_fs_root_t *copyfrom_root;
1310
1311       /* Was this node copied? */
1312       SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
1313                             &src_readable, root, change,
1314                             authz_read_func, authz_read_baton,
1315                             repos_relpath, scratch_pool, scratch_pool));
1316
1317       /* If we have a copyfrom path, and we can't read it or we're just
1318          ignoring it, or the copyfrom rev is prior to the low water mark
1319          then we just null them out and do a raw add with no history at
1320          all. */
1321       if (copyfrom_path
1322           && ((! src_readable)
1323               || (svn_relpath_skip_ancestor(base_repos_relpath,
1324                                             copyfrom_path + 1) == NULL)
1325               || (low_water_mark > copyfrom_rev)))
1326         {
1327           copyfrom_path = NULL;
1328           copyfrom_rev = SVN_INVALID_REVNUM;
1329         }
1330
1331       /* Do the right thing based on the path KIND. */
1332       if (change->node_kind == svn_node_dir)
1333         {
1334           /* If this is a copy, but we can't represent it as such,
1335              then we just do a recursive add of the source path
1336              contents. */
1337           if (change->copyfrom_path && ! copyfrom_path)
1338             {
1339               SVN_ERR(add_subdir_ev2(copyfrom_root, root, editor,
1340                                      repos_relpath, change->copyfrom_path,
1341                                      authz_read_func, authz_read_baton,
1342                                      changed_paths, result_pool,
1343                                      scratch_pool));
1344             }
1345           else
1346             {
1347               if (copyfrom_path)
1348                 {
1349                   if (copyfrom_path[0] == '/')
1350                     ++copyfrom_path;
1351                   SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
1352                                           repos_relpath, replaces_rev));
1353                 }
1354               else
1355                 {
1356                   apr_array_header_t *children;
1357                   apr_hash_t *props;
1358                   apr_hash_t *dirents;
1359
1360                   SVN_ERR(svn_fs_dir_entries(&dirents, root, repos_relpath,
1361                                              scratch_pool));
1362                   SVN_ERR(svn_hash_keys(&children, dirents, scratch_pool));
1363
1364                   SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1365                                                scratch_pool));
1366
1367                   SVN_ERR(svn_editor_add_directory(editor, repos_relpath,
1368                                                    children, props,
1369                                                    replaces_rev));
1370                 }
1371             }
1372         }
1373       else
1374         {
1375           if (copyfrom_path)
1376             {
1377               if (copyfrom_path[0] == '/')
1378                 ++copyfrom_path;
1379               SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
1380                                       repos_relpath, replaces_rev));
1381             }
1382           else
1383             {
1384               apr_hash_t *props;
1385               svn_checksum_t *checksum;
1386               svn_stream_t *contents;
1387
1388               SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1389                                            scratch_pool));
1390
1391               SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
1392                                            scratch_pool));
1393
1394               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root,
1395                                            repos_relpath, TRUE, scratch_pool));
1396
1397               SVN_ERR(svn_editor_add_file(editor, repos_relpath, checksum,
1398                                           contents, props, replaces_rev));
1399             }
1400         }
1401
1402       /* If we represent this as a copy... */
1403       if (copyfrom_path)
1404         {
1405           /* If it is a directory, make sure descendants get the correct
1406              delta source by remembering that we are operating inside a
1407              (possibly nested) copy operation. */
1408           if (change->node_kind == svn_node_dir)
1409             {
1410               struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
1411
1412               info->path = apr_pstrdup(result_pool, repos_relpath);
1413               info->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
1414               info->copyfrom_rev = copyfrom_rev;
1415
1416               APR_ARRAY_PUSH(copies, struct copy_info *) = info;
1417             }
1418         }
1419       else
1420         /* Else, we are an add without history... */
1421         {
1422           /* If an ancestor is added with history, we need to forget about
1423              that here, go on with life and repeat all the mistakes of our
1424              past... */
1425           if (change->node_kind == svn_node_dir && copies->nelts > 0)
1426             {
1427               struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
1428
1429               info->path = apr_pstrdup(result_pool, repos_relpath);
1430               info->copyfrom_path = NULL;
1431               info->copyfrom_rev = SVN_INVALID_REVNUM;
1432
1433               APR_ARRAY_PUSH(copies, struct copy_info *) = info;
1434             }
1435         }
1436     }
1437   else if (! do_delete)
1438     {
1439       /* If we are inside an add with history, we need to adjust the
1440          delta source. */
1441       if (copies->nelts > 0)
1442         {
1443           struct copy_info *info = APR_ARRAY_IDX(copies,
1444                                                  copies->nelts - 1,
1445                                                  struct copy_info *);
1446           if (info->copyfrom_path)
1447             {
1448               const char *relpath = svn_relpath_skip_ancestor(info->path,
1449                                                               repos_relpath);
1450               SVN_ERR_ASSERT(relpath && *relpath);
1451               repos_relpath = svn_relpath_join(info->copyfrom_path,
1452                                                relpath, scratch_pool);
1453             }
1454         }
1455     }
1456
1457   if (! do_delete && !do_add)
1458     {
1459       apr_hash_t *props = NULL;
1460
1461       /* Is this a copy that was downgraded to a raw add?  (If so,
1462          we'll need to transmit properties and file contents and such
1463          for it regardless of what the CHANGE structure's text_mod
1464          and prop_mod flags say.)  */
1465       svn_boolean_t downgraded_copy = (change->copyfrom_known
1466                                        && change->copyfrom_path
1467                                        && (! copyfrom_path));
1468
1469       /* Handle property modifications. */
1470       if (change->prop_mod || downgraded_copy)
1471         {
1472           SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1473                                        scratch_pool));
1474         }
1475
1476       /* Handle textual modifications. */
1477       if (change->node_kind == svn_node_file
1478           && (change->text_mod || change->prop_mod || downgraded_copy))
1479         {
1480           svn_checksum_t *checksum = NULL;
1481           svn_stream_t *contents = NULL;
1482
1483           if (change->text_mod)
1484             {
1485               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1486                                            root, repos_relpath, TRUE,
1487                                            scratch_pool));
1488
1489               SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
1490                                            scratch_pool));
1491             }
1492
1493           SVN_ERR(svn_editor_alter_file(editor, repos_relpath,
1494                                         SVN_INVALID_REVNUM,
1495                                         checksum, contents, props));
1496         }
1497
1498       if (change->node_kind == svn_node_dir
1499           && (change->prop_mod || downgraded_copy))
1500         {
1501           apr_array_header_t *children = NULL;
1502
1503           SVN_ERR(svn_editor_alter_directory(editor, repos_relpath,
1504                                              SVN_INVALID_REVNUM, children,
1505                                              props));
1506         }
1507     }
1508
1509   return SVN_NO_ERROR;
1510 }
1511
1512 svn_error_t *
1513 svn_repos__replay_ev2(svn_fs_root_t *root,
1514                       const char *base_repos_relpath,
1515                       svn_revnum_t low_water_mark,
1516                       svn_editor_t *editor,
1517                       svn_repos_authz_func_t authz_read_func,
1518                       void *authz_read_baton,
1519                       apr_pool_t *scratch_pool)
1520 {
1521   apr_hash_t *changed_paths;
1522   apr_array_header_t *paths;
1523   apr_array_header_t *copies;
1524   apr_pool_t *iterpool;
1525   svn_error_t *err = SVN_NO_ERROR;
1526   int i;
1527
1528   SVN_ERR_ASSERT(svn_relpath_is_canonical(base_repos_relpath));
1529
1530   /* Special-case r0, which we know is an empty revision; if we don't
1531      special-case it we might end up trying to compare it to "r-1". */
1532   if (svn_fs_is_revision_root(root)
1533         && svn_fs_revision_root_revision(root) == 0)
1534     {
1535       return SVN_NO_ERROR;
1536     }
1537
1538   /* Fetch the paths changed under ROOT. */
1539   SVN_ERR(get_relevant_changes(&changed_paths, &paths, root,
1540                                base_repos_relpath,
1541                                authz_read_func, authz_read_baton,
1542                                scratch_pool, scratch_pool));
1543
1544   /* If we were not given a low water mark, assume that everything is there,
1545      all the way back to revision 0. */
1546   if (! SVN_IS_VALID_REVNUM(low_water_mark))
1547     low_water_mark = 0;
1548
1549   copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *));
1550
1551   /* Sort the paths.  Although not strictly required by the API, this has
1552      the pleasant side effect of maintaining a consistent ordering of
1553      dumpfile contents. */
1554   svn_sort__array(paths, svn_sort_compare_paths);
1555
1556   /* Now actually handle the various paths. */
1557   iterpool = svn_pool_create(scratch_pool);
1558   for (i = 0; i < paths->nelts; i++)
1559     {
1560       const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *);
1561
1562       svn_pool_clear(iterpool);
1563       err = replay_node(root, repos_relpath, editor, low_water_mark,
1564                         base_repos_relpath, copies, changed_paths,
1565                         authz_read_func, authz_read_baton,
1566                         scratch_pool, iterpool);
1567       if (err)
1568         break;
1569     }
1570
1571   if (err)
1572     return svn_error_compose_create(err, svn_editor_abort(editor));
1573   else
1574     SVN_ERR(svn_editor_complete(editor));
1575
1576   svn_pool_destroy(iterpool);
1577   return SVN_NO_ERROR;
1578 }