1251881Speter/*
2251881Speter * replay.c:   an editor driver for changes made in a given revision
3251881Speter *             or transaction
4251881Speter *
5251881Speter * ====================================================================
6251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
7251881Speter *    or more contributor license agreements.  See the NOTICE file
8251881Speter *    distributed with this work for additional information
9251881Speter *    regarding copyright ownership.  The ASF licenses this file
10251881Speter *    to you under the Apache License, Version 2.0 (the
11251881Speter *    "License"); you may not use this file except in compliance
12251881Speter *    with the License.  You may obtain a copy of the License at
13251881Speter *
14251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
15251881Speter *
16251881Speter *    Unless required by applicable law or agreed to in writing,
17251881Speter *    software distributed under the License is distributed on an
18251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19251881Speter *    KIND, either express or implied.  See the License for the
20251881Speter *    specific language governing permissions and limitations
21251881Speter *    under the License.
22251881Speter * ====================================================================
23251881Speter */
24251881Speter
25251881Speter
26251881Speter#include <apr_hash.h>
27251881Speter
28251881Speter#include "svn_types.h"
29251881Speter#include "svn_delta.h"
30251881Speter#include "svn_hash.h"
31251881Speter#include "svn_fs.h"
32251881Speter#include "svn_checksum.h"
33251881Speter#include "svn_repos.h"
34251881Speter#include "svn_sorts.h"
35251881Speter#include "svn_props.h"
36251881Speter#include "svn_pools.h"
37251881Speter#include "svn_path.h"
38251881Speter#include "svn_private_config.h"
39251881Speter#include "private/svn_fspath.h"
40251881Speter#include "private/svn_repos_private.h"
41251881Speter#include "private/svn_delta_private.h"
42289180Speter#include "private/svn_sorts_private.h"
43251881Speter
44251881Speter
45251881Speter/*** Backstory ***/
46251881Speter
47251881Speter/* The year was 2003.  Subversion usage was rampant in the world, and
48251881Speter   there was a rapidly growing issues database to prove it.  To make
49251881Speter   matters worse, svn_repos_dir_delta() had simply outgrown itself.
50251881Speter   No longer content to simply describe the differences between two
51251881Speter   trees, the function had been slowly bearing the added
52251881Speter   responsibility of representing the actions that had been taken to
53251881Speter   cause those differences -- a burden it was never meant to bear.
54251881Speter   Now grown into a twisted mess of razor-sharp metal and glass, and
55251881Speter   trembling with a sort of momentarily stayed spring force,
56251881Speter   svn_repos_dir_delta was a timebomb poised for total annihilation of
57251881Speter   the American Midwest.
58251881Speter
59251881Speter   Subversion needed a change.
60251881Speter
61251881Speter   Changes, in fact.  And not just in the literary segue sense.  What
62251881Speter   Subversion desperately needed was a new mechanism solely
63251881Speter   responsible for replaying repository actions back to some
64251881Speter   interested party -- to translate and retransmit the contents of the
65251881Speter   Berkeley 'changes' database file. */
66251881Speter
67251881Speter/*** Overview ***/
68251881Speter
69251881Speter/* The filesystem keeps a record of high-level actions that affect the
70251881Speter   files and directories in itself.  The 'changes' table records
71251881Speter   additions, deletions, textual and property modifications, and so
72251881Speter   on.  The goal of the functions in this file is to examine those
73251881Speter   change records, and use them to drive an editor interface in such a
74251881Speter   way as to effectively replay those actions.
75251881Speter
76251881Speter   This is critically different than what svn_repos_dir_delta() was
77251881Speter   designed to do.  That function describes, in the simplest way it
78251881Speter   can, how to transform one tree into another.  It doesn't care
79251881Speter   whether or not this was the same way a user might have done this
80251881Speter   transformation.  More to the point, it doesn't care if this is how
81251881Speter   those differences *did* come into being.  And it is for this reason
82251881Speter   that it cannot be relied upon for tasks such as the repository
83251881Speter   dumpfile-generation code, which is supposed to represent not
84251881Speter   changes, but actions that cause changes.
85251881Speter
86251881Speter   So, what's the plan here?
87251881Speter
88251881Speter   First, we fetch the changes for a particular revision or
89251881Speter   transaction.  We get these as an array, sorted chronologically.
90251881Speter   From this array we will build a hash, keyed on the path associated
91251881Speter   with each change item, and whose values are arrays of changes made
92251881Speter   to that path, again preserving the chronological ordering.
93251881Speter
94251881Speter   Once our hash is built, we then sort all the keys of the hash (the
95251881Speter   paths) using a depth-first directory sort routine.
96251881Speter
97251881Speter   Finally, we drive an editor, moving down our list of sorted paths,
98251881Speter   and manufacturing any intermediate editor calls (directory openings
99251881Speter   and closures) needed to navigate between each successive path.  For
100251881Speter   each path, we replay the sorted actions that occurred at that path.
101251881Speter
102251881Speter   When we've finished the editor drive, we should have fully replayed
103251881Speter   the filesystem events that occurred in that revision or transaction
104251881Speter   (though not necessarily in the same order in which they
105251881Speter   occurred). */
106251881Speter
107251881Speter/* #define USE_EV2_IMPL */
108251881Speter
109251881Speter
110251881Speter/*** Helper functions. ***/
111251881Speter
112251881Speter
113251881Speter/* Information for an active copy, that is a directory which we are currently
114251881Speter   working on and which was added with history. */
115251881Speterstruct copy_info
116251881Speter{
117251881Speter  /* Destination relpath (relative to the root of the  . */
118251881Speter  const char *path;
119251881Speter
120251881Speter  /* Copy source path (expressed as an absolute FS path) or revision.
121251881Speter     NULL and SVN_INVALID_REVNUM if this is an add without history,
122251881Speter     nested inside an add with history. */
123251881Speter  const char *copyfrom_path;
124251881Speter  svn_revnum_t copyfrom_rev;
125251881Speter};
126251881Speter
127251881Speterstruct path_driver_cb_baton
128251881Speter{
129251881Speter  /* The root of the revision we're replaying. */
130251881Speter  svn_fs_root_t *root;
131251881Speter
132251881Speter  /* The root of the previous revision.  If this is non-NULL it means that
133251881Speter     we are supposed to generate props and text deltas relative to it. */
134251881Speter  svn_fs_root_t *compare_root;
135251881Speter
136251881Speter  apr_hash_t *changed_paths;
137251881Speter
138251881Speter  svn_repos_authz_func_t authz_read_func;
139251881Speter  void *authz_read_baton;
140251881Speter
141251881Speter  const char *base_path; /* relpath */
142251881Speter
143251881Speter  svn_revnum_t low_water_mark;
144251881Speter  /* Stack of active copy operations. */
145251881Speter  apr_array_header_t *copies;
146251881Speter
147251881Speter  /* The global pool for this replay operation. */
148251881Speter  apr_pool_t *pool;
149251881Speter};
150251881Speter
151251881Speter/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
152251881Speter   the appropriate editor calls to add it and its children without any
153251881Speter   history.  This is meant to be used when either a subset of the tree
154251881Speter   has been ignored and we need to copy something from that subset to
155251881Speter   the part of the tree we do care about, or if a subset of the tree is
156251881Speter   unavailable because of authz and we need to use it as the source of
157251881Speter   a copy. */
158251881Speterstatic svn_error_t *
159251881Speteradd_subdir(svn_fs_root_t *source_root,
160251881Speter           svn_fs_root_t *target_root,
161251881Speter           const svn_delta_editor_t *editor,
162251881Speter           void *edit_baton,
163251881Speter           const char *edit_path,
164251881Speter           void *parent_baton,
165251881Speter           const char *source_fspath,
166251881Speter           svn_repos_authz_func_t authz_read_func,
167251881Speter           void *authz_read_baton,
168251881Speter           apr_hash_t *changed_paths,
169251881Speter           apr_pool_t *pool,
170251881Speter           void **dir_baton)
171251881Speter{
172251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
173251881Speter  apr_hash_index_t *hi, *phi;
174251881Speter  apr_hash_t *dirents;
175251881Speter  apr_hash_t *props;
176251881Speter
177251881Speter  SVN_ERR(editor->add_directory(edit_path, parent_baton, NULL,
178251881Speter                                SVN_INVALID_REVNUM, pool, dir_baton));
179251881Speter
180251881Speter  SVN_ERR(svn_fs_node_proplist(&props, target_root, edit_path, pool));
181251881Speter
182251881Speter  for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
183251881Speter    {
184289180Speter      const char *key = apr_hash_this_key(phi);
185289180Speter      svn_string_t *val = apr_hash_this_val(phi);
186251881Speter
187251881Speter      svn_pool_clear(subpool);
188251881Speter      SVN_ERR(editor->change_dir_prop(*dir_baton, key, val, subpool));
189251881Speter    }
190251881Speter
191251881Speter  /* We have to get the dirents from the source path, not the target,
192251881Speter     because we want nested copies from *readable* paths to be handled by
193251881Speter     path_driver_cb_func, not add_subdir (in order to preserve history). */
194251881Speter  SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath, pool));
195251881Speter
196251881Speter  for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
197251881Speter    {
198362181Sdim      svn_fs_path_change3_t *change;
199251881Speter      svn_boolean_t readable = TRUE;
200289180Speter      svn_fs_dirent_t *dent = apr_hash_this_val(hi);
201251881Speter      const char *copyfrom_path = NULL;
202251881Speter      svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
203251881Speter      const char *new_edit_path;
204251881Speter
205251881Speter      svn_pool_clear(subpool);
206251881Speter
207251881Speter      new_edit_path = svn_relpath_join(edit_path, dent->name, subpool);
208251881Speter
209251881Speter      /* If a file or subdirectory of the copied directory is listed as a
210251881Speter         changed path (because it was modified after the copy but before the
211251881Speter         commit), we remove it from the changed_paths hash so that future
212251881Speter         calls to path_driver_cb_func will ignore it. */
213251881Speter      change = svn_hash_gets(changed_paths, new_edit_path);
214251881Speter      if (change)
215251881Speter        {
216251881Speter          svn_hash_sets(changed_paths, new_edit_path, NULL);
217251881Speter
218251881Speter          /* If it's a delete, skip this entry. */
219251881Speter          if (change->change_kind == svn_fs_path_change_delete)
220251881Speter            continue;
221251881Speter
222251881Speter          /* If it's a replacement, check for copyfrom info (if we
223251881Speter             don't have it already. */
224251881Speter          if (change->change_kind == svn_fs_path_change_replace)
225251881Speter            {
226251881Speter              if (! change->copyfrom_known)
227251881Speter                {
228251881Speter                  SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
229251881Speter                                             &change->copyfrom_path,
230251881Speter                                             target_root, new_edit_path, pool));
231251881Speter                  change->copyfrom_known = TRUE;
232251881Speter                }
233251881Speter              copyfrom_path = change->copyfrom_path;
234251881Speter              copyfrom_rev = change->copyfrom_rev;
235251881Speter            }
236251881Speter        }
237251881Speter
238251881Speter      if (authz_read_func)
239251881Speter        SVN_ERR(authz_read_func(&readable, target_root, new_edit_path,
240251881Speter                                authz_read_baton, pool));
241251881Speter
242251881Speter      if (! readable)
243251881Speter        continue;
244251881Speter
245251881Speter      if (dent->kind == svn_node_dir)
246251881Speter        {
247251881Speter          svn_fs_root_t *new_source_root;
248251881Speter          const char *new_source_fspath;
249251881Speter          void *new_dir_baton;
250251881Speter
251251881Speter          if (copyfrom_path)
252251881Speter            {
253251881Speter              svn_fs_t *fs = svn_fs_root_fs(source_root);
254251881Speter              SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
255251881Speter                                           copyfrom_rev, pool));
256251881Speter              new_source_fspath = copyfrom_path;
257251881Speter            }
258251881Speter          else
259251881Speter            {
260251881Speter              new_source_root = source_root;
261251881Speter              new_source_fspath = svn_fspath__join(source_fspath, dent->name,
262251881Speter                                                   subpool);
263251881Speter            }
264251881Speter
265251881Speter          /* ### authz considerations?
266251881Speter           *
267251881Speter           * I think not; when path_driver_cb_func() calls add_subdir(), it
268251881Speter           * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
269251881Speter           */
270251881Speter          if (change && change->change_kind == svn_fs_path_change_replace
271251881Speter              && copyfrom_path == NULL)
272251881Speter            {
273251881Speter              SVN_ERR(editor->add_directory(new_edit_path, *dir_baton,
274251881Speter                                            NULL, SVN_INVALID_REVNUM,
275251881Speter                                            subpool, &new_dir_baton));
276251881Speter            }
277251881Speter          else
278251881Speter            {
279251881Speter              SVN_ERR(add_subdir(new_source_root, target_root,
280251881Speter                                 editor, edit_baton, new_edit_path,
281251881Speter                                 *dir_baton, new_source_fspath,
282251881Speter                                 authz_read_func, authz_read_baton,
283251881Speter                                 changed_paths, subpool, &new_dir_baton));
284251881Speter            }
285251881Speter
286251881Speter          SVN_ERR(editor->close_directory(new_dir_baton, subpool));
287251881Speter        }
288251881Speter      else if (dent->kind == svn_node_file)
289251881Speter        {
290251881Speter          svn_txdelta_window_handler_t delta_handler;
291251881Speter          void *delta_handler_baton, *file_baton;
292251881Speter          svn_txdelta_stream_t *delta_stream;
293251881Speter          svn_checksum_t *checksum;
294251881Speter
295251881Speter          SVN_ERR(editor->add_file(new_edit_path, *dir_baton, NULL,
296251881Speter                                   SVN_INVALID_REVNUM, pool, &file_baton));
297251881Speter
298251881Speter          SVN_ERR(svn_fs_node_proplist(&props, target_root,
299251881Speter                                       new_edit_path, subpool));
300251881Speter
301251881Speter          for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
302251881Speter            {
303289180Speter              const char *key = apr_hash_this_key(phi);
304289180Speter              svn_string_t *val = apr_hash_this_val(phi);
305251881Speter
306251881Speter              SVN_ERR(editor->change_file_prop(file_baton, key, val, subpool));
307251881Speter            }
308251881Speter
309251881Speter          SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool,
310251881Speter                                          &delta_handler,
311251881Speter                                          &delta_handler_baton));
312251881Speter
313251881Speter          SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, NULL, NULL,
314251881Speter                                               target_root, new_edit_path,
315251881Speter                                               pool));
316251881Speter
317251881Speter          SVN_ERR(svn_txdelta_send_txstream(delta_stream,
318251881Speter                                            delta_handler,
319251881Speter                                            delta_handler_baton,
320251881Speter                                            pool));
321251881Speter
322251881Speter          SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, target_root,
323251881Speter                                       new_edit_path, TRUE, pool));
324251881Speter          SVN_ERR(editor->close_file(file_baton,
325251881Speter                                     svn_checksum_to_cstring(checksum, pool),
326251881Speter                                     pool));
327251881Speter        }
328251881Speter      else
329251881Speter        SVN_ERR_MALFUNCTION();
330251881Speter    }
331251881Speter
332251881Speter  svn_pool_destroy(subpool);
333251881Speter
334251881Speter  return SVN_NO_ERROR;
335251881Speter}
336251881Speter
337251881Speter/* Given PATH deleted under ROOT, return in READABLE whether the path was
338251881Speter   readable prior to the deletion.  Consult COPIES (a stack of 'struct
339251881Speter   copy_info') and AUTHZ_READ_FUNC. */
340251881Speterstatic svn_error_t *
341251881Speterwas_readable(svn_boolean_t *readable,
342251881Speter             svn_fs_root_t *root,
343251881Speter             const char *path,
344251881Speter             apr_array_header_t *copies,
345251881Speter             svn_repos_authz_func_t authz_read_func,
346251881Speter             void *authz_read_baton,
347251881Speter             apr_pool_t *result_pool,
348251881Speter             apr_pool_t *scratch_pool)
349251881Speter{
350251881Speter  svn_fs_root_t *inquire_root;
351251881Speter  const char *inquire_path;
352251881Speter  struct copy_info *info = NULL;
353251881Speter  const char *relpath;
354251881Speter
355251881Speter  /* Short circuit. */
356251881Speter  if (! authz_read_func)
357251881Speter    {
358251881Speter      *readable = TRUE;
359251881Speter      return SVN_NO_ERROR;
360251881Speter    }
361251881Speter
362251881Speter  if (copies->nelts != 0)
363251881Speter    info = APR_ARRAY_IDX(copies, copies->nelts - 1, struct copy_info *);
364251881Speter
365251881Speter  /* Are we under a copy? */
366251881Speter  if (info && (relpath = svn_relpath_skip_ancestor(info->path, path)))
367251881Speter    {
368251881Speter      SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root),
369251881Speter                                   info->copyfrom_rev, scratch_pool));
370251881Speter      inquire_path = svn_fspath__join(info->copyfrom_path, relpath,
371251881Speter                                      scratch_pool);
372251881Speter    }
373251881Speter  else
374251881Speter    {
375251881Speter      /* Compute the revision that ROOT is based on.  (Note that ROOT is not
376251881Speter         r0's root, since this function is only called for deletions.)
377251881Speter         ### Need a more succinct way to express this */
378251881Speter      svn_revnum_t inquire_rev = SVN_INVALID_REVNUM;
379251881Speter      if (svn_fs_is_txn_root(root))
380251881Speter        inquire_rev = svn_fs_txn_root_base_revision(root);
381251881Speter      if (svn_fs_is_revision_root(root))
382251881Speter        inquire_rev =  svn_fs_revision_root_revision(root)-1;
383251881Speter      SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(inquire_rev));
384251881Speter
385251881Speter      SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root),
386251881Speter                                   inquire_rev, scratch_pool));
387251881Speter      inquire_path = path;
388251881Speter    }
389251881Speter
390251881Speter  SVN_ERR(authz_read_func(readable, inquire_root, inquire_path,
391251881Speter                          authz_read_baton, result_pool));
392251881Speter
393251881Speter  return SVN_NO_ERROR;
394251881Speter}
395251881Speter
396251881Speter/* Initialize COPYFROM_ROOT, COPYFROM_PATH, and COPYFROM_REV with the
397251881Speter   revision root, fspath, and revnum of the copyfrom of CHANGE, which
398251881Speter   corresponds to PATH under ROOT.  If the copyfrom info is valid
399251881Speter   (i.e., is not (NULL, SVN_INVALID_REVNUM)), then initialize SRC_READABLE
400251881Speter   too, consulting AUTHZ_READ_FUNC and AUTHZ_READ_BATON if provided.
401251881Speter
402251881Speter   NOTE: If the copyfrom information in CHANGE is marked as unknown
403251881Speter   (meaning, its ->copyfrom_rev and ->copyfrom_path cannot be
404251881Speter   trusted), this function will also update those members of the
405251881Speter   CHANGE structure to carry accurate copyfrom information.  */
406251881Speterstatic svn_error_t *
407251881Speterfill_copyfrom(svn_fs_root_t **copyfrom_root,
408251881Speter              const char **copyfrom_path,
409251881Speter              svn_revnum_t *copyfrom_rev,
410251881Speter              svn_boolean_t *src_readable,
411251881Speter              svn_fs_root_t *root,
412362181Sdim              svn_fs_path_change3_t *change,
413251881Speter              svn_repos_authz_func_t authz_read_func,
414251881Speter              void *authz_read_baton,
415251881Speter              const char *path,
416251881Speter              apr_pool_t *result_pool,
417251881Speter              apr_pool_t *scratch_pool)
418251881Speter{
419251881Speter  if (! change->copyfrom_known)
420251881Speter    {
421251881Speter      SVN_ERR(svn_fs_copied_from(&(change->copyfrom_rev),
422251881Speter                                 &(change->copyfrom_path),
423251881Speter                                 root, path, result_pool));
424251881Speter      change->copyfrom_known = TRUE;
425251881Speter    }
426251881Speter  *copyfrom_rev = change->copyfrom_rev;
427251881Speter  *copyfrom_path = change->copyfrom_path;
428251881Speter
429251881Speter  if (*copyfrom_path && SVN_IS_VALID_REVNUM(*copyfrom_rev))
430251881Speter    {
431251881Speter      SVN_ERR(svn_fs_revision_root(copyfrom_root,
432251881Speter                                   svn_fs_root_fs(root),
433251881Speter                                   *copyfrom_rev, result_pool));
434251881Speter
435251881Speter      if (authz_read_func)
436251881Speter        {
437251881Speter          SVN_ERR(authz_read_func(src_readable, *copyfrom_root,
438251881Speter                                  *copyfrom_path,
439251881Speter                                  authz_read_baton, result_pool));
440251881Speter        }
441251881Speter      else
442251881Speter        *src_readable = TRUE;
443251881Speter    }
444251881Speter  else
445251881Speter    {
446251881Speter      *copyfrom_root = NULL;
447251881Speter      /* SRC_READABLE left uninitialized */
448251881Speter    }
449251881Speter  return SVN_NO_ERROR;
450251881Speter}
451251881Speter
452251881Speterstatic svn_error_t *
453251881Speterpath_driver_cb_func(void **dir_baton,
454362181Sdim                    const svn_delta_editor_t *editor,
455362181Sdim                    void *edit_baton,
456251881Speter                    void *parent_baton,
457251881Speter                    void *callback_baton,
458251881Speter                    const char *edit_path,
459251881Speter                    apr_pool_t *pool)
460251881Speter{
461251881Speter  struct path_driver_cb_baton *cb = callback_baton;
462251881Speter  svn_fs_root_t *root = cb->root;
463362181Sdim  svn_fs_path_change3_t *change;
464251881Speter  svn_boolean_t do_add = FALSE, do_delete = FALSE;
465251881Speter  void *file_baton = NULL;
466251881Speter  svn_revnum_t copyfrom_rev;
467251881Speter  const char *copyfrom_path;
468251881Speter  svn_fs_root_t *source_root = cb->compare_root;
469251881Speter  const char *source_fspath = NULL;
470251881Speter  const char *base_path = cb->base_path;
471251881Speter
472251881Speter  *dir_baton = NULL;
473251881Speter
474251881Speter  /* Initialize SOURCE_FSPATH. */
475251881Speter  if (source_root)
476251881Speter    source_fspath = svn_fspath__canonicalize(edit_path, pool);
477251881Speter
478251881Speter  /* First, flush the copies stack so it only contains ancestors of path. */
479251881Speter  while (cb->copies->nelts > 0
480251881Speter         && ! svn_dirent_is_ancestor(APR_ARRAY_IDX(cb->copies,
481251881Speter                                                   cb->copies->nelts - 1,
482251881Speter                                                   struct copy_info *)->path,
483251881Speter                                     edit_path))
484251881Speter    apr_array_pop(cb->copies);
485251881Speter
486251881Speter  change = svn_hash_gets(cb->changed_paths, edit_path);
487251881Speter  if (! change)
488251881Speter    {
489251881Speter      /* This can only happen if the path was removed from cb->changed_paths
490251881Speter         by an earlier call to add_subdir, which means the path was already
491251881Speter         handled and we should simply ignore it. */
492251881Speter      return SVN_NO_ERROR;
493251881Speter    }
494251881Speter  switch (change->change_kind)
495251881Speter    {
496251881Speter    case svn_fs_path_change_add:
497251881Speter      do_add = TRUE;
498251881Speter      break;
499251881Speter
500251881Speter    case svn_fs_path_change_delete:
501251881Speter      do_delete = TRUE;
502251881Speter      break;
503251881Speter
504251881Speter    case svn_fs_path_change_replace:
505251881Speter      do_add = TRUE;
506251881Speter      do_delete = TRUE;
507251881Speter      break;
508251881Speter
509251881Speter    case svn_fs_path_change_modify:
510251881Speter    default:
511251881Speter      /* do nothing */
512251881Speter      break;
513251881Speter    }
514251881Speter
515251881Speter  /* Handle any deletions. */
516251881Speter  if (do_delete)
517251881Speter    {
518251881Speter      svn_boolean_t readable;
519251881Speter
520251881Speter      /* Issue #4121: delete under under a copy, of a path that was unreadable
521251881Speter         at its pre-copy location. */
522251881Speter      SVN_ERR(was_readable(&readable, root, edit_path, cb->copies,
523251881Speter                            cb->authz_read_func, cb->authz_read_baton,
524251881Speter                            pool, pool));
525251881Speter      if (readable)
526251881Speter        SVN_ERR(editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
527251881Speter                                     parent_baton, pool));
528251881Speter    }
529251881Speter
530251881Speter  /* Fetch the node kind if it makes sense to do so. */
531251881Speter  if (! do_delete || do_add)
532251881Speter    {
533251881Speter      if (change->node_kind == svn_node_unknown)
534251881Speter        SVN_ERR(svn_fs_check_path(&(change->node_kind), root, edit_path, pool));
535251881Speter      if ((change->node_kind != svn_node_dir) &&
536251881Speter          (change->node_kind != svn_node_file))
537251881Speter        return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
538251881Speter                                 _("Filesystem path '%s' is neither a file "
539251881Speter                                   "nor a directory"), edit_path);
540251881Speter    }
541251881Speter
542251881Speter  /* Handle any adds/opens. */
543251881Speter  if (do_add)
544251881Speter    {
545251881Speter      svn_boolean_t src_readable;
546251881Speter      svn_fs_root_t *copyfrom_root;
547251881Speter
548289180Speter      /* E.g. when verifying corrupted repositories, their changed path
549289180Speter         lists may contain an ADD for "/".  The delta path driver will
550289180Speter         call us with a NULL parent in that case. */
551289180Speter      if (*edit_path == 0)
552289180Speter        return svn_error_create(SVN_ERR_FS_ALREADY_EXISTS, NULL,
553289180Speter                                _("Root directory already exists."));
554289180Speter
555251881Speter      /* Was this node copied? */
556251881Speter      SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
557251881Speter                            &src_readable, root, change,
558251881Speter                            cb->authz_read_func, cb->authz_read_baton,
559251881Speter                            edit_path, pool, pool));
560251881Speter
561251881Speter      /* If we have a copyfrom path, and we can't read it or we're just
562251881Speter         ignoring it, or the copyfrom rev is prior to the low water mark
563251881Speter         then we just null them out and do a raw add with no history at
564251881Speter         all. */
565251881Speter      if (copyfrom_path
566251881Speter          && ((! src_readable)
567251881Speter              || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL)
568251881Speter              || (cb->low_water_mark > copyfrom_rev)))
569251881Speter        {
570251881Speter          copyfrom_path = NULL;
571251881Speter          copyfrom_rev = SVN_INVALID_REVNUM;
572251881Speter        }
573251881Speter
574251881Speter      /* Do the right thing based on the path KIND. */
575251881Speter      if (change->node_kind == svn_node_dir)
576251881Speter        {
577251881Speter          /* If this is a copy, but we can't represent it as such,
578251881Speter             then we just do a recursive add of the source path
579251881Speter             contents. */
580251881Speter          if (change->copyfrom_path && ! copyfrom_path)
581251881Speter            {
582251881Speter              SVN_ERR(add_subdir(copyfrom_root, root, editor, edit_baton,
583251881Speter                                 edit_path, parent_baton, change->copyfrom_path,
584251881Speter                                 cb->authz_read_func, cb->authz_read_baton,
585251881Speter                                 cb->changed_paths, pool, dir_baton));
586251881Speter            }
587251881Speter          else
588251881Speter            {
589251881Speter              SVN_ERR(editor->add_directory(edit_path, parent_baton,
590251881Speter                                            copyfrom_path, copyfrom_rev,
591251881Speter                                            pool, dir_baton));
592251881Speter            }
593251881Speter        }
594251881Speter      else
595251881Speter        {
596251881Speter          SVN_ERR(editor->add_file(edit_path, parent_baton, copyfrom_path,
597251881Speter                                   copyfrom_rev, pool, &file_baton));
598251881Speter        }
599251881Speter
600251881Speter      /* If we represent this as a copy... */
601251881Speter      if (copyfrom_path)
602251881Speter        {
603251881Speter          /* If it is a directory, make sure descendants get the correct
604251881Speter             delta source by remembering that we are operating inside a
605251881Speter             (possibly nested) copy operation. */
606251881Speter          if (change->node_kind == svn_node_dir)
607251881Speter            {
608251881Speter              struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
609251881Speter
610251881Speter              info->path = apr_pstrdup(cb->pool, edit_path);
611251881Speter              info->copyfrom_path = apr_pstrdup(cb->pool, copyfrom_path);
612251881Speter              info->copyfrom_rev = copyfrom_rev;
613251881Speter
614251881Speter              APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
615251881Speter            }
616251881Speter
617251881Speter          /* Save the source so that we can use it later, when we
618251881Speter             need to generate text and prop deltas. */
619251881Speter          source_root = copyfrom_root;
620251881Speter          source_fspath = copyfrom_path;
621251881Speter        }
622251881Speter      else
623251881Speter        /* Else, we are an add without history... */
624251881Speter        {
625251881Speter          /* If an ancestor is added with history, we need to forget about
626251881Speter             that here, go on with life and repeat all the mistakes of our
627251881Speter             past... */
628251881Speter          if (change->node_kind == svn_node_dir && cb->copies->nelts > 0)
629251881Speter            {
630251881Speter              struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
631251881Speter
632251881Speter              info->path = apr_pstrdup(cb->pool, edit_path);
633251881Speter              info->copyfrom_path = NULL;
634251881Speter              info->copyfrom_rev = SVN_INVALID_REVNUM;
635251881Speter
636251881Speter              APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
637251881Speter            }
638251881Speter          source_root = NULL;
639251881Speter          source_fspath = NULL;
640251881Speter        }
641251881Speter    }
642251881Speter  else if (! do_delete)
643251881Speter    {
644251881Speter      /* Do the right thing based on the path KIND (and the presence
645251881Speter         of a PARENT_BATON). */
646251881Speter      if (change->node_kind == svn_node_dir)
647251881Speter        {
648251881Speter          if (parent_baton)
649251881Speter            {
650251881Speter              SVN_ERR(editor->open_directory(edit_path, parent_baton,
651251881Speter                                             SVN_INVALID_REVNUM,
652251881Speter                                             pool, dir_baton));
653251881Speter            }
654251881Speter          else
655251881Speter            {
656251881Speter              SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
657251881Speter                                        pool, dir_baton));
658251881Speter            }
659251881Speter        }
660251881Speter      else
661251881Speter        {
662251881Speter          SVN_ERR(editor->open_file(edit_path, parent_baton, SVN_INVALID_REVNUM,
663251881Speter                                    pool, &file_baton));
664251881Speter        }
665251881Speter      /* If we are inside an add with history, we need to adjust the
666251881Speter         delta source. */
667251881Speter      if (cb->copies->nelts > 0)
668251881Speter        {
669251881Speter          struct copy_info *info = APR_ARRAY_IDX(cb->copies,
670251881Speter                                                 cb->copies->nelts - 1,
671251881Speter                                                 struct copy_info *);
672251881Speter          if (info->copyfrom_path)
673251881Speter            {
674251881Speter              const char *relpath = svn_relpath_skip_ancestor(info->path,
675251881Speter                                                              edit_path);
676251881Speter              SVN_ERR_ASSERT(relpath && *relpath);
677251881Speter              SVN_ERR(svn_fs_revision_root(&source_root,
678251881Speter                                           svn_fs_root_fs(root),
679251881Speter                                           info->copyfrom_rev, pool));
680251881Speter              source_fspath = svn_fspath__join(info->copyfrom_path,
681251881Speter                                               relpath, pool);
682251881Speter            }
683251881Speter          else
684251881Speter            {
685251881Speter              /* This is an add without history, nested inside an
686251881Speter                 add with history.  We have no delta source in this case. */
687251881Speter              source_root = NULL;
688251881Speter              source_fspath = NULL;
689251881Speter            }
690251881Speter        }
691251881Speter    }
692251881Speter
693251881Speter  if (! do_delete || do_add)
694251881Speter    {
695251881Speter      /* Is this a copy that was downgraded to a raw add?  (If so,
696251881Speter         we'll need to transmit properties and file contents and such
697251881Speter         for it regardless of what the CHANGE structure's text_mod
698251881Speter         and prop_mod flags say.)  */
699251881Speter      svn_boolean_t downgraded_copy = (change->copyfrom_known
700251881Speter                                       && change->copyfrom_path
701251881Speter                                       && (! copyfrom_path));
702251881Speter
703251881Speter      /* Handle property modifications. */
704251881Speter      if (change->prop_mod || downgraded_copy)
705251881Speter        {
706251881Speter          if (cb->compare_root)
707251881Speter            {
708251881Speter              apr_array_header_t *prop_diffs;
709251881Speter              apr_hash_t *old_props;
710251881Speter              apr_hash_t *new_props;
711251881Speter              int i;
712251881Speter
713251881Speter              if (source_root)
714251881Speter                SVN_ERR(svn_fs_node_proplist(&old_props, source_root,
715251881Speter                                             source_fspath, pool));
716251881Speter              else
717251881Speter                old_props = apr_hash_make(pool);
718251881Speter
719251881Speter              SVN_ERR(svn_fs_node_proplist(&new_props, root, edit_path, pool));
720251881Speter
721251881Speter              SVN_ERR(svn_prop_diffs(&prop_diffs, new_props, old_props,
722251881Speter                                     pool));
723251881Speter
724251881Speter              for (i = 0; i < prop_diffs->nelts; ++i)
725251881Speter                {
726251881Speter                  svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
727251881Speter                   if (change->node_kind == svn_node_dir)
728251881Speter                     SVN_ERR(editor->change_dir_prop(*dir_baton, pc->name,
729251881Speter                                                     pc->value, pool));
730251881Speter                   else if (change->node_kind == svn_node_file)
731251881Speter                     SVN_ERR(editor->change_file_prop(file_baton, pc->name,
732251881Speter                                                      pc->value, pool));
733251881Speter                }
734251881Speter            }
735251881Speter          else
736251881Speter            {
737251881Speter              /* Just do a dummy prop change to signal that there are *any*
738251881Speter                 propmods. */
739251881Speter              if (change->node_kind == svn_node_dir)
740251881Speter                SVN_ERR(editor->change_dir_prop(*dir_baton, "", NULL,
741251881Speter                                                pool));
742251881Speter              else if (change->node_kind == svn_node_file)
743251881Speter                SVN_ERR(editor->change_file_prop(file_baton, "", NULL,
744251881Speter                                                 pool));
745251881Speter            }
746251881Speter        }
747251881Speter
748251881Speter      /* Handle textual modifications. */
749251881Speter      if (change->node_kind == svn_node_file
750251881Speter          && (change->text_mod || downgraded_copy))
751251881Speter        {
752251881Speter          svn_txdelta_window_handler_t delta_handler;
753251881Speter          void *delta_handler_baton;
754251881Speter          const char *hex_digest = NULL;
755251881Speter
756251881Speter          if (cb->compare_root && source_root && source_fspath)
757251881Speter            {
758251881Speter              svn_checksum_t *checksum;
759251881Speter              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
760251881Speter                                           source_root, source_fspath, TRUE,
761251881Speter                                           pool));
762251881Speter              hex_digest = svn_checksum_to_cstring(checksum, pool);
763251881Speter            }
764251881Speter
765251881Speter          SVN_ERR(editor->apply_textdelta(file_baton, hex_digest, pool,
766251881Speter                                          &delta_handler,
767251881Speter                                          &delta_handler_baton));
768251881Speter          if (cb->compare_root)
769251881Speter            {
770251881Speter              svn_txdelta_stream_t *delta_stream;
771251881Speter
772251881Speter              SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, source_root,
773251881Speter                                                   source_fspath, root,
774251881Speter                                                   edit_path, pool));
775251881Speter              SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
776251881Speter                                                delta_handler_baton, pool));
777251881Speter            }
778251881Speter          else
779251881Speter            SVN_ERR(delta_handler(NULL, delta_handler_baton));
780251881Speter        }
781251881Speter    }
782251881Speter
783251881Speter  /* Close the file baton if we opened it. */
784251881Speter  if (file_baton)
785251881Speter    {
786251881Speter      svn_checksum_t *checksum;
787251881Speter      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, edit_path,
788251881Speter                                   TRUE, pool));
789251881Speter      SVN_ERR(editor->close_file(file_baton,
790251881Speter                                 svn_checksum_to_cstring(checksum, pool),
791251881Speter                                 pool));
792251881Speter    }
793251881Speter
794251881Speter  return SVN_NO_ERROR;
795251881Speter}
796251881Speter
797251881Speter#ifdef USE_EV2_IMPL
798251881Speterstatic svn_error_t *
799251881Speterfetch_kind_func(svn_node_kind_t *kind,
800251881Speter                void *baton,
801251881Speter                const char *path,
802251881Speter                svn_revnum_t base_revision,
803251881Speter                apr_pool_t *scratch_pool)
804251881Speter{
805251881Speter  svn_fs_root_t *root = baton;
806251881Speter  svn_fs_root_t *prev_root;
807251881Speter  svn_fs_t *fs = svn_fs_root_fs(root);
808251881Speter
809251881Speter  if (!SVN_IS_VALID_REVNUM(base_revision))
810251881Speter    base_revision = svn_fs_revision_root_revision(root) - 1;
811251881Speter
812251881Speter  SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
813251881Speter  SVN_ERR(svn_fs_check_path(kind, prev_root, path, scratch_pool));
814251881Speter
815251881Speter  return SVN_NO_ERROR;
816251881Speter}
817251881Speter
818251881Speterstatic svn_error_t *
819251881Speterfetch_props_func(apr_hash_t **props,
820251881Speter                 void *baton,
821251881Speter                 const char *path,
822251881Speter                 svn_revnum_t base_revision,
823251881Speter                 apr_pool_t *result_pool,
824251881Speter                 apr_pool_t *scratch_pool)
825251881Speter{
826251881Speter  svn_fs_root_t *root = baton;
827251881Speter  svn_fs_root_t *prev_root;
828251881Speter  svn_fs_t *fs = svn_fs_root_fs(root);
829251881Speter
830251881Speter  if (!SVN_IS_VALID_REVNUM(base_revision))
831251881Speter    base_revision = svn_fs_revision_root_revision(root) - 1;
832251881Speter
833251881Speter  SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
834251881Speter  SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool));
835251881Speter
836251881Speter  return SVN_NO_ERROR;
837251881Speter}
838251881Speter#endif
839251881Speter
840251881Speter
841251881Speter
842251881Speter
843362181Sdim/* Retrieve the path changes under ROOT, filter them with AUTHZ_READ_FUNC
844362181Sdim   and AUTHZ_READ_BATON and return those that intersect with BASE_RELPATH.
845362181Sdim
846362181Sdim   The svn_fs_path_change3_t* will be returned in *CHANGED_PATHS, keyed by
847362181Sdim   their path.  The paths themselves are additionally returned in *PATHS.
848362181Sdim
849362181Sdim   Allocate the returned data in RESULT_POOL and use SCRATCH_POOL for
850362181Sdim   temporary allocations.
851362181Sdim */
852362181Sdimstatic svn_error_t *
853362181Sdimget_relevant_changes(apr_hash_t **changed_paths,
854362181Sdim                     apr_array_header_t **paths,
855362181Sdim                     svn_fs_root_t *root,
856362181Sdim                     const char *base_relpath,
857362181Sdim                     svn_repos_authz_func_t authz_read_func,
858362181Sdim                     void *authz_read_baton,
859362181Sdim                     apr_pool_t *result_pool,
860362181Sdim                     apr_pool_t *scratch_pool)
861362181Sdim{
862362181Sdim  svn_fs_path_change_iterator_t *iterator;
863362181Sdim  svn_fs_path_change3_t *change;
864362181Sdim  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
865362181Sdim
866362181Sdim  /* Fetch the paths changed under ROOT. */
867362181Sdim  SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool));
868362181Sdim  SVN_ERR(svn_fs_path_change_get(&change, iterator));
869362181Sdim
870362181Sdim  /* Make an array from the keys of our CHANGED_PATHS hash, and copy
871362181Sdim     the values into a new hash whose keys have no leading slashes. */
872362181Sdim  *paths = apr_array_make(result_pool, 16, sizeof(const char *));
873362181Sdim  *changed_paths = apr_hash_make(result_pool);
874362181Sdim  while (change)
875362181Sdim    {
876362181Sdim      const char *path = change->path.data;
877362181Sdim      apr_ssize_t keylen = change->path.len;
878362181Sdim      svn_boolean_t allowed = TRUE;
879362181Sdim
880362181Sdim      svn_pool_clear(iterpool);
881362181Sdim      if (authz_read_func)
882362181Sdim        SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
883362181Sdim                                iterpool));
884362181Sdim
885362181Sdim      if (allowed)
886362181Sdim        {
887362181Sdim          if (path[0] == '/')
888362181Sdim            {
889362181Sdim              path++;
890362181Sdim              keylen--;
891362181Sdim            }
892362181Sdim
893362181Sdim          /* If the base_path doesn't match the top directory of this path
894362181Sdim             we don't want anything to do with it...
895362181Sdim             ...unless this was a change to one of the parent directories of
896362181Sdim             base_path. */
897362181Sdim          if (   svn_relpath_skip_ancestor(base_relpath, path)
898362181Sdim              || svn_relpath_skip_ancestor(path, base_relpath))
899362181Sdim            {
900362181Sdim              change = svn_fs_path_change3_dup(change, result_pool);
901362181Sdim              path = change->path.data;
902362181Sdim              if (path[0] == '/')
903362181Sdim                path++;
904362181Sdim
905362181Sdim              APR_ARRAY_PUSH(*paths, const char *) = path;
906362181Sdim              apr_hash_set(*changed_paths, path, keylen, change);
907362181Sdim            }
908362181Sdim        }
909362181Sdim
910362181Sdim      SVN_ERR(svn_fs_path_change_get(&change, iterator));
911362181Sdim    }
912362181Sdim
913362181Sdim  svn_pool_destroy(iterpool);
914362181Sdim  return SVN_NO_ERROR;
915362181Sdim}
916362181Sdim
917251881Spetersvn_error_t *
918251881Spetersvn_repos_replay2(svn_fs_root_t *root,
919251881Speter                  const char *base_path,
920251881Speter                  svn_revnum_t low_water_mark,
921251881Speter                  svn_boolean_t send_deltas,
922251881Speter                  const svn_delta_editor_t *editor,
923251881Speter                  void *edit_baton,
924251881Speter                  svn_repos_authz_func_t authz_read_func,
925251881Speter                  void *authz_read_baton,
926251881Speter                  apr_pool_t *pool)
927251881Speter{
928251881Speter#ifndef USE_EV2_IMPL
929251881Speter  apr_hash_t *changed_paths;
930251881Speter  apr_array_header_t *paths;
931251881Speter  struct path_driver_cb_baton cb_baton;
932251881Speter
933251881Speter  /* Special-case r0, which we know is an empty revision; if we don't
934251881Speter     special-case it we might end up trying to compare it to "r-1". */
935251881Speter  if (svn_fs_is_revision_root(root) && svn_fs_revision_root_revision(root) == 0)
936251881Speter    {
937251881Speter      SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
938251881Speter      return SVN_NO_ERROR;
939251881Speter    }
940251881Speter
941251881Speter  if (! base_path)
942251881Speter    base_path = "";
943251881Speter  else if (base_path[0] == '/')
944251881Speter    ++base_path;
945251881Speter
946362181Sdim  /* Fetch the paths changed under ROOT. */
947362181Sdim  SVN_ERR(get_relevant_changes(&changed_paths, &paths, root, base_path,
948362181Sdim                               authz_read_func, authz_read_baton,
949362181Sdim                               pool, pool));
950251881Speter
951251881Speter  /* If we were not given a low water mark, assume that everything is there,
952251881Speter     all the way back to revision 0. */
953251881Speter  if (! SVN_IS_VALID_REVNUM(low_water_mark))
954251881Speter    low_water_mark = 0;
955251881Speter
956251881Speter  /* Initialize our callback baton. */
957251881Speter  cb_baton.root = root;
958251881Speter  cb_baton.changed_paths = changed_paths;
959251881Speter  cb_baton.authz_read_func = authz_read_func;
960251881Speter  cb_baton.authz_read_baton = authz_read_baton;
961251881Speter  cb_baton.base_path = base_path;
962251881Speter  cb_baton.low_water_mark = low_water_mark;
963251881Speter  cb_baton.compare_root = NULL;
964251881Speter
965251881Speter  if (send_deltas)
966251881Speter    {
967251881Speter      SVN_ERR(svn_fs_revision_root(&cb_baton.compare_root,
968251881Speter                                   svn_fs_root_fs(root),
969251881Speter                                   svn_fs_is_revision_root(root)
970251881Speter                                     ? svn_fs_revision_root_revision(root) - 1
971251881Speter                                     : svn_fs_txn_root_base_revision(root),
972251881Speter                                   pool));
973251881Speter    }
974251881Speter
975251881Speter  cb_baton.copies = apr_array_make(pool, 4, sizeof(struct copy_info *));
976251881Speter  cb_baton.pool = pool;
977251881Speter
978251881Speter  /* Determine the revision to use throughout the edit, and call
979251881Speter     EDITOR's set_target_revision() function.  */
980251881Speter  if (svn_fs_is_revision_root(root))
981251881Speter    {
982251881Speter      svn_revnum_t revision = svn_fs_revision_root_revision(root);
983251881Speter      SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
984251881Speter    }
985251881Speter
986251881Speter  /* Call the path-based editor driver. */
987362181Sdim  return svn_delta_path_driver3(editor, edit_baton,
988251881Speter                                paths, TRUE,
989251881Speter                                path_driver_cb_func, &cb_baton, pool);
990251881Speter#else
991251881Speter  svn_editor_t *editorv2;
992251881Speter  struct svn_delta__extra_baton *exb;
993251881Speter  svn_delta__unlock_func_t unlock_func;
994251881Speter  svn_boolean_t send_abs_paths;
995251881Speter  const char *repos_root = "";
996251881Speter  void *unlock_baton;
997251881Speter
998362181Sdim  /* If we were not given a low water mark, assume that everything is there,
999362181Sdim     all the way back to revision 0. */
1000362181Sdim  if (! SVN_IS_VALID_REVNUM(low_water_mark))
1001362181Sdim    low_water_mark = 0;
1002362181Sdim
1003251881Speter  /* Special-case r0, which we know is an empty revision; if we don't
1004251881Speter     special-case it we might end up trying to compare it to "r-1". */
1005251881Speter  if (svn_fs_is_revision_root(root)
1006251881Speter        && svn_fs_revision_root_revision(root) == 0)
1007251881Speter    {
1008251881Speter      SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
1009251881Speter      return SVN_NO_ERROR;
1010251881Speter    }
1011251881Speter
1012251881Speter  /* Determine the revision to use throughout the edit, and call
1013251881Speter     EDITOR's set_target_revision() function.  */
1014251881Speter  if (svn_fs_is_revision_root(root))
1015251881Speter    {
1016251881Speter      svn_revnum_t revision = svn_fs_revision_root_revision(root);
1017251881Speter      SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
1018251881Speter    }
1019251881Speter
1020251881Speter  if (! base_path)
1021251881Speter    base_path = "";
1022251881Speter  else if (base_path[0] == '/')
1023251881Speter    ++base_path;
1024251881Speter
1025251881Speter  /* Use the shim to convert our editor to an Ev2 editor, and pass it down
1026251881Speter     the stack. */
1027251881Speter  SVN_ERR(svn_delta__editor_from_delta(&editorv2, &exb,
1028251881Speter                                       &unlock_func, &unlock_baton,
1029251881Speter                                       editor, edit_baton,
1030251881Speter                                       &send_abs_paths,
1031251881Speter                                       repos_root, "",
1032251881Speter                                       NULL, NULL,
1033251881Speter                                       fetch_kind_func, root,
1034251881Speter                                       fetch_props_func, root,
1035251881Speter                                       pool, pool));
1036251881Speter
1037251881Speter  /* Tell the shim that we're starting the process. */
1038251881Speter  SVN_ERR(exb->start_edit(exb->baton, svn_fs_revision_root_revision(root)));
1039251881Speter
1040251881Speter  /* ### We're ignoring SEND_DELTAS here. */
1041251881Speter  SVN_ERR(svn_repos__replay_ev2(root, base_path, low_water_mark,
1042251881Speter                                editorv2, authz_read_func, authz_read_baton,
1043251881Speter                                pool));
1044251881Speter
1045251881Speter  return SVN_NO_ERROR;
1046251881Speter#endif
1047251881Speter}
1048251881Speter
1049251881Speter
1050251881Speter/*****************************************************************
1051251881Speter *                      Ev2 Implementation                       *
1052251881Speter *****************************************************************/
1053251881Speter
1054251881Speter/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
1055251881Speter   the appropriate editor calls to add it and its children without any
1056251881Speter   history.  This is meant to be used when either a subset of the tree
1057251881Speter   has been ignored and we need to copy something from that subset to
1058251881Speter   the part of the tree we do care about, or if a subset of the tree is
1059251881Speter   unavailable because of authz and we need to use it as the source of
1060251881Speter   a copy. */
1061251881Speterstatic svn_error_t *
1062251881Speteradd_subdir_ev2(svn_fs_root_t *source_root,
1063251881Speter               svn_fs_root_t *target_root,
1064251881Speter               svn_editor_t *editor,
1065251881Speter               const char *repos_relpath,
1066251881Speter               const char *source_fspath,
1067251881Speter               svn_repos_authz_func_t authz_read_func,
1068251881Speter               void *authz_read_baton,
1069251881Speter               apr_hash_t *changed_paths,
1070251881Speter               apr_pool_t *result_pool,
1071251881Speter               apr_pool_t *scratch_pool)
1072251881Speter{
1073251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1074251881Speter  apr_hash_index_t *hi;
1075251881Speter  apr_hash_t *dirents;
1076251881Speter  apr_hash_t *props = NULL;
1077251881Speter  apr_array_header_t *children = NULL;
1078251881Speter
1079251881Speter  SVN_ERR(svn_fs_node_proplist(&props, target_root, repos_relpath,
1080251881Speter                               scratch_pool));
1081251881Speter
1082251881Speter  SVN_ERR(svn_editor_add_directory(editor, repos_relpath, children,
1083251881Speter                                   props, SVN_INVALID_REVNUM));
1084251881Speter
1085251881Speter  /* We have to get the dirents from the source path, not the target,
1086251881Speter     because we want nested copies from *readable* paths to be handled by
1087251881Speter     path_driver_cb_func, not add_subdir (in order to preserve history). */
1088251881Speter  SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath,
1089251881Speter                             scratch_pool));
1090251881Speter
1091251881Speter  for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
1092251881Speter    {
1093362181Sdim      svn_fs_path_change3_t *change;
1094251881Speter      svn_boolean_t readable = TRUE;
1095289180Speter      svn_fs_dirent_t *dent = apr_hash_this_val(hi);
1096251881Speter      const char *copyfrom_path = NULL;
1097251881Speter      svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
1098251881Speter      const char *child_relpath;
1099251881Speter
1100251881Speter      svn_pool_clear(iterpool);
1101251881Speter
1102251881Speter      child_relpath = svn_relpath_join(repos_relpath, dent->name, iterpool);
1103251881Speter
1104251881Speter      /* If a file or subdirectory of the copied directory is listed as a
1105251881Speter         changed path (because it was modified after the copy but before the
1106251881Speter         commit), we remove it from the changed_paths hash so that future
1107251881Speter         calls to path_driver_cb_func will ignore it. */
1108251881Speter      change = svn_hash_gets(changed_paths, child_relpath);
1109251881Speter      if (change)
1110251881Speter        {
1111251881Speter          svn_hash_sets(changed_paths, child_relpath, NULL);
1112251881Speter
1113251881Speter          /* If it's a delete, skip this entry. */
1114251881Speter          if (change->change_kind == svn_fs_path_change_delete)
1115251881Speter            continue;
1116251881Speter
1117251881Speter          /* If it's a replacement, check for copyfrom info (if we
1118251881Speter             don't have it already. */
1119251881Speter          if (change->change_kind == svn_fs_path_change_replace)
1120251881Speter            {
1121251881Speter              if (! change->copyfrom_known)
1122251881Speter                {
1123251881Speter                  SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
1124251881Speter                                             &change->copyfrom_path,
1125251881Speter                                             target_root, child_relpath,
1126251881Speter                                             result_pool));
1127251881Speter                  change->copyfrom_known = TRUE;
1128251881Speter                }
1129251881Speter              copyfrom_path = change->copyfrom_path;
1130251881Speter              copyfrom_rev = change->copyfrom_rev;
1131251881Speter            }
1132251881Speter        }
1133251881Speter
1134251881Speter      if (authz_read_func)
1135251881Speter        SVN_ERR(authz_read_func(&readable, target_root, child_relpath,
1136251881Speter                                authz_read_baton, iterpool));
1137251881Speter
1138251881Speter      if (! readable)
1139251881Speter        continue;
1140251881Speter
1141251881Speter      if (dent->kind == svn_node_dir)
1142251881Speter        {
1143251881Speter          svn_fs_root_t *new_source_root;
1144251881Speter          const char *new_source_fspath;
1145251881Speter
1146251881Speter          if (copyfrom_path)
1147251881Speter            {
1148251881Speter              svn_fs_t *fs = svn_fs_root_fs(source_root);
1149251881Speter              SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
1150251881Speter                                           copyfrom_rev, result_pool));
1151251881Speter              new_source_fspath = copyfrom_path;
1152251881Speter            }
1153251881Speter          else
1154251881Speter            {
1155251881Speter              new_source_root = source_root;
1156251881Speter              new_source_fspath = svn_fspath__join(source_fspath, dent->name,
1157251881Speter                                                   iterpool);
1158251881Speter            }
1159251881Speter
1160251881Speter          /* ### authz considerations?
1161251881Speter           *
1162251881Speter           * I think not; when path_driver_cb_func() calls add_subdir(), it
1163251881Speter           * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
1164251881Speter           */
1165251881Speter          if (change && change->change_kind == svn_fs_path_change_replace
1166251881Speter              && copyfrom_path == NULL)
1167251881Speter            {
1168251881Speter              SVN_ERR(svn_editor_add_directory(editor, child_relpath,
1169251881Speter                                               children, props,
1170251881Speter                                               SVN_INVALID_REVNUM));
1171251881Speter            }
1172251881Speter          else
1173251881Speter            {
1174251881Speter              SVN_ERR(add_subdir_ev2(new_source_root, target_root,
1175251881Speter                                     editor, child_relpath,
1176251881Speter                                     new_source_fspath,
1177251881Speter                                     authz_read_func, authz_read_baton,
1178251881Speter                                     changed_paths, result_pool, iterpool));
1179251881Speter            }
1180251881Speter        }
1181251881Speter      else if (dent->kind == svn_node_file)
1182251881Speter        {
1183251881Speter          svn_checksum_t *checksum;
1184251881Speter          svn_stream_t *contents;
1185251881Speter
1186251881Speter          SVN_ERR(svn_fs_node_proplist(&props, target_root,
1187251881Speter                                       child_relpath, iterpool));
1188251881Speter
1189251881Speter          SVN_ERR(svn_fs_file_contents(&contents, target_root,
1190251881Speter                                       child_relpath, iterpool));
1191251881Speter
1192251881Speter          SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1193251881Speter                                       target_root,
1194251881Speter                                       child_relpath, TRUE, iterpool));
1195251881Speter
1196251881Speter          SVN_ERR(svn_editor_add_file(editor, child_relpath, checksum,
1197251881Speter                                      contents, props, SVN_INVALID_REVNUM));
1198251881Speter        }
1199251881Speter      else
1200251881Speter        SVN_ERR_MALFUNCTION();
1201251881Speter    }
1202251881Speter
1203251881Speter  svn_pool_destroy(iterpool);
1204251881Speter
1205251881Speter  return SVN_NO_ERROR;
1206251881Speter}
1207251881Speter
1208251881Speterstatic svn_error_t *
1209251881Speterreplay_node(svn_fs_root_t *root,
1210251881Speter            const char *repos_relpath,
1211251881Speter            svn_editor_t *editor,
1212251881Speter            svn_revnum_t low_water_mark,
1213251881Speter            const char *base_repos_relpath,
1214251881Speter            apr_array_header_t *copies,
1215251881Speter            apr_hash_t *changed_paths,
1216251881Speter            svn_repos_authz_func_t authz_read_func,
1217251881Speter            void *authz_read_baton,
1218251881Speter            apr_pool_t *result_pool,
1219251881Speter            apr_pool_t *scratch_pool)
1220251881Speter{
1221362181Sdim  svn_fs_path_change3_t *change;
1222251881Speter  svn_boolean_t do_add = FALSE;
1223251881Speter  svn_boolean_t do_delete = FALSE;
1224251881Speter  svn_revnum_t copyfrom_rev;
1225251881Speter  const char *copyfrom_path;
1226251881Speter  svn_revnum_t replaces_rev;
1227251881Speter
1228251881Speter  /* First, flush the copies stack so it only contains ancestors of path. */
1229251881Speter  while (copies->nelts > 0
1230251881Speter         && (svn_relpath_skip_ancestor(APR_ARRAY_IDX(copies,
1231251881Speter                                                    copies->nelts - 1,
1232251881Speter                                                     struct copy_info *)->path,
1233251881Speter                                       repos_relpath) == NULL) )
1234251881Speter    apr_array_pop(copies);
1235251881Speter
1236251881Speter  change = svn_hash_gets(changed_paths, repos_relpath);
1237251881Speter  if (! change)
1238251881Speter    {
1239251881Speter      /* This can only happen if the path was removed from changed_paths
1240251881Speter         by an earlier call to add_subdir, which means the path was already
1241251881Speter         handled and we should simply ignore it. */
1242251881Speter      return SVN_NO_ERROR;
1243251881Speter    }
1244251881Speter  switch (change->change_kind)
1245251881Speter    {
1246251881Speter    case svn_fs_path_change_add:
1247251881Speter      do_add = TRUE;
1248251881Speter      break;
1249251881Speter
1250251881Speter    case svn_fs_path_change_delete:
1251251881Speter      do_delete = TRUE;
1252251881Speter      break;
1253251881Speter
1254251881Speter    case svn_fs_path_change_replace:
1255251881Speter      do_add = TRUE;
1256251881Speter      do_delete = TRUE;
1257251881Speter      break;
1258251881Speter
1259251881Speter    case svn_fs_path_change_modify:
1260251881Speter    default:
1261251881Speter      /* do nothing */
1262251881Speter      break;
1263251881Speter    }
1264251881Speter
1265251881Speter  /* Handle any deletions. */
1266251881Speter  if (do_delete && ! do_add)
1267251881Speter    {
1268251881Speter      svn_boolean_t readable;
1269251881Speter
1270251881Speter      /* Issue #4121: delete under under a copy, of a path that was unreadable
1271251881Speter         at its pre-copy location. */
1272251881Speter      SVN_ERR(was_readable(&readable, root, repos_relpath, copies,
1273251881Speter                            authz_read_func, authz_read_baton,
1274251881Speter                            scratch_pool, scratch_pool));
1275251881Speter      if (readable)
1276251881Speter        SVN_ERR(svn_editor_delete(editor, repos_relpath, SVN_INVALID_REVNUM));
1277251881Speter
1278251881Speter      return SVN_NO_ERROR;
1279251881Speter    }
1280251881Speter
1281251881Speter  /* Handle replacements. */
1282251881Speter  if (do_delete && do_add)
1283251881Speter    replaces_rev = svn_fs_revision_root_revision(root);
1284251881Speter  else
1285251881Speter    replaces_rev = SVN_INVALID_REVNUM;
1286251881Speter
1287251881Speter  /* Fetch the node kind if it makes sense to do so. */
1288251881Speter  if (! do_delete || do_add)
1289251881Speter    {
1290251881Speter      if (change->node_kind == svn_node_unknown)
1291251881Speter        SVN_ERR(svn_fs_check_path(&(change->node_kind), root, repos_relpath,
1292251881Speter                                  scratch_pool));
1293251881Speter      if ((change->node_kind != svn_node_dir) &&
1294251881Speter          (change->node_kind != svn_node_file))
1295251881Speter        return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1296251881Speter                                 _("Filesystem path '%s' is neither a file "
1297251881Speter                                   "nor a directory"), repos_relpath);
1298251881Speter    }
1299251881Speter
1300251881Speter  /* Handle any adds/opens. */
1301251881Speter  if (do_add)
1302251881Speter    {
1303251881Speter      svn_boolean_t src_readable;
1304251881Speter      svn_fs_root_t *copyfrom_root;
1305251881Speter
1306251881Speter      /* Was this node copied? */
1307251881Speter      SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
1308251881Speter                            &src_readable, root, change,
1309251881Speter                            authz_read_func, authz_read_baton,
1310251881Speter                            repos_relpath, scratch_pool, scratch_pool));
1311251881Speter
1312251881Speter      /* If we have a copyfrom path, and we can't read it or we're just
1313251881Speter         ignoring it, or the copyfrom rev is prior to the low water mark
1314251881Speter         then we just null them out and do a raw add with no history at
1315251881Speter         all. */
1316251881Speter      if (copyfrom_path
1317251881Speter          && ((! src_readable)
1318251881Speter              || (svn_relpath_skip_ancestor(base_repos_relpath,
1319251881Speter                                            copyfrom_path + 1) == NULL)
1320251881Speter              || (low_water_mark > copyfrom_rev)))
1321251881Speter        {
1322251881Speter          copyfrom_path = NULL;
1323251881Speter          copyfrom_rev = SVN_INVALID_REVNUM;
1324251881Speter        }
1325251881Speter
1326251881Speter      /* Do the right thing based on the path KIND. */
1327251881Speter      if (change->node_kind == svn_node_dir)
1328251881Speter        {
1329251881Speter          /* If this is a copy, but we can't represent it as such,
1330251881Speter             then we just do a recursive add of the source path
1331251881Speter             contents. */
1332251881Speter          if (change->copyfrom_path && ! copyfrom_path)
1333251881Speter            {
1334251881Speter              SVN_ERR(add_subdir_ev2(copyfrom_root, root, editor,
1335251881Speter                                     repos_relpath, change->copyfrom_path,
1336251881Speter                                     authz_read_func, authz_read_baton,
1337251881Speter                                     changed_paths, result_pool,
1338251881Speter                                     scratch_pool));
1339251881Speter            }
1340251881Speter          else
1341251881Speter            {
1342251881Speter              if (copyfrom_path)
1343251881Speter                {
1344251881Speter                  if (copyfrom_path[0] == '/')
1345251881Speter                    ++copyfrom_path;
1346251881Speter                  SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
1347251881Speter                                          repos_relpath, replaces_rev));
1348251881Speter                }
1349251881Speter              else
1350251881Speter                {
1351251881Speter                  apr_array_header_t *children;
1352251881Speter                  apr_hash_t *props;
1353251881Speter                  apr_hash_t *dirents;
1354251881Speter
1355251881Speter                  SVN_ERR(svn_fs_dir_entries(&dirents, root, repos_relpath,
1356251881Speter                                             scratch_pool));
1357251881Speter                  SVN_ERR(svn_hash_keys(&children, dirents, scratch_pool));
1358251881Speter
1359251881Speter                  SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1360251881Speter                                               scratch_pool));
1361251881Speter
1362251881Speter                  SVN_ERR(svn_editor_add_directory(editor, repos_relpath,
1363251881Speter                                                   children, props,
1364251881Speter                                                   replaces_rev));
1365251881Speter                }
1366251881Speter            }
1367251881Speter        }
1368251881Speter      else
1369251881Speter        {
1370251881Speter          if (copyfrom_path)
1371251881Speter            {
1372251881Speter              if (copyfrom_path[0] == '/')
1373251881Speter                ++copyfrom_path;
1374251881Speter              SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
1375251881Speter                                      repos_relpath, replaces_rev));
1376251881Speter            }
1377251881Speter          else
1378251881Speter            {
1379251881Speter              apr_hash_t *props;
1380251881Speter              svn_checksum_t *checksum;
1381251881Speter              svn_stream_t *contents;
1382251881Speter
1383251881Speter              SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1384251881Speter                                           scratch_pool));
1385251881Speter
1386251881Speter              SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
1387251881Speter                                           scratch_pool));
1388251881Speter
1389251881Speter              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root,
1390251881Speter                                           repos_relpath, TRUE, scratch_pool));
1391251881Speter
1392251881Speter              SVN_ERR(svn_editor_add_file(editor, repos_relpath, checksum,
1393251881Speter                                          contents, props, replaces_rev));
1394251881Speter            }
1395251881Speter        }
1396251881Speter
1397251881Speter      /* If we represent this as a copy... */
1398251881Speter      if (copyfrom_path)
1399251881Speter        {
1400251881Speter          /* If it is a directory, make sure descendants get the correct
1401251881Speter             delta source by remembering that we are operating inside a
1402251881Speter             (possibly nested) copy operation. */
1403251881Speter          if (change->node_kind == svn_node_dir)
1404251881Speter            {
1405251881Speter              struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
1406251881Speter
1407251881Speter              info->path = apr_pstrdup(result_pool, repos_relpath);
1408251881Speter              info->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
1409251881Speter              info->copyfrom_rev = copyfrom_rev;
1410251881Speter
1411251881Speter              APR_ARRAY_PUSH(copies, struct copy_info *) = info;
1412251881Speter            }
1413251881Speter        }
1414251881Speter      else
1415251881Speter        /* Else, we are an add without history... */
1416251881Speter        {
1417251881Speter          /* If an ancestor is added with history, we need to forget about
1418251881Speter             that here, go on with life and repeat all the mistakes of our
1419251881Speter             past... */
1420251881Speter          if (change->node_kind == svn_node_dir && copies->nelts > 0)
1421251881Speter            {
1422251881Speter              struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
1423251881Speter
1424251881Speter              info->path = apr_pstrdup(result_pool, repos_relpath);
1425251881Speter              info->copyfrom_path = NULL;
1426251881Speter              info->copyfrom_rev = SVN_INVALID_REVNUM;
1427251881Speter
1428251881Speter              APR_ARRAY_PUSH(copies, struct copy_info *) = info;
1429251881Speter            }
1430251881Speter        }
1431251881Speter    }
1432251881Speter  else if (! do_delete)
1433251881Speter    {
1434251881Speter      /* If we are inside an add with history, we need to adjust the
1435251881Speter         delta source. */
1436251881Speter      if (copies->nelts > 0)
1437251881Speter        {
1438251881Speter          struct copy_info *info = APR_ARRAY_IDX(copies,
1439251881Speter                                                 copies->nelts - 1,
1440251881Speter                                                 struct copy_info *);
1441251881Speter          if (info->copyfrom_path)
1442251881Speter            {
1443251881Speter              const char *relpath = svn_relpath_skip_ancestor(info->path,
1444251881Speter                                                              repos_relpath);
1445251881Speter              SVN_ERR_ASSERT(relpath && *relpath);
1446251881Speter              repos_relpath = svn_relpath_join(info->copyfrom_path,
1447251881Speter                                               relpath, scratch_pool);
1448251881Speter            }
1449251881Speter        }
1450251881Speter    }
1451251881Speter
1452251881Speter  if (! do_delete && !do_add)
1453251881Speter    {
1454251881Speter      apr_hash_t *props = NULL;
1455251881Speter
1456251881Speter      /* Is this a copy that was downgraded to a raw add?  (If so,
1457251881Speter         we'll need to transmit properties and file contents and such
1458251881Speter         for it regardless of what the CHANGE structure's text_mod
1459251881Speter         and prop_mod flags say.)  */
1460251881Speter      svn_boolean_t downgraded_copy = (change->copyfrom_known
1461251881Speter                                       && change->copyfrom_path
1462251881Speter                                       && (! copyfrom_path));
1463251881Speter
1464251881Speter      /* Handle property modifications. */
1465251881Speter      if (change->prop_mod || downgraded_copy)
1466251881Speter        {
1467251881Speter          SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
1468251881Speter                                       scratch_pool));
1469251881Speter        }
1470251881Speter
1471251881Speter      /* Handle textual modifications. */
1472251881Speter      if (change->node_kind == svn_node_file
1473251881Speter          && (change->text_mod || change->prop_mod || downgraded_copy))
1474251881Speter        {
1475251881Speter          svn_checksum_t *checksum = NULL;
1476251881Speter          svn_stream_t *contents = NULL;
1477251881Speter
1478251881Speter          if (change->text_mod)
1479251881Speter            {
1480251881Speter              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1481251881Speter                                           root, repos_relpath, TRUE,
1482251881Speter                                           scratch_pool));
1483251881Speter
1484251881Speter              SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
1485251881Speter                                           scratch_pool));
1486251881Speter            }
1487251881Speter
1488251881Speter          SVN_ERR(svn_editor_alter_file(editor, repos_relpath,
1489289180Speter                                        SVN_INVALID_REVNUM,
1490289180Speter                                        checksum, contents, props));
1491251881Speter        }
1492251881Speter
1493251881Speter      if (change->node_kind == svn_node_dir
1494251881Speter          && (change->prop_mod || downgraded_copy))
1495251881Speter        {
1496251881Speter          apr_array_header_t *children = NULL;
1497251881Speter
1498251881Speter          SVN_ERR(svn_editor_alter_directory(editor, repos_relpath,
1499251881Speter                                             SVN_INVALID_REVNUM, children,
1500251881Speter                                             props));
1501251881Speter        }
1502251881Speter    }
1503251881Speter
1504251881Speter  return SVN_NO_ERROR;
1505251881Speter}
1506251881Speter
1507251881Spetersvn_error_t *
1508251881Spetersvn_repos__replay_ev2(svn_fs_root_t *root,
1509251881Speter                      const char *base_repos_relpath,
1510251881Speter                      svn_revnum_t low_water_mark,
1511251881Speter                      svn_editor_t *editor,
1512251881Speter                      svn_repos_authz_func_t authz_read_func,
1513251881Speter                      void *authz_read_baton,
1514251881Speter                      apr_pool_t *scratch_pool)
1515251881Speter{
1516251881Speter  apr_hash_t *changed_paths;
1517251881Speter  apr_array_header_t *paths;
1518251881Speter  apr_array_header_t *copies;
1519251881Speter  apr_pool_t *iterpool;
1520251881Speter  svn_error_t *err = SVN_NO_ERROR;
1521251881Speter  int i;
1522251881Speter
1523362181Sdim  SVN_ERR_ASSERT(svn_relpath_is_canonical(base_repos_relpath));
1524251881Speter
1525251881Speter  /* Special-case r0, which we know is an empty revision; if we don't
1526251881Speter     special-case it we might end up trying to compare it to "r-1". */
1527251881Speter  if (svn_fs_is_revision_root(root)
1528251881Speter        && svn_fs_revision_root_revision(root) == 0)
1529251881Speter    {
1530251881Speter      return SVN_NO_ERROR;
1531251881Speter    }
1532251881Speter
1533251881Speter  /* Fetch the paths changed under ROOT. */
1534362181Sdim  SVN_ERR(get_relevant_changes(&changed_paths, &paths, root,
1535362181Sdim                               base_repos_relpath,
1536362181Sdim                               authz_read_func, authz_read_baton,
1537362181Sdim                               scratch_pool, scratch_pool));
1538251881Speter
1539251881Speter  /* If we were not given a low water mark, assume that everything is there,
1540251881Speter     all the way back to revision 0. */
1541251881Speter  if (! SVN_IS_VALID_REVNUM(low_water_mark))
1542251881Speter    low_water_mark = 0;
1543251881Speter
1544251881Speter  copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *));
1545251881Speter
1546251881Speter  /* Sort the paths.  Although not strictly required by the API, this has
1547251881Speter     the pleasant side effect of maintaining a consistent ordering of
1548251881Speter     dumpfile contents. */
1549289180Speter  svn_sort__array(paths, svn_sort_compare_paths);
1550251881Speter
1551251881Speter  /* Now actually handle the various paths. */
1552251881Speter  iterpool = svn_pool_create(scratch_pool);
1553251881Speter  for (i = 0; i < paths->nelts; i++)
1554251881Speter    {
1555251881Speter      const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *);
1556251881Speter
1557251881Speter      svn_pool_clear(iterpool);
1558251881Speter      err = replay_node(root, repos_relpath, editor, low_water_mark,
1559251881Speter                        base_repos_relpath, copies, changed_paths,
1560251881Speter                        authz_read_func, authz_read_baton,
1561251881Speter                        scratch_pool, iterpool);
1562251881Speter      if (err)
1563251881Speter        break;
1564251881Speter    }
1565251881Speter
1566251881Speter  if (err)
1567251881Speter    return svn_error_compose_create(err, svn_editor_abort(editor));
1568251881Speter  else
1569251881Speter    SVN_ERR(svn_editor_complete(editor));
1570251881Speter
1571251881Speter  svn_pool_destroy(iterpool);
1572251881Speter  return SVN_NO_ERROR;
1573251881Speter}
1574