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