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