replay.c revision 299742
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
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
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
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. */
115struct 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
127struct 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. */
161static svn_error_t *
162add_subdir(svn_fs_root_t *source_root,
163           svn_fs_root_t *target_root,
164           const svn_delta_editor_t *editor,
165           void *edit_baton,
166           const char *edit_path,
167           void *parent_baton,
168           const char *source_fspath,
169           svn_repos_authz_func_t authz_read_func,
170           void *authz_read_baton,
171           apr_hash_t *changed_paths,
172           apr_pool_t *pool,
173           void **dir_baton)
174{
175  apr_pool_t *subpool = svn_pool_create(pool);
176  apr_hash_index_t *hi, *phi;
177  apr_hash_t *dirents;
178  apr_hash_t *props;
179
180  SVN_ERR(editor->add_directory(edit_path, parent_baton, NULL,
181                                SVN_INVALID_REVNUM, pool, dir_baton));
182
183  SVN_ERR(svn_fs_node_proplist(&props, target_root, edit_path, pool));
184
185  for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
186    {
187      const char *key = apr_hash_this_key(phi);
188      svn_string_t *val = apr_hash_this_val(phi);
189
190      svn_pool_clear(subpool);
191      SVN_ERR(editor->change_dir_prop(*dir_baton, key, val, subpool));
192    }
193
194  /* We have to get the dirents from the source path, not the target,
195     because we want nested copies from *readable* paths to be handled by
196     path_driver_cb_func, not add_subdir (in order to preserve history). */
197  SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath, pool));
198
199  for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
200    {
201      svn_fs_path_change2_t *change;
202      svn_boolean_t readable = TRUE;
203      svn_fs_dirent_t *dent = apr_hash_this_val(hi);
204      const char *copyfrom_path = NULL;
205      svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
206      const char *new_edit_path;
207
208      svn_pool_clear(subpool);
209
210      new_edit_path = svn_relpath_join(edit_path, dent->name, subpool);
211
212      /* If a file or subdirectory of the copied directory is listed as a
213         changed path (because it was modified after the copy but before the
214         commit), we remove it from the changed_paths hash so that future
215         calls to path_driver_cb_func will ignore it. */
216      change = svn_hash_gets(changed_paths, new_edit_path);
217      if (change)
218        {
219          svn_hash_sets(changed_paths, new_edit_path, NULL);
220
221          /* If it's a delete, skip this entry. */
222          if (change->change_kind == svn_fs_path_change_delete)
223            continue;
224
225          /* If it's a replacement, check for copyfrom info (if we
226             don't have it already. */
227          if (change->change_kind == svn_fs_path_change_replace)
228            {
229              if (! change->copyfrom_known)
230                {
231                  SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
232                                             &change->copyfrom_path,
233                                             target_root, new_edit_path, pool));
234                  change->copyfrom_known = TRUE;
235                }
236              copyfrom_path = change->copyfrom_path;
237              copyfrom_rev = change->copyfrom_rev;
238            }
239        }
240
241      if (authz_read_func)
242        SVN_ERR(authz_read_func(&readable, target_root, new_edit_path,
243                                authz_read_baton, pool));
244
245      if (! readable)
246        continue;
247
248      if (dent->kind == svn_node_dir)
249        {
250          svn_fs_root_t *new_source_root;
251          const char *new_source_fspath;
252          void *new_dir_baton;
253
254          if (copyfrom_path)
255            {
256              svn_fs_t *fs = svn_fs_root_fs(source_root);
257              SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
258                                           copyfrom_rev, pool));
259              new_source_fspath = copyfrom_path;
260            }
261          else
262            {
263              new_source_root = source_root;
264              new_source_fspath = svn_fspath__join(source_fspath, dent->name,
265                                                   subpool);
266            }
267
268          /* ### authz considerations?
269           *
270           * I think not; when path_driver_cb_func() calls add_subdir(), it
271           * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
272           */
273          if (change && change->change_kind == svn_fs_path_change_replace
274              && copyfrom_path == NULL)
275            {
276              SVN_ERR(editor->add_directory(new_edit_path, *dir_baton,
277                                            NULL, SVN_INVALID_REVNUM,
278                                            subpool, &new_dir_baton));
279            }
280          else
281            {
282              SVN_ERR(add_subdir(new_source_root, target_root,
283                                 editor, edit_baton, new_edit_path,
284                                 *dir_baton, new_source_fspath,
285                                 authz_read_func, authz_read_baton,
286                                 changed_paths, subpool, &new_dir_baton));
287            }
288
289          SVN_ERR(editor->close_directory(new_dir_baton, subpool));
290        }
291      else if (dent->kind == svn_node_file)
292        {
293          svn_txdelta_window_handler_t delta_handler;
294          void *delta_handler_baton, *file_baton;
295          svn_txdelta_stream_t *delta_stream;
296          svn_checksum_t *checksum;
297
298          SVN_ERR(editor->add_file(new_edit_path, *dir_baton, NULL,
299                                   SVN_INVALID_REVNUM, pool, &file_baton));
300
301          SVN_ERR(svn_fs_node_proplist(&props, target_root,
302                                       new_edit_path, subpool));
303
304          for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
305            {
306              const char *key = apr_hash_this_key(phi);
307              svn_string_t *val = apr_hash_this_val(phi);
308
309              SVN_ERR(editor->change_file_prop(file_baton, key, val, subpool));
310            }
311
312          SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool,
313                                          &delta_handler,
314                                          &delta_handler_baton));
315
316          SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, NULL, NULL,
317                                               target_root, new_edit_path,
318                                               pool));
319
320          SVN_ERR(svn_txdelta_send_txstream(delta_stream,
321                                            delta_handler,
322                                            delta_handler_baton,
323                                            pool));
324
325          SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, target_root,
326                                       new_edit_path, TRUE, pool));
327          SVN_ERR(editor->close_file(file_baton,
328                                     svn_checksum_to_cstring(checksum, pool),
329                                     pool));
330        }
331      else
332        SVN_ERR_MALFUNCTION();
333    }
334
335  svn_pool_destroy(subpool);
336
337  return SVN_NO_ERROR;
338}
339
340/* Given PATH deleted under ROOT, return in READABLE whether the path was
341   readable prior to the deletion.  Consult COPIES (a stack of 'struct
342   copy_info') and AUTHZ_READ_FUNC. */
343static svn_error_t *
344was_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.  */
409static svn_error_t *
410fill_copyfrom(svn_fs_root_t **copyfrom_root,
411              const char **copyfrom_path,
412              svn_revnum_t *copyfrom_rev,
413              svn_boolean_t *src_readable,
414              svn_fs_root_t *root,
415              svn_fs_path_change2_t *change,
416              svn_repos_authz_func_t authz_read_func,
417              void *authz_read_baton,
418              const char *path,
419              apr_pool_t *result_pool,
420              apr_pool_t *scratch_pool)
421{
422  if (! change->copyfrom_known)
423    {
424      SVN_ERR(svn_fs_copied_from(&(change->copyfrom_rev),
425                                 &(change->copyfrom_path),
426                                 root, path, result_pool));
427      change->copyfrom_known = TRUE;
428    }
429  *copyfrom_rev = change->copyfrom_rev;
430  *copyfrom_path = change->copyfrom_path;
431
432  if (*copyfrom_path && SVN_IS_VALID_REVNUM(*copyfrom_rev))
433    {
434      SVN_ERR(svn_fs_revision_root(copyfrom_root,
435                                   svn_fs_root_fs(root),
436                                   *copyfrom_rev, result_pool));
437
438      if (authz_read_func)
439        {
440          SVN_ERR(authz_read_func(src_readable, *copyfrom_root,
441                                  *copyfrom_path,
442                                  authz_read_baton, result_pool));
443        }
444      else
445        *src_readable = TRUE;
446    }
447  else
448    {
449      *copyfrom_root = NULL;
450      /* SRC_READABLE left uninitialized */
451    }
452  return SVN_NO_ERROR;
453}
454
455static svn_error_t *
456path_driver_cb_func(void **dir_baton,
457                    void *parent_baton,
458                    void *callback_baton,
459                    const char *edit_path,
460                    apr_pool_t *pool)
461{
462  struct path_driver_cb_baton *cb = callback_baton;
463  const svn_delta_editor_t *editor = cb->editor;
464  void *edit_baton = cb->edit_baton;
465  svn_fs_root_t *root = cb->root;
466  svn_fs_path_change2_t *change;
467  svn_boolean_t do_add = FALSE, do_delete = FALSE;
468  void *file_baton = NULL;
469  svn_revnum_t copyfrom_rev;
470  const char *copyfrom_path;
471  svn_fs_root_t *source_root = cb->compare_root;
472  const char *source_fspath = NULL;
473  const char *base_path = cb->base_path;
474
475  *dir_baton = NULL;
476
477  /* Initialize SOURCE_FSPATH. */
478  if (source_root)
479    source_fspath = svn_fspath__canonicalize(edit_path, pool);
480
481  /* First, flush the copies stack so it only contains ancestors of path. */
482  while (cb->copies->nelts > 0
483         && ! svn_dirent_is_ancestor(APR_ARRAY_IDX(cb->copies,
484                                                   cb->copies->nelts - 1,
485                                                   struct copy_info *)->path,
486                                     edit_path))
487    apr_array_pop(cb->copies);
488
489  change = svn_hash_gets(cb->changed_paths, edit_path);
490  if (! change)
491    {
492      /* This can only happen if the path was removed from cb->changed_paths
493         by an earlier call to add_subdir, which means the path was already
494         handled and we should simply ignore it. */
495      return SVN_NO_ERROR;
496    }
497  switch (change->change_kind)
498    {
499    case svn_fs_path_change_add:
500      do_add = TRUE;
501      break;
502
503    case svn_fs_path_change_delete:
504      do_delete = TRUE;
505      break;
506
507    case svn_fs_path_change_replace:
508      do_add = TRUE;
509      do_delete = TRUE;
510      break;
511
512    case svn_fs_path_change_modify:
513    default:
514      /* do nothing */
515      break;
516    }
517
518  /* Handle any deletions. */
519  if (do_delete)
520    {
521      svn_boolean_t readable;
522
523      /* Issue #4121: delete under under a copy, of a path that was unreadable
524         at its pre-copy location. */
525      SVN_ERR(was_readable(&readable, root, edit_path, cb->copies,
526                            cb->authz_read_func, cb->authz_read_baton,
527                            pool, pool));
528      if (readable)
529        SVN_ERR(editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
530                                     parent_baton, pool));
531    }
532
533  /* Fetch the node kind if it makes sense to do so. */
534  if (! do_delete || do_add)
535    {
536      if (change->node_kind == svn_node_unknown)
537        SVN_ERR(svn_fs_check_path(&(change->node_kind), root, edit_path, pool));
538      if ((change->node_kind != svn_node_dir) &&
539          (change->node_kind != svn_node_file))
540        return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
541                                 _("Filesystem path '%s' is neither a file "
542                                   "nor a directory"), edit_path);
543    }
544
545  /* Handle any adds/opens. */
546  if (do_add)
547    {
548      svn_boolean_t src_readable;
549      svn_fs_root_t *copyfrom_root;
550
551      /* E.g. when verifying corrupted repositories, their changed path
552         lists may contain an ADD for "/".  The delta path driver will
553         call us with a NULL parent in that case. */
554      if (*edit_path == 0)
555        return svn_error_create(SVN_ERR_FS_ALREADY_EXISTS, NULL,
556                                _("Root directory already exists."));
557
558      /* A NULL parent_baton will cause a segfault.  It should never be
559          NULL for non-root paths. */
560      SVN_ERR_ASSERT(parent_baton);
561
562      /* Was this node copied? */
563      SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
564                            &src_readable, root, change,
565                            cb->authz_read_func, cb->authz_read_baton,
566                            edit_path, pool, pool));
567
568      /* If we have a copyfrom path, and we can't read it or we're just
569         ignoring it, or the copyfrom rev is prior to the low water mark
570         then we just null them out and do a raw add with no history at
571         all. */
572      if (copyfrom_path
573          && ((! src_readable)
574              || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL)
575              || (cb->low_water_mark > copyfrom_rev)))
576        {
577          copyfrom_path = NULL;
578          copyfrom_rev = SVN_INVALID_REVNUM;
579        }
580
581      /* Do the right thing based on the path KIND. */
582      if (change->node_kind == svn_node_dir)
583        {
584          /* If this is a copy, but we can't represent it as such,
585             then we just do a recursive add of the source path
586             contents. */
587          if (change->copyfrom_path && ! copyfrom_path)
588            {
589              SVN_ERR(add_subdir(copyfrom_root, root, editor, edit_baton,
590                                 edit_path, parent_baton, change->copyfrom_path,
591                                 cb->authz_read_func, cb->authz_read_baton,
592                                 cb->changed_paths, pool, dir_baton));
593            }
594          else
595            {
596              SVN_ERR(editor->add_directory(edit_path, parent_baton,
597                                            copyfrom_path, copyfrom_rev,
598                                            pool, dir_baton));
599            }
600        }
601      else
602        {
603          SVN_ERR(editor->add_file(edit_path, parent_baton, copyfrom_path,
604                                   copyfrom_rev, pool, &file_baton));
605        }
606
607      /* If we represent this as a copy... */
608      if (copyfrom_path)
609        {
610          /* If it is a directory, make sure descendants get the correct
611             delta source by remembering that we are operating inside a
612             (possibly nested) copy operation. */
613          if (change->node_kind == svn_node_dir)
614            {
615              struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
616
617              info->path = apr_pstrdup(cb->pool, edit_path);
618              info->copyfrom_path = apr_pstrdup(cb->pool, copyfrom_path);
619              info->copyfrom_rev = copyfrom_rev;
620
621              APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
622            }
623
624          /* Save the source so that we can use it later, when we
625             need to generate text and prop deltas. */
626          source_root = copyfrom_root;
627          source_fspath = copyfrom_path;
628        }
629      else
630        /* Else, we are an add without history... */
631        {
632          /* If an ancestor is added with history, we need to forget about
633             that here, go on with life and repeat all the mistakes of our
634             past... */
635          if (change->node_kind == svn_node_dir && cb->copies->nelts > 0)
636            {
637              struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
638
639              info->path = apr_pstrdup(cb->pool, edit_path);
640              info->copyfrom_path = NULL;
641              info->copyfrom_rev = SVN_INVALID_REVNUM;
642
643              APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
644            }
645          source_root = NULL;
646          source_fspath = NULL;
647        }
648    }
649  else if (! do_delete)
650    {
651      /* Do the right thing based on the path KIND (and the presence
652         of a PARENT_BATON). */
653      if (change->node_kind == svn_node_dir)
654        {
655          if (parent_baton)
656            {
657              SVN_ERR(editor->open_directory(edit_path, parent_baton,
658                                             SVN_INVALID_REVNUM,
659                                             pool, dir_baton));
660            }
661          else
662            {
663              SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
664                                        pool, dir_baton));
665            }
666        }
667      else
668        {
669          SVN_ERR(editor->open_file(edit_path, parent_baton, SVN_INVALID_REVNUM,
670                                    pool, &file_baton));
671        }
672      /* If we are inside an add with history, we need to adjust the
673         delta source. */
674      if (cb->copies->nelts > 0)
675        {
676          struct copy_info *info = APR_ARRAY_IDX(cb->copies,
677                                                 cb->copies->nelts - 1,
678                                                 struct copy_info *);
679          if (info->copyfrom_path)
680            {
681              const char *relpath = svn_relpath_skip_ancestor(info->path,
682                                                              edit_path);
683              SVN_ERR_ASSERT(relpath && *relpath);
684              SVN_ERR(svn_fs_revision_root(&source_root,
685                                           svn_fs_root_fs(root),
686                                           info->copyfrom_rev, pool));
687              source_fspath = svn_fspath__join(info->copyfrom_path,
688                                               relpath, pool);
689            }
690          else
691            {
692              /* This is an add without history, nested inside an
693                 add with history.  We have no delta source in this case. */
694              source_root = NULL;
695              source_fspath = NULL;
696            }
697        }
698    }
699
700  if (! do_delete || do_add)
701    {
702      /* Is this a copy that was downgraded to a raw add?  (If so,
703         we'll need to transmit properties and file contents and such
704         for it regardless of what the CHANGE structure's text_mod
705         and prop_mod flags say.)  */
706      svn_boolean_t downgraded_copy = (change->copyfrom_known
707                                       && change->copyfrom_path
708                                       && (! copyfrom_path));
709
710      /* Handle property modifications. */
711      if (change->prop_mod || downgraded_copy)
712        {
713          if (cb->compare_root)
714            {
715              apr_array_header_t *prop_diffs;
716              apr_hash_t *old_props;
717              apr_hash_t *new_props;
718              int i;
719
720              if (source_root)
721                SVN_ERR(svn_fs_node_proplist(&old_props, source_root,
722                                             source_fspath, pool));
723              else
724                old_props = apr_hash_make(pool);
725
726              SVN_ERR(svn_fs_node_proplist(&new_props, root, edit_path, pool));
727
728              SVN_ERR(svn_prop_diffs(&prop_diffs, new_props, old_props,
729                                     pool));
730
731              for (i = 0; i < prop_diffs->nelts; ++i)
732                {
733                  svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
734                   if (change->node_kind == svn_node_dir)
735                     SVN_ERR(editor->change_dir_prop(*dir_baton, pc->name,
736                                                     pc->value, pool));
737                   else if (change->node_kind == svn_node_file)
738                     SVN_ERR(editor->change_file_prop(file_baton, pc->name,
739                                                      pc->value, pool));
740                }
741            }
742          else
743            {
744              /* Just do a dummy prop change to signal that there are *any*
745                 propmods. */
746              if (change->node_kind == svn_node_dir)
747                SVN_ERR(editor->change_dir_prop(*dir_baton, "", NULL,
748                                                pool));
749              else if (change->node_kind == svn_node_file)
750                SVN_ERR(editor->change_file_prop(file_baton, "", NULL,
751                                                 pool));
752            }
753        }
754
755      /* Handle textual modifications. */
756      if (change->node_kind == svn_node_file
757          && (change->text_mod || downgraded_copy))
758        {
759          svn_txdelta_window_handler_t delta_handler;
760          void *delta_handler_baton;
761          const char *hex_digest = NULL;
762
763          if (cb->compare_root && source_root && source_fspath)
764            {
765              svn_checksum_t *checksum;
766              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
767                                           source_root, source_fspath, TRUE,
768                                           pool));
769              hex_digest = svn_checksum_to_cstring(checksum, pool);
770            }
771
772          SVN_ERR(editor->apply_textdelta(file_baton, hex_digest, pool,
773                                          &delta_handler,
774                                          &delta_handler_baton));
775          if (cb->compare_root)
776            {
777              svn_txdelta_stream_t *delta_stream;
778
779              SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, source_root,
780                                                   source_fspath, root,
781                                                   edit_path, pool));
782              SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
783                                                delta_handler_baton, pool));
784            }
785          else
786            SVN_ERR(delta_handler(NULL, delta_handler_baton));
787        }
788    }
789
790  /* Close the file baton if we opened it. */
791  if (file_baton)
792    {
793      svn_checksum_t *checksum;
794      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, edit_path,
795                                   TRUE, pool));
796      SVN_ERR(editor->close_file(file_baton,
797                                 svn_checksum_to_cstring(checksum, pool),
798                                 pool));
799    }
800
801  return SVN_NO_ERROR;
802}
803
804#ifdef USE_EV2_IMPL
805static svn_error_t *
806fetch_kind_func(svn_node_kind_t *kind,
807                void *baton,
808                const char *path,
809                svn_revnum_t base_revision,
810                apr_pool_t *scratch_pool)
811{
812  svn_fs_root_t *root = baton;
813  svn_fs_root_t *prev_root;
814  svn_fs_t *fs = svn_fs_root_fs(root);
815
816  if (!SVN_IS_VALID_REVNUM(base_revision))
817    base_revision = svn_fs_revision_root_revision(root) - 1;
818
819  SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
820  SVN_ERR(svn_fs_check_path(kind, prev_root, path, scratch_pool));
821
822  return SVN_NO_ERROR;
823}
824
825static svn_error_t *
826fetch_props_func(apr_hash_t **props,
827                 void *baton,
828                 const char *path,
829                 svn_revnum_t base_revision,
830                 apr_pool_t *result_pool,
831                 apr_pool_t *scratch_pool)
832{
833  svn_fs_root_t *root = baton;
834  svn_fs_root_t *prev_root;
835  svn_fs_t *fs = svn_fs_root_fs(root);
836
837  if (!SVN_IS_VALID_REVNUM(base_revision))
838    base_revision = svn_fs_revision_root_revision(root) - 1;
839
840  SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
841  SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool));
842
843  return SVN_NO_ERROR;
844}
845#endif
846
847
848
849
850svn_error_t *
851svn_repos_replay2(svn_fs_root_t *root,
852                  const char *base_path,
853                  svn_revnum_t low_water_mark,
854                  svn_boolean_t send_deltas,
855                  const svn_delta_editor_t *editor,
856                  void *edit_baton,
857                  svn_repos_authz_func_t authz_read_func,
858                  void *authz_read_baton,
859                  apr_pool_t *pool)
860{
861#ifndef USE_EV2_IMPL
862  apr_hash_t *fs_changes;
863  apr_hash_t *changed_paths;
864  apr_hash_index_t *hi;
865  apr_array_header_t *paths;
866  struct path_driver_cb_baton cb_baton;
867
868  /* Special-case r0, which we know is an empty revision; if we don't
869     special-case it we might end up trying to compare it to "r-1". */
870  if (svn_fs_is_revision_root(root) && svn_fs_revision_root_revision(root) == 0)
871    {
872      SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
873      return SVN_NO_ERROR;
874    }
875
876  /* Fetch the paths changed under ROOT. */
877  SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, pool));
878
879  if (! base_path)
880    base_path = "";
881  else if (base_path[0] == '/')
882    ++base_path;
883
884  /* Make an array from the keys of our CHANGED_PATHS hash, and copy
885     the values into a new hash whose keys have no leading slashes. */
886  paths = apr_array_make(pool, apr_hash_count(fs_changes),
887                         sizeof(const char *));
888  changed_paths = apr_hash_make(pool);
889  for (hi = apr_hash_first(pool, fs_changes); hi; hi = apr_hash_next(hi))
890    {
891      const char *path = apr_hash_this_key(hi);
892      apr_ssize_t keylen = apr_hash_this_key_len(hi);
893      svn_fs_path_change2_t *change = apr_hash_this_val(hi);
894      svn_boolean_t allowed = TRUE;
895
896      if (authz_read_func)
897        SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
898                                pool));
899
900      if (allowed)
901        {
902          if (path[0] == '/')
903            {
904              path++;
905              keylen--;
906            }
907
908          /* If the base_path doesn't match the top directory of this path
909             we don't want anything to do with it... */
910          if (svn_relpath_skip_ancestor(base_path, path) != NULL)
911            {
912              APR_ARRAY_PUSH(paths, const char *) = path;
913              apr_hash_set(changed_paths, path, keylen, change);
914            }
915          /* ...unless this was a change to one of the parent directories of
916             base_path. */
917          else if (svn_relpath_skip_ancestor(path, base_path) != NULL)
918            {
919              APR_ARRAY_PUSH(paths, const char *) = path;
920              apr_hash_set(changed_paths, path, keylen, change);
921            }
922        }
923    }
924
925  /* If we were not given a low water mark, assume that everything is there,
926     all the way back to revision 0. */
927  if (! SVN_IS_VALID_REVNUM(low_water_mark))
928    low_water_mark = 0;
929
930  /* Initialize our callback baton. */
931  cb_baton.editor = editor;
932  cb_baton.edit_baton = edit_baton;
933  cb_baton.root = root;
934  cb_baton.changed_paths = changed_paths;
935  cb_baton.authz_read_func = authz_read_func;
936  cb_baton.authz_read_baton = authz_read_baton;
937  cb_baton.base_path = base_path;
938  cb_baton.low_water_mark = low_water_mark;
939  cb_baton.compare_root = NULL;
940
941  if (send_deltas)
942    {
943      SVN_ERR(svn_fs_revision_root(&cb_baton.compare_root,
944                                   svn_fs_root_fs(root),
945                                   svn_fs_is_revision_root(root)
946                                     ? svn_fs_revision_root_revision(root) - 1
947                                     : svn_fs_txn_root_base_revision(root),
948                                   pool));
949    }
950
951  cb_baton.copies = apr_array_make(pool, 4, sizeof(struct copy_info *));
952  cb_baton.pool = pool;
953
954  /* Determine the revision to use throughout the edit, and call
955     EDITOR's set_target_revision() function.  */
956  if (svn_fs_is_revision_root(root))
957    {
958      svn_revnum_t revision = svn_fs_revision_root_revision(root);
959      SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
960    }
961
962  /* Call the path-based editor driver. */
963  return svn_delta_path_driver2(editor, edit_baton,
964                                paths, TRUE,
965                                path_driver_cb_func, &cb_baton, pool);
966#else
967  svn_editor_t *editorv2;
968  struct svn_delta__extra_baton *exb;
969  svn_delta__unlock_func_t unlock_func;
970  svn_boolean_t send_abs_paths;
971  const char *repos_root = "";
972  void *unlock_baton;
973
974  /* Special-case r0, which we know is an empty revision; if we don't
975     special-case it we might end up trying to compare it to "r-1". */
976  if (svn_fs_is_revision_root(root)
977        && svn_fs_revision_root_revision(root) == 0)
978    {
979      SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
980      return SVN_NO_ERROR;
981    }
982
983  /* Determine the revision to use throughout the edit, and call
984     EDITOR's set_target_revision() function.  */
985  if (svn_fs_is_revision_root(root))
986    {
987      svn_revnum_t revision = svn_fs_revision_root_revision(root);
988      SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
989    }
990
991  if (! base_path)
992    base_path = "";
993  else if (base_path[0] == '/')
994    ++base_path;
995
996  /* Use the shim to convert our editor to an Ev2 editor, and pass it down
997     the stack. */
998  SVN_ERR(svn_delta__editor_from_delta(&editorv2, &exb,
999                                       &unlock_func, &unlock_baton,
1000                                       editor, edit_baton,
1001                                       &send_abs_paths,
1002                                       repos_root, "",
1003                                       NULL, NULL,
1004                                       fetch_kind_func, root,
1005                                       fetch_props_func, root,
1006                                       pool, pool));
1007
1008  /* Tell the shim that we're starting the process. */
1009  SVN_ERR(exb->start_edit(exb->baton, svn_fs_revision_root_revision(root)));
1010
1011  /* ### We're ignoring SEND_DELTAS here. */
1012  SVN_ERR(svn_repos__replay_ev2(root, base_path, low_water_mark,
1013                                editorv2, authz_read_func, authz_read_baton,
1014                                pool));
1015
1016  return SVN_NO_ERROR;
1017#endif
1018}
1019
1020
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. */
1032static svn_error_t *
1033add_subdir_ev2(svn_fs_root_t *source_root,
1034               svn_fs_root_t *target_root,
1035               svn_editor_t *editor,
1036               const char *repos_relpath,
1037               const char *source_fspath,
1038               svn_repos_authz_func_t authz_read_func,
1039               void *authz_read_baton,
1040               apr_hash_t *changed_paths,
1041               apr_pool_t *result_pool,
1042               apr_pool_t *scratch_pool)
1043{
1044  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1045  apr_hash_index_t *hi;
1046  apr_hash_t *dirents;
1047  apr_hash_t *props = NULL;
1048  apr_array_header_t *children = NULL;
1049
1050  SVN_ERR(svn_fs_node_proplist(&props, target_root, repos_relpath,
1051                               scratch_pool));
1052
1053  SVN_ERR(svn_editor_add_directory(editor, repos_relpath, children,
1054                                   props, SVN_INVALID_REVNUM));
1055
1056  /* We have to get the dirents from the source path, not the target,
1057     because we want nested copies from *readable* paths to be handled by
1058     path_driver_cb_func, not add_subdir (in order to preserve history). */
1059  SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath,
1060                             scratch_pool));
1061
1062  for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
1063    {
1064      svn_fs_path_change2_t *change;
1065      svn_boolean_t readable = TRUE;
1066      svn_fs_dirent_t *dent = apr_hash_this_val(hi);
1067      const char *copyfrom_path = NULL;
1068      svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
1069      const char *child_relpath;
1070
1071      svn_pool_clear(iterpool);
1072
1073      child_relpath = svn_relpath_join(repos_relpath, dent->name, iterpool);
1074
1075      /* If a file or subdirectory of the copied directory is listed as a
1076         changed path (because it was modified after the copy but before the
1077         commit), we remove it from the changed_paths hash so that future
1078         calls to path_driver_cb_func will ignore it. */
1079      change = svn_hash_gets(changed_paths, child_relpath);
1080      if (change)
1081        {
1082          svn_hash_sets(changed_paths, child_relpath, NULL);
1083
1084          /* If it's a delete, skip this entry. */
1085          if (change->change_kind == svn_fs_path_change_delete)
1086            continue;
1087
1088          /* If it's a replacement, check for copyfrom info (if we
1089             don't have it already. */
1090          if (change->change_kind == svn_fs_path_change_replace)
1091            {
1092              if (! change->copyfrom_known)
1093                {
1094                  SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
1095                                             &change->copyfrom_path,
1096                                             target_root, child_relpath,
1097                                             result_pool));
1098                  change->copyfrom_known = TRUE;
1099                }
1100              copyfrom_path = change->copyfrom_path;
1101              copyfrom_rev = change->copyfrom_rev;
1102            }
1103        }
1104
1105      if (authz_read_func)
1106        SVN_ERR(authz_read_func(&readable, target_root, child_relpath,
1107                                authz_read_baton, iterpool));
1108
1109      if (! readable)
1110        continue;
1111
1112      if (dent->kind == svn_node_dir)
1113        {
1114          svn_fs_root_t *new_source_root;
1115          const char *new_source_fspath;
1116
1117          if (copyfrom_path)
1118            {
1119              svn_fs_t *fs = svn_fs_root_fs(source_root);
1120              SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
1121                                           copyfrom_rev, result_pool));
1122              new_source_fspath = copyfrom_path;
1123            }
1124          else
1125            {
1126              new_source_root = source_root;
1127              new_source_fspath = svn_fspath__join(source_fspath, dent->name,
1128                                                   iterpool);
1129            }
1130
1131          /* ### authz considerations?
1132           *
1133           * I think not; when path_driver_cb_func() calls add_subdir(), it
1134           * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
1135           */
1136          if (change && change->change_kind == svn_fs_path_change_replace
1137              && copyfrom_path == NULL)
1138            {
1139              SVN_ERR(svn_editor_add_directory(editor, child_relpath,
1140                                               children, props,
1141                                               SVN_INVALID_REVNUM));
1142            }
1143          else
1144            {
1145              SVN_ERR(add_subdir_ev2(new_source_root, target_root,
1146                                     editor, child_relpath,
1147                                     new_source_fspath,
1148                                     authz_read_func, authz_read_baton,
1149                                     changed_paths, result_pool, iterpool));
1150            }
1151        }
1152      else if (dent->kind == svn_node_file)
1153        {
1154          svn_checksum_t *checksum;
1155          svn_stream_t *contents;
1156
1157          SVN_ERR(svn_fs_node_proplist(&props, target_root,
1158                                       child_relpath, iterpool));
1159
1160          SVN_ERR(svn_fs_file_contents(&contents, target_root,
1161                                       child_relpath, iterpool));
1162
1163          SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1164                                       target_root,
1165                                       child_relpath, TRUE, iterpool));
1166
1167          SVN_ERR(svn_editor_add_file(editor, child_relpath, checksum,
1168                                      contents, props, SVN_INVALID_REVNUM));
1169        }
1170      else
1171        SVN_ERR_MALFUNCTION();
1172    }
1173
1174  svn_pool_destroy(iterpool);
1175
1176  return SVN_NO_ERROR;
1177}
1178
1179static svn_error_t *
1180replay_node(svn_fs_root_t *root,
1181            const char *repos_relpath,
1182            svn_editor_t *editor,
1183            svn_revnum_t low_water_mark,
1184            const char *base_repos_relpath,
1185            apr_array_header_t *copies,
1186            apr_hash_t *changed_paths,
1187            svn_repos_authz_func_t authz_read_func,
1188            void *authz_read_baton,
1189            apr_pool_t *result_pool,
1190            apr_pool_t *scratch_pool)
1191{
1192  svn_fs_path_change2_t *change;
1193  svn_boolean_t do_add = FALSE;
1194  svn_boolean_t do_delete = FALSE;
1195  svn_revnum_t copyfrom_rev;
1196  const char *copyfrom_path;
1197  svn_revnum_t replaces_rev;
1198
1199  /* First, flush the copies stack so it only contains ancestors of path. */
1200  while (copies->nelts > 0
1201         && (svn_relpath_skip_ancestor(APR_ARRAY_IDX(copies,
1202                                                    copies->nelts - 1,
1203                                                     struct copy_info *)->path,
1204                                       repos_relpath) == NULL) )
1205    apr_array_pop(copies);
1206
1207  change = svn_hash_gets(changed_paths, repos_relpath);
1208  if (! change)
1209    {
1210      /* This can only happen if the path was removed from changed_paths
1211         by an earlier call to add_subdir, which means the path was already
1212         handled and we should simply ignore it. */
1213      return SVN_NO_ERROR;
1214    }
1215  switch (change->change_kind)
1216    {
1217    case svn_fs_path_change_add:
1218      do_add = TRUE;
1219      break;
1220
1221    case svn_fs_path_change_delete:
1222      do_delete = TRUE;
1223      break;
1224
1225    case svn_fs_path_change_replace:
1226      do_add = TRUE;
1227      do_delete = TRUE;
1228      break;
1229
1230    case svn_fs_path_change_modify:
1231    default:
1232      /* do nothing */
1233      break;
1234    }
1235
1236  /* Handle any deletions. */
1237  if (do_delete && ! do_add)
1238    {
1239      svn_boolean_t readable;
1240
1241      /* Issue #4121: delete under under a copy, of a path that was unreadable
1242         at its pre-copy location. */
1243      SVN_ERR(was_readable(&readable, root, repos_relpath, copies,
1244                            authz_read_func, authz_read_baton,
1245                            scratch_pool, scratch_pool));
1246      if (readable)
1247        SVN_ERR(svn_editor_delete(editor, repos_relpath, SVN_INVALID_REVNUM));
1248
1249      return SVN_NO_ERROR;
1250    }
1251
1252  /* Handle replacements. */
1253  if (do_delete && do_add)
1254    replaces_rev = svn_fs_revision_root_revision(root);
1255  else
1256    replaces_rev = SVN_INVALID_REVNUM;
1257
1258  /* Fetch the node kind if it makes sense to do so. */
1259  if (! do_delete || do_add)
1260    {
1261      if (change->node_kind == svn_node_unknown)
1262        SVN_ERR(svn_fs_check_path(&(change->node_kind), root, repos_relpath,
1263                                  scratch_pool));
1264      if ((change->node_kind != svn_node_dir) &&
1265          (change->node_kind != svn_node_file))
1266        return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1267                                 _("Filesystem path '%s' is neither a file "
1268                                   "nor a directory"), repos_relpath);
1269    }
1270
1271  /* Handle any adds/opens. */
1272  if (do_add)
1273    {
1274      svn_boolean_t src_readable;
1275      svn_fs_root_t *copyfrom_root;
1276
1277      /* Was this node copied? */
1278      SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
1279                            &src_readable, root, change,
1280                            authz_read_func, authz_read_baton,
1281                            repos_relpath, scratch_pool, scratch_pool));
1282
1283      /* If we have a copyfrom path, and we can't read it or we're just
1284         ignoring it, or the copyfrom rev is prior to the low water mark
1285         then we just null them out and do a raw add with no history at
1286         all. */
1287      if (copyfrom_path
1288          && ((! src_readable)
1289              || (svn_relpath_skip_ancestor(base_repos_relpath,
1290                                            copyfrom_path + 1) == NULL)
1291              || (low_water_mark > copyfrom_rev)))
1292        {
1293          copyfrom_path = NULL;
1294          copyfrom_rev = SVN_INVALID_REVNUM;
1295        }
1296
1297      /* Do the right thing based on the path KIND. */
1298      if (change->node_kind == svn_node_dir)
1299        {
1300          /* If this is a copy, but we can't represent it as such,
1301             then we just do a recursive add of the source path
1302             contents. */
1303          if (change->copyfrom_path && ! copyfrom_path)
1304            {
1305              SVN_ERR(add_subdir_ev2(copyfrom_root, root, editor,
1306                                     repos_relpath, change->copyfrom_path,
1307                                     authz_read_func, authz_read_baton,
1308                                     changed_paths, result_pool,
1309                                     scratch_pool));
1310            }
1311          else
1312            {
1313              if (copyfrom_path)
1314                {
1315                  if (copyfrom_path[0] == '/')
1316                    ++copyfrom_path;
1317                  SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
1318                                          repos_relpath, replaces_rev));
1319                }
1320              else
1321                {
1322                  apr_array_header_t *children;
1323                  apr_hash_t *props;
1324                  apr_hash_t *dirents;
1325
1326                  SVN_ERR(svn_fs_dir_entries(&dirents, root, repos_relpath,
1327                                             scratch_pool));
1328                  SVN_ERR(svn_hash_keys(&children, dirents, scratch_pool));
1329
1330                  SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1331                                               scratch_pool));
1332
1333                  SVN_ERR(svn_editor_add_directory(editor, repos_relpath,
1334                                                   children, props,
1335                                                   replaces_rev));
1336                }
1337            }
1338        }
1339      else
1340        {
1341          if (copyfrom_path)
1342            {
1343              if (copyfrom_path[0] == '/')
1344                ++copyfrom_path;
1345              SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
1346                                      repos_relpath, replaces_rev));
1347            }
1348          else
1349            {
1350              apr_hash_t *props;
1351              svn_checksum_t *checksum;
1352              svn_stream_t *contents;
1353
1354              SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1355                                           scratch_pool));
1356
1357              SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
1358                                           scratch_pool));
1359
1360              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root,
1361                                           repos_relpath, TRUE, scratch_pool));
1362
1363              SVN_ERR(svn_editor_add_file(editor, repos_relpath, checksum,
1364                                          contents, props, replaces_rev));
1365            }
1366        }
1367
1368      /* If we represent this as a copy... */
1369      if (copyfrom_path)
1370        {
1371          /* If it is a directory, make sure descendants get the correct
1372             delta source by remembering that we are operating inside a
1373             (possibly nested) copy operation. */
1374          if (change->node_kind == svn_node_dir)
1375            {
1376              struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
1377
1378              info->path = apr_pstrdup(result_pool, repos_relpath);
1379              info->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
1380              info->copyfrom_rev = copyfrom_rev;
1381
1382              APR_ARRAY_PUSH(copies, struct copy_info *) = info;
1383            }
1384        }
1385      else
1386        /* Else, we are an add without history... */
1387        {
1388          /* If an ancestor is added with history, we need to forget about
1389             that here, go on with life and repeat all the mistakes of our
1390             past... */
1391          if (change->node_kind == svn_node_dir && copies->nelts > 0)
1392            {
1393              struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
1394
1395              info->path = apr_pstrdup(result_pool, repos_relpath);
1396              info->copyfrom_path = NULL;
1397              info->copyfrom_rev = SVN_INVALID_REVNUM;
1398
1399              APR_ARRAY_PUSH(copies, struct copy_info *) = info;
1400            }
1401        }
1402    }
1403  else if (! do_delete)
1404    {
1405      /* If we are inside an add with history, we need to adjust the
1406         delta source. */
1407      if (copies->nelts > 0)
1408        {
1409          struct copy_info *info = APR_ARRAY_IDX(copies,
1410                                                 copies->nelts - 1,
1411                                                 struct copy_info *);
1412          if (info->copyfrom_path)
1413            {
1414              const char *relpath = svn_relpath_skip_ancestor(info->path,
1415                                                              repos_relpath);
1416              SVN_ERR_ASSERT(relpath && *relpath);
1417              repos_relpath = svn_relpath_join(info->copyfrom_path,
1418                                               relpath, scratch_pool);
1419            }
1420        }
1421    }
1422
1423  if (! do_delete && !do_add)
1424    {
1425      apr_hash_t *props = NULL;
1426
1427      /* Is this a copy that was downgraded to a raw add?  (If so,
1428         we'll need to transmit properties and file contents and such
1429         for it regardless of what the CHANGE structure's text_mod
1430         and prop_mod flags say.)  */
1431      svn_boolean_t downgraded_copy = (change->copyfrom_known
1432                                       && change->copyfrom_path
1433                                       && (! copyfrom_path));
1434
1435      /* Handle property modifications. */
1436      if (change->prop_mod || downgraded_copy)
1437        {
1438          SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1439                                       scratch_pool));
1440        }
1441
1442      /* Handle textual modifications. */
1443      if (change->node_kind == svn_node_file
1444          && (change->text_mod || change->prop_mod || downgraded_copy))
1445        {
1446          svn_checksum_t *checksum = NULL;
1447          svn_stream_t *contents = NULL;
1448
1449          if (change->text_mod)
1450            {
1451              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1452                                           root, repos_relpath, TRUE,
1453                                           scratch_pool));
1454
1455              SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
1456                                           scratch_pool));
1457            }
1458
1459          SVN_ERR(svn_editor_alter_file(editor, repos_relpath,
1460                                        SVN_INVALID_REVNUM,
1461                                        checksum, contents, props));
1462        }
1463
1464      if (change->node_kind == svn_node_dir
1465          && (change->prop_mod || downgraded_copy))
1466        {
1467          apr_array_header_t *children = NULL;
1468
1469          SVN_ERR(svn_editor_alter_directory(editor, repos_relpath,
1470                                             SVN_INVALID_REVNUM, children,
1471                                             props));
1472        }
1473    }
1474
1475  return SVN_NO_ERROR;
1476}
1477
1478svn_error_t *
1479svn_repos__replay_ev2(svn_fs_root_t *root,
1480                      const char *base_repos_relpath,
1481                      svn_revnum_t low_water_mark,
1482                      svn_editor_t *editor,
1483                      svn_repos_authz_func_t authz_read_func,
1484                      void *authz_read_baton,
1485                      apr_pool_t *scratch_pool)
1486{
1487  apr_hash_t *fs_changes;
1488  apr_hash_t *changed_paths;
1489  apr_hash_index_t *hi;
1490  apr_array_header_t *paths;
1491  apr_array_header_t *copies;
1492  apr_pool_t *iterpool;
1493  svn_error_t *err = SVN_NO_ERROR;
1494  int i;
1495
1496  SVN_ERR_ASSERT(!svn_dirent_is_absolute(base_repos_relpath));
1497
1498  /* Special-case r0, which we know is an empty revision; if we don't
1499     special-case it we might end up trying to compare it to "r-1". */
1500  if (svn_fs_is_revision_root(root)
1501        && svn_fs_revision_root_revision(root) == 0)
1502    {
1503      return SVN_NO_ERROR;
1504    }
1505
1506  /* Fetch the paths changed under ROOT. */
1507  SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, scratch_pool));
1508
1509  /* Make an array from the keys of our CHANGED_PATHS hash, and copy
1510     the values into a new hash whose keys have no leading slashes. */
1511  paths = apr_array_make(scratch_pool, apr_hash_count(fs_changes),
1512                         sizeof(const char *));
1513  changed_paths = apr_hash_make(scratch_pool);
1514  for (hi = apr_hash_first(scratch_pool, fs_changes); hi;
1515        hi = apr_hash_next(hi))
1516    {
1517      const char *path = apr_hash_this_key(hi);
1518      apr_ssize_t keylen = apr_hash_this_key_len(hi);
1519      svn_fs_path_change2_t *change = apr_hash_this_val(hi);
1520      svn_boolean_t allowed = TRUE;
1521
1522      if (authz_read_func)
1523        SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
1524                                scratch_pool));
1525
1526      if (allowed)
1527        {
1528          if (path[0] == '/')
1529            {
1530              path++;
1531              keylen--;
1532            }
1533
1534          /* If the base_path doesn't match the top directory of this path
1535             we don't want anything to do with it... */
1536          if (svn_relpath_skip_ancestor(base_repos_relpath, path) != NULL)
1537            {
1538              APR_ARRAY_PUSH(paths, const char *) = path;
1539              apr_hash_set(changed_paths, path, keylen, change);
1540            }
1541          /* ...unless this was a change to one of the parent directories of
1542             base_path. */
1543          else if (svn_relpath_skip_ancestor(path, base_repos_relpath) != NULL)
1544            {
1545              APR_ARRAY_PUSH(paths, const char *) = path;
1546              apr_hash_set(changed_paths, path, keylen, change);
1547            }
1548        }
1549    }
1550
1551  /* If we were not given a low water mark, assume that everything is there,
1552     all the way back to revision 0. */
1553  if (! SVN_IS_VALID_REVNUM(low_water_mark))
1554    low_water_mark = 0;
1555
1556  copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *));
1557
1558  /* Sort the paths.  Although not strictly required by the API, this has
1559     the pleasant side effect of maintaining a consistent ordering of
1560     dumpfile contents. */
1561  svn_sort__array(paths, svn_sort_compare_paths);
1562
1563  /* Now actually handle the various paths. */
1564  iterpool = svn_pool_create(scratch_pool);
1565  for (i = 0; i < paths->nelts; i++)
1566    {
1567      const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *);
1568
1569      svn_pool_clear(iterpool);
1570      err = replay_node(root, repos_relpath, editor, low_water_mark,
1571                        base_repos_relpath, copies, changed_paths,
1572                        authz_read_func, authz_read_baton,
1573                        scratch_pool, iterpool);
1574      if (err)
1575        break;
1576    }
1577
1578  if (err)
1579    return svn_error_compose_create(err, svn_editor_abort(editor));
1580  else
1581    SVN_ERR(svn_editor_complete(editor));
1582
1583  svn_pool_destroy(iterpool);
1584  return SVN_NO_ERROR;
1585}
1586