1251881Speter/* dump.c --- writing filesystem contents into a portable 'dumpfile' format.
2251881Speter *
3251881Speter * ====================================================================
4251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
5251881Speter *    or more contributor license agreements.  See the NOTICE file
6251881Speter *    distributed with this work for additional information
7251881Speter *    regarding copyright ownership.  The ASF licenses this file
8251881Speter *    to you under the Apache License, Version 2.0 (the
9251881Speter *    "License"); you may not use this file except in compliance
10251881Speter *    with the License.  You may obtain a copy of the License at
11251881Speter *
12251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
13251881Speter *
14251881Speter *    Unless required by applicable law or agreed to in writing,
15251881Speter *    software distributed under the License is distributed on an
16251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17251881Speter *    KIND, either express or implied.  See the License for the
18251881Speter *    specific language governing permissions and limitations
19251881Speter *    under the License.
20251881Speter * ====================================================================
21251881Speter */
22251881Speter
23251881Speter
24251881Speter#include "svn_private_config.h"
25251881Speter#include "svn_pools.h"
26251881Speter#include "svn_error.h"
27251881Speter#include "svn_fs.h"
28251881Speter#include "svn_hash.h"
29251881Speter#include "svn_iter.h"
30251881Speter#include "svn_repos.h"
31251881Speter#include "svn_string.h"
32251881Speter#include "svn_dirent_uri.h"
33251881Speter#include "svn_path.h"
34251881Speter#include "svn_time.h"
35251881Speter#include "svn_checksum.h"
36251881Speter#include "svn_props.h"
37251881Speter#include "svn_sorts.h"
38251881Speter
39251881Speter#include "private/svn_mergeinfo_private.h"
40251881Speter#include "private/svn_fs_private.h"
41251881Speter
42251881Speter#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
43251881Speter
44251881Speter/*----------------------------------------------------------------------*/
45251881Speter
46251881Speter
47251881Speter
48251881Speter/* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and
49251881Speter   store it into a new temporary file *TEMPFILE.  OLDROOT may be NULL,
50251881Speter   in which case the delta will be computed against an empty file, as
51251881Speter   per the svn_fs_get_file_delta_stream docstring.  Record the length
52251881Speter   of the temporary file in *LEN, and rewind the file before
53251881Speter   returning. */
54251881Speterstatic svn_error_t *
55251881Speterstore_delta(apr_file_t **tempfile, svn_filesize_t *len,
56251881Speter            svn_fs_root_t *oldroot, const char *oldpath,
57251881Speter            svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool)
58251881Speter{
59251881Speter  svn_stream_t *temp_stream;
60251881Speter  apr_off_t offset = 0;
61251881Speter  svn_txdelta_stream_t *delta_stream;
62251881Speter  svn_txdelta_window_handler_t wh;
63251881Speter  void *whb;
64251881Speter
65251881Speter  /* Create a temporary file and open a stream to it. Note that we need
66251881Speter     the file handle in order to rewind it. */
67251881Speter  SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL,
68251881Speter                                   svn_io_file_del_on_pool_cleanup,
69251881Speter                                   pool, pool));
70251881Speter  temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool);
71251881Speter
72251881Speter  /* Compute the delta and send it to the temporary file. */
73251881Speter  SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath,
74251881Speter                                       newroot, newpath, pool));
75251881Speter  svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0,
76251881Speter                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
77251881Speter  SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool));
78251881Speter
79251881Speter  /* Get the length of the temporary file and rewind it. */
80251881Speter  SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool));
81251881Speter  *len = offset;
82251881Speter  offset = 0;
83251881Speter  return svn_io_file_seek(*tempfile, APR_SET, &offset, pool);
84251881Speter}
85251881Speter
86251881Speter
87251881Speter/*----------------------------------------------------------------------*/
88251881Speter
89251881Speter/** An editor which dumps node-data in 'dumpfile format' to a file. **/
90251881Speter
91251881Speter/* Look, mom!  No file batons! */
92251881Speter
93251881Speterstruct edit_baton
94251881Speter{
95251881Speter  /* The relpath which implicitly prepends all full paths coming into
96251881Speter     this editor.  This will almost always be "".  */
97251881Speter  const char *path;
98251881Speter
99251881Speter  /* The stream to dump to. */
100251881Speter  svn_stream_t *stream;
101251881Speter
102251881Speter  /* Send feedback here, if non-NULL */
103251881Speter  svn_repos_notify_func_t notify_func;
104251881Speter  void *notify_baton;
105251881Speter
106251881Speter  /* The fs revision root, so we can read the contents of paths. */
107251881Speter  svn_fs_root_t *fs_root;
108251881Speter  svn_revnum_t current_rev;
109251881Speter
110251881Speter  /* The fs, so we can grab historic information if needed. */
111251881Speter  svn_fs_t *fs;
112251881Speter
113251881Speter  /* True if dumped nodes should output deltas instead of full text. */
114251881Speter  svn_boolean_t use_deltas;
115251881Speter
116251881Speter  /* True if this "dump" is in fact a verify. */
117251881Speter  svn_boolean_t verify;
118251881Speter
119251881Speter  /* The first revision dumped in this dumpstream. */
120251881Speter  svn_revnum_t oldest_dumped_rev;
121251881Speter
122251881Speter  /* If not NULL, set to true if any references to revisions older than
123251881Speter     OLDEST_DUMPED_REV were found in the dumpstream. */
124251881Speter  svn_boolean_t *found_old_reference;
125251881Speter
126251881Speter  /* If not NULL, set to true if any mergeinfo was dumped which contains
127251881Speter     revisions older than OLDEST_DUMPED_REV. */
128251881Speter  svn_boolean_t *found_old_mergeinfo;
129251881Speter
130251881Speter  /* reusable buffer for writing file contents */
131251881Speter  char buffer[SVN__STREAM_CHUNK_SIZE];
132251881Speter  apr_size_t bufsize;
133251881Speter};
134251881Speter
135251881Speterstruct dir_baton
136251881Speter{
137251881Speter  struct edit_baton *edit_baton;
138251881Speter  struct dir_baton *parent_dir_baton;
139251881Speter
140251881Speter  /* is this directory a new addition to this revision? */
141251881Speter  svn_boolean_t added;
142251881Speter
143251881Speter  /* has this directory been written to the output stream? */
144251881Speter  svn_boolean_t written_out;
145251881Speter
146251881Speter  /* the repository relpath associated with this directory */
147251881Speter  const char *path;
148251881Speter
149251881Speter  /* The comparison repository relpath and revision of this directory.
150251881Speter     If both of these are valid, use them as a source against which to
151251881Speter     compare the directory instead of the default comparison source of
152251881Speter     PATH in the previous revision. */
153251881Speter  const char *cmp_path;
154251881Speter  svn_revnum_t cmp_rev;
155251881Speter
156251881Speter  /* hash of paths that need to be deleted, though some -might- be
157251881Speter     replaced.  maps const char * paths to this dir_baton.  (they're
158251881Speter     full paths, because that's what the editor driver gives us.  but
159251881Speter     really, they're all within this directory.) */
160251881Speter  apr_hash_t *deleted_entries;
161251881Speter
162251881Speter  /* pool to be used for deleting the hash items */
163251881Speter  apr_pool_t *pool;
164251881Speter};
165251881Speter
166251881Speter
167251881Speter/* Make a directory baton to represent the directory was path
168251881Speter   (relative to EDIT_BATON's path) is PATH.
169251881Speter
170251881Speter   CMP_PATH/CMP_REV are the path/revision against which this directory
171251881Speter   should be compared for changes.  If either is omitted (NULL for the
172251881Speter   path, SVN_INVALID_REVNUM for the rev), just compare this directory
173251881Speter   PATH against itself in the previous revision.
174251881Speter
175251881Speter   PARENT_DIR_BATON is the directory baton of this directory's parent,
176251881Speter   or NULL if this is the top-level directory of the edit.  ADDED
177251881Speter   indicated if this directory is newly added in this revision.
178251881Speter   Perform all allocations in POOL.  */
179251881Speterstatic struct dir_baton *
180251881Spetermake_dir_baton(const char *path,
181251881Speter               const char *cmp_path,
182251881Speter               svn_revnum_t cmp_rev,
183251881Speter               void *edit_baton,
184251881Speter               void *parent_dir_baton,
185251881Speter               svn_boolean_t added,
186251881Speter               apr_pool_t *pool)
187251881Speter{
188251881Speter  struct edit_baton *eb = edit_baton;
189251881Speter  struct dir_baton *pb = parent_dir_baton;
190251881Speter  struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
191251881Speter  const char *full_path;
192251881Speter
193251881Speter  /* A path relative to nothing?  I don't think so. */
194251881Speter  SVN_ERR_ASSERT_NO_RETURN(!path || pb);
195251881Speter
196251881Speter  /* Construct the full path of this node. */
197251881Speter  if (pb)
198251881Speter    full_path = svn_relpath_join(eb->path, path, pool);
199251881Speter  else
200251881Speter    full_path = apr_pstrdup(pool, eb->path);
201251881Speter
202251881Speter  /* Remove leading slashes from copyfrom paths. */
203251881Speter  if (cmp_path)
204251881Speter    cmp_path = svn_relpath_canonicalize(cmp_path, pool);
205251881Speter
206251881Speter  new_db->edit_baton = eb;
207251881Speter  new_db->parent_dir_baton = pb;
208251881Speter  new_db->path = full_path;
209251881Speter  new_db->cmp_path = cmp_path;
210251881Speter  new_db->cmp_rev = cmp_rev;
211251881Speter  new_db->added = added;
212251881Speter  new_db->written_out = FALSE;
213251881Speter  new_db->deleted_entries = apr_hash_make(pool);
214251881Speter  new_db->pool = pool;
215251881Speter
216251881Speter  return new_db;
217251881Speter}
218251881Speter
219251881Speter
220251881Speter/* This helper is the main "meat" of the editor -- it does all the
221251881Speter   work of writing a node record.
222251881Speter
223251881Speter   Write out a node record for PATH of type KIND under EB->FS_ROOT.
224251881Speter   ACTION describes what is happening to the node (see enum svn_node_action).
225251881Speter   Write record to writable EB->STREAM, using EB->BUFFER to write in chunks.
226251881Speter
227251881Speter   If the node was itself copied, IS_COPY is TRUE and the
228251881Speter   path/revision of the copy source are in CMP_PATH/CMP_REV.  If
229251881Speter   IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
230251881Speter   of a copied subtree.
231251881Speter  */
232251881Speterstatic svn_error_t *
233251881Speterdump_node(struct edit_baton *eb,
234251881Speter          const char *path,
235251881Speter          svn_node_kind_t kind,
236251881Speter          enum svn_node_action action,
237251881Speter          svn_boolean_t is_copy,
238251881Speter          const char *cmp_path,
239251881Speter          svn_revnum_t cmp_rev,
240251881Speter          apr_pool_t *pool)
241251881Speter{
242251881Speter  svn_stringbuf_t *propstring;
243251881Speter  svn_filesize_t content_length = 0;
244251881Speter  apr_size_t len;
245251881Speter  svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
246251881Speter  const char *compare_path = path;
247251881Speter  svn_revnum_t compare_rev = eb->current_rev - 1;
248251881Speter  svn_fs_root_t *compare_root = NULL;
249251881Speter  apr_file_t *delta_file = NULL;
250251881Speter
251251881Speter  /* Maybe validate the path. */
252251881Speter  if (eb->verify || eb->notify_func)
253251881Speter    {
254251881Speter      svn_error_t *err = svn_fs__path_valid(path, pool);
255251881Speter
256251881Speter      if (err)
257251881Speter        {
258251881Speter          if (eb->notify_func)
259251881Speter            {
260251881Speter              char errbuf[512]; /* ### svn_strerror() magic number  */
261251881Speter              svn_repos_notify_t *notify;
262251881Speter              notify = svn_repos_notify_create(svn_repos_notify_warning, pool);
263251881Speter
264251881Speter              notify->warning = svn_repos_notify_warning_invalid_fspath;
265251881Speter              notify->warning_str = apr_psprintf(
266251881Speter                     pool,
267251881Speter                     _("E%06d: While validating fspath '%s': %s"),
268251881Speter                     err->apr_err, path,
269251881Speter                     svn_err_best_message(err, errbuf, sizeof(errbuf)));
270251881Speter
271251881Speter              eb->notify_func(eb->notify_baton, notify, pool);
272251881Speter            }
273251881Speter
274251881Speter          /* Return the error in addition to notifying about it. */
275251881Speter          if (eb->verify)
276251881Speter            return svn_error_trace(err);
277251881Speter          else
278251881Speter            svn_error_clear(err);
279251881Speter        }
280251881Speter    }
281251881Speter
282251881Speter  /* Write out metadata headers for this file node. */
283251881Speter  SVN_ERR(svn_stream_printf(eb->stream, pool,
284251881Speter                            SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
285251881Speter                            path));
286251881Speter  if (kind == svn_node_file)
287251881Speter    SVN_ERR(svn_stream_puts(eb->stream,
288251881Speter                            SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
289251881Speter  else if (kind == svn_node_dir)
290251881Speter    SVN_ERR(svn_stream_puts(eb->stream,
291251881Speter                            SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
292251881Speter
293251881Speter  /* Remove leading slashes from copyfrom paths. */
294251881Speter  if (cmp_path)
295251881Speter    cmp_path = svn_relpath_canonicalize(cmp_path, pool);
296251881Speter
297251881Speter  /* Validate the comparison path/rev. */
298251881Speter  if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
299251881Speter    {
300251881Speter      compare_path = cmp_path;
301251881Speter      compare_rev = cmp_rev;
302251881Speter    }
303251881Speter
304251881Speter  if (action == svn_node_action_change)
305251881Speter    {
306251881Speter      SVN_ERR(svn_stream_puts(eb->stream,
307251881Speter                              SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n"));
308251881Speter
309251881Speter      /* either the text or props changed, or possibly both. */
310251881Speter      SVN_ERR(svn_fs_revision_root(&compare_root,
311251881Speter                                   svn_fs_root_fs(eb->fs_root),
312251881Speter                                   compare_rev, pool));
313251881Speter
314251881Speter      SVN_ERR(svn_fs_props_changed(&must_dump_props,
315251881Speter                                   compare_root, compare_path,
316251881Speter                                   eb->fs_root, path, pool));
317251881Speter      if (kind == svn_node_file)
318251881Speter        SVN_ERR(svn_fs_contents_changed(&must_dump_text,
319251881Speter                                        compare_root, compare_path,
320251881Speter                                        eb->fs_root, path, pool));
321251881Speter    }
322251881Speter  else if (action == svn_node_action_replace)
323251881Speter    {
324251881Speter      if (! is_copy)
325251881Speter        {
326251881Speter          /* a simple delete+add, implied by a single 'replace' action. */
327251881Speter          SVN_ERR(svn_stream_puts(eb->stream,
328251881Speter                                  SVN_REPOS_DUMPFILE_NODE_ACTION
329251881Speter                                  ": replace\n"));
330251881Speter
331251881Speter          /* definitely need to dump all content for a replace. */
332251881Speter          if (kind == svn_node_file)
333251881Speter            must_dump_text = TRUE;
334251881Speter          must_dump_props = TRUE;
335251881Speter        }
336251881Speter      else
337251881Speter        {
338251881Speter          /* more complex:  delete original, then add-with-history.  */
339251881Speter
340251881Speter          /* the path & kind headers have already been printed;  just
341251881Speter             add a delete action, and end the current record.*/
342251881Speter          SVN_ERR(svn_stream_puts(eb->stream,
343251881Speter                                  SVN_REPOS_DUMPFILE_NODE_ACTION
344251881Speter                                  ": delete\n\n"));
345251881Speter
346251881Speter          /* recurse:  print an additional add-with-history record. */
347251881Speter          SVN_ERR(dump_node(eb, path, kind, svn_node_action_add,
348251881Speter                            is_copy, compare_path, compare_rev, pool));
349251881Speter
350251881Speter          /* we can leave this routine quietly now, don't need to dump
351251881Speter             any content;  that was already done in the second record. */
352251881Speter          must_dump_text = FALSE;
353251881Speter          must_dump_props = FALSE;
354251881Speter        }
355251881Speter    }
356251881Speter  else if (action == svn_node_action_delete)
357251881Speter    {
358251881Speter      SVN_ERR(svn_stream_puts(eb->stream,
359251881Speter                              SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n"));
360251881Speter
361251881Speter      /* we can leave this routine quietly now, don't need to dump
362251881Speter         any content. */
363251881Speter      must_dump_text = FALSE;
364251881Speter      must_dump_props = FALSE;
365251881Speter    }
366251881Speter  else if (action == svn_node_action_add)
367251881Speter    {
368251881Speter      SVN_ERR(svn_stream_puts(eb->stream,
369251881Speter                              SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
370251881Speter
371251881Speter      if (! is_copy)
372251881Speter        {
373251881Speter          /* Dump all contents for a simple 'add'. */
374251881Speter          if (kind == svn_node_file)
375251881Speter            must_dump_text = TRUE;
376251881Speter          must_dump_props = TRUE;
377251881Speter        }
378251881Speter      else
379251881Speter        {
380251881Speter          if (!eb->verify && cmp_rev < eb->oldest_dumped_rev
381251881Speter              && eb->notify_func)
382251881Speter            {
383251881Speter              svn_repos_notify_t *notify =
384251881Speter                    svn_repos_notify_create(svn_repos_notify_warning, pool);
385251881Speter
386251881Speter              notify->warning = svn_repos_notify_warning_found_old_reference;
387251881Speter              notify->warning_str = apr_psprintf(
388251881Speter                     pool,
389251881Speter                     _("Referencing data in revision %ld,"
390251881Speter                       " which is older than the oldest"
391251881Speter                       " dumped revision (r%ld).  Loading this dump"
392251881Speter                       " into an empty repository"
393251881Speter                       " will fail."),
394251881Speter                     cmp_rev, eb->oldest_dumped_rev);
395251881Speter              if (eb->found_old_reference)
396251881Speter                *eb->found_old_reference = TRUE;
397251881Speter              eb->notify_func(eb->notify_baton, notify, pool);
398251881Speter            }
399251881Speter
400251881Speter          SVN_ERR(svn_stream_printf(eb->stream, pool,
401251881Speter                                    SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
402251881Speter                                    ": %ld\n"
403251881Speter                                    SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
404251881Speter                                    ": %s\n",
405251881Speter                                    cmp_rev, cmp_path));
406251881Speter
407251881Speter          SVN_ERR(svn_fs_revision_root(&compare_root,
408251881Speter                                       svn_fs_root_fs(eb->fs_root),
409251881Speter                                       compare_rev, pool));
410251881Speter
411251881Speter          /* Need to decide if the copied node had any extra textual or
412251881Speter             property mods as well.  */
413251881Speter          SVN_ERR(svn_fs_props_changed(&must_dump_props,
414251881Speter                                       compare_root, compare_path,
415251881Speter                                       eb->fs_root, path, pool));
416251881Speter          if (kind == svn_node_file)
417251881Speter            {
418251881Speter              svn_checksum_t *checksum;
419251881Speter              const char *hex_digest;
420251881Speter              SVN_ERR(svn_fs_contents_changed(&must_dump_text,
421251881Speter                                              compare_root, compare_path,
422251881Speter                                              eb->fs_root, path, pool));
423251881Speter
424251881Speter              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
425251881Speter                                           compare_root, compare_path,
426251881Speter                                           FALSE, pool));
427251881Speter              hex_digest = svn_checksum_to_cstring(checksum, pool);
428251881Speter              if (hex_digest)
429251881Speter                SVN_ERR(svn_stream_printf(eb->stream, pool,
430251881Speter                                      SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5
431251881Speter                                      ": %s\n", hex_digest));
432251881Speter
433251881Speter              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
434251881Speter                                           compare_root, compare_path,
435251881Speter                                           FALSE, pool));
436251881Speter              hex_digest = svn_checksum_to_cstring(checksum, pool);
437251881Speter              if (hex_digest)
438251881Speter                SVN_ERR(svn_stream_printf(eb->stream, pool,
439251881Speter                                      SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1
440251881Speter                                      ": %s\n", hex_digest));
441251881Speter            }
442251881Speter        }
443251881Speter    }
444251881Speter
445251881Speter  if ((! must_dump_text) && (! must_dump_props))
446251881Speter    {
447251881Speter      /* If we're not supposed to dump text or props, so be it, we can
448251881Speter         just go home.  However, if either one needs to be dumped,
449251881Speter         then our dumpstream format demands that at a *minimum*, we
450251881Speter         see a lone "PROPS-END" as a divider between text and props
451251881Speter         content within the content-block. */
452251881Speter      len = 2;
453251881Speter      return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
454251881Speter    }
455251881Speter
456251881Speter  /*** Start prepping content to dump... ***/
457251881Speter
458251881Speter  /* If we are supposed to dump properties, write out a property
459251881Speter     length header and generate a stringbuf that contains those
460251881Speter     property values here. */
461251881Speter  if (must_dump_props)
462251881Speter    {
463251881Speter      apr_hash_t *prophash, *oldhash = NULL;
464251881Speter      apr_size_t proplen;
465251881Speter      svn_stream_t *propstream;
466251881Speter
467251881Speter      SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
468251881Speter
469251881Speter      /* If this is a partial dump, then issue a warning if we dump mergeinfo
470251881Speter         properties that refer to revisions older than the first revision
471251881Speter         dumped. */
472251881Speter      if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1)
473251881Speter        {
474251881Speter          svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
475251881Speter                                                      SVN_PROP_MERGEINFO);
476251881Speter          if (mergeinfo_str)
477251881Speter            {
478251881Speter              svn_mergeinfo_t mergeinfo, old_mergeinfo;
479251881Speter
480251881Speter              SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str->data,
481251881Speter                                          pool));
482251881Speter              SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
483251881Speter                &old_mergeinfo, mergeinfo,
484251881Speter                eb->oldest_dumped_rev - 1, 0,
485251881Speter                TRUE, pool, pool));
486251881Speter              if (apr_hash_count(old_mergeinfo))
487251881Speter                {
488251881Speter                  svn_repos_notify_t *notify =
489251881Speter                    svn_repos_notify_create(svn_repos_notify_warning, pool);
490251881Speter
491251881Speter                  notify->warning = svn_repos_notify_warning_found_old_mergeinfo;
492251881Speter                  notify->warning_str = apr_psprintf(
493251881Speter                    pool,
494251881Speter                    _("Mergeinfo referencing revision(s) prior "
495251881Speter                      "to the oldest dumped revision (r%ld). "
496251881Speter                      "Loading this dump may result in invalid "
497251881Speter                      "mergeinfo."),
498251881Speter                    eb->oldest_dumped_rev);
499251881Speter
500251881Speter                  if (eb->found_old_mergeinfo)
501251881Speter                    *eb->found_old_mergeinfo = TRUE;
502251881Speter                  eb->notify_func(eb->notify_baton, notify, pool);
503251881Speter                }
504251881Speter            }
505251881Speter        }
506251881Speter
507251881Speter      if (eb->use_deltas && compare_root)
508251881Speter        {
509251881Speter          /* Fetch the old property hash to diff against and output a header
510251881Speter             saying that our property contents are a delta. */
511251881Speter          SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
512251881Speter                                       pool));
513251881Speter          SVN_ERR(svn_stream_puts(eb->stream,
514251881Speter                                  SVN_REPOS_DUMPFILE_PROP_DELTA ": true\n"));
515251881Speter        }
516251881Speter      else
517251881Speter        oldhash = apr_hash_make(pool);
518251881Speter      propstring = svn_stringbuf_create_ensure(0, pool);
519251881Speter      propstream = svn_stream_from_stringbuf(propstring, pool);
520251881Speter      SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream,
521251881Speter                                         "PROPS-END", pool));
522251881Speter      SVN_ERR(svn_stream_close(propstream));
523251881Speter      proplen = propstring->len;
524251881Speter      content_length += proplen;
525251881Speter      SVN_ERR(svn_stream_printf(eb->stream, pool,
526251881Speter                                SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
527251881Speter                                ": %" APR_SIZE_T_FMT "\n", proplen));
528251881Speter    }
529251881Speter
530251881Speter  /* If we are supposed to dump text, write out a text length header
531251881Speter     here, and an MD5 checksum (if available). */
532251881Speter  if (must_dump_text && (kind == svn_node_file))
533251881Speter    {
534251881Speter      svn_checksum_t *checksum;
535251881Speter      const char *hex_digest;
536251881Speter      svn_filesize_t textlen;
537251881Speter
538251881Speter      if (eb->use_deltas)
539251881Speter        {
540251881Speter          /* Compute the text delta now and write it into a temporary
541251881Speter             file, so that we can find its length.  Output a header
542251881Speter             saying our text contents are a delta. */
543251881Speter          SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
544251881Speter                              compare_path, eb->fs_root, path, pool));
545251881Speter          SVN_ERR(svn_stream_puts(eb->stream,
546251881Speter                                  SVN_REPOS_DUMPFILE_TEXT_DELTA ": true\n"));
547251881Speter
548251881Speter          if (compare_root)
549251881Speter            {
550251881Speter              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
551251881Speter                                           compare_root, compare_path,
552251881Speter                                           FALSE, pool));
553251881Speter              hex_digest = svn_checksum_to_cstring(checksum, pool);
554251881Speter              if (hex_digest)
555251881Speter                SVN_ERR(svn_stream_printf(eb->stream, pool,
556251881Speter                                          SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
557251881Speter                                          ": %s\n", hex_digest));
558251881Speter
559251881Speter              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
560251881Speter                                           compare_root, compare_path,
561251881Speter                                           FALSE, pool));
562251881Speter              hex_digest = svn_checksum_to_cstring(checksum, pool);
563251881Speter              if (hex_digest)
564251881Speter                SVN_ERR(svn_stream_printf(eb->stream, pool,
565251881Speter                                      SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1
566251881Speter                                      ": %s\n", hex_digest));
567251881Speter            }
568251881Speter        }
569251881Speter      else
570251881Speter        {
571251881Speter          /* Just fetch the length of the file. */
572251881Speter          SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
573251881Speter        }
574251881Speter
575251881Speter      content_length += textlen;
576251881Speter      SVN_ERR(svn_stream_printf(eb->stream, pool,
577251881Speter                                SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
578251881Speter                                ": %" SVN_FILESIZE_T_FMT "\n", textlen));
579251881Speter
580251881Speter      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
581251881Speter                                   eb->fs_root, path, FALSE, pool));
582251881Speter      hex_digest = svn_checksum_to_cstring(checksum, pool);
583251881Speter      if (hex_digest)
584251881Speter        SVN_ERR(svn_stream_printf(eb->stream, pool,
585251881Speter                                  SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
586251881Speter                                  ": %s\n", hex_digest));
587251881Speter
588251881Speter      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
589251881Speter                                   eb->fs_root, path, FALSE, pool));
590251881Speter      hex_digest = svn_checksum_to_cstring(checksum, pool);
591251881Speter      if (hex_digest)
592251881Speter        SVN_ERR(svn_stream_printf(eb->stream, pool,
593251881Speter                                  SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1
594251881Speter                                  ": %s\n", hex_digest));
595251881Speter    }
596251881Speter
597251881Speter  /* 'Content-length:' is the last header before we dump the content,
598251881Speter     and is the sum of the text and prop contents lengths.  We write
599251881Speter     this only for the benefit of non-Subversion RFC-822 parsers. */
600251881Speter  SVN_ERR(svn_stream_printf(eb->stream, pool,
601251881Speter                            SVN_REPOS_DUMPFILE_CONTENT_LENGTH
602251881Speter                            ": %" SVN_FILESIZE_T_FMT "\n\n",
603251881Speter                            content_length));
604251881Speter
605251881Speter  /* Dump property content if we're supposed to do so. */
606251881Speter  if (must_dump_props)
607251881Speter    {
608251881Speter      len = propstring->len;
609251881Speter      SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len));
610251881Speter    }
611251881Speter
612251881Speter  /* Dump text content */
613251881Speter  if (must_dump_text && (kind == svn_node_file))
614251881Speter    {
615251881Speter      svn_stream_t *contents;
616251881Speter
617251881Speter      if (delta_file)
618251881Speter        {
619251881Speter          /* Make sure to close the underlying file when the stream is
620251881Speter             closed. */
621251881Speter          contents = svn_stream_from_aprfile2(delta_file, FALSE, pool);
622251881Speter        }
623251881Speter      else
624251881Speter        SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
625251881Speter
626251881Speter      SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool),
627251881Speter                               NULL, NULL, pool));
628251881Speter    }
629251881Speter
630251881Speter  len = 2;
631251881Speter  return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
632251881Speter}
633251881Speter
634251881Speter
635251881Speterstatic svn_error_t *
636251881Speteropen_root(void *edit_baton,
637251881Speter          svn_revnum_t base_revision,
638251881Speter          apr_pool_t *pool,
639251881Speter          void **root_baton)
640251881Speter{
641251881Speter  *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
642251881Speter                               edit_baton, NULL, FALSE, pool);
643251881Speter  return SVN_NO_ERROR;
644251881Speter}
645251881Speter
646251881Speter
647251881Speterstatic svn_error_t *
648251881Speterdelete_entry(const char *path,
649251881Speter             svn_revnum_t revision,
650251881Speter             void *parent_baton,
651251881Speter             apr_pool_t *pool)
652251881Speter{
653251881Speter  struct dir_baton *pb = parent_baton;
654251881Speter  const char *mypath = apr_pstrdup(pb->pool, path);
655251881Speter
656251881Speter  /* remember this path needs to be deleted. */
657251881Speter  svn_hash_sets(pb->deleted_entries, mypath, pb);
658251881Speter
659251881Speter  return SVN_NO_ERROR;
660251881Speter}
661251881Speter
662251881Speter
663251881Speterstatic svn_error_t *
664251881Speteradd_directory(const char *path,
665251881Speter              void *parent_baton,
666251881Speter              const char *copyfrom_path,
667251881Speter              svn_revnum_t copyfrom_rev,
668251881Speter              apr_pool_t *pool,
669251881Speter              void **child_baton)
670251881Speter{
671251881Speter  struct dir_baton *pb = parent_baton;
672251881Speter  struct edit_baton *eb = pb->edit_baton;
673251881Speter  void *val;
674251881Speter  svn_boolean_t is_copy = FALSE;
675251881Speter  struct dir_baton *new_db
676251881Speter    = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool);
677251881Speter
678251881Speter  /* This might be a replacement -- is the path already deleted? */
679251881Speter  val = svn_hash_gets(pb->deleted_entries, path);
680251881Speter
681251881Speter  /* Detect an add-with-history. */
682251881Speter  is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
683251881Speter
684251881Speter  /* Dump the node. */
685251881Speter  SVN_ERR(dump_node(eb, path,
686251881Speter                    svn_node_dir,
687251881Speter                    val ? svn_node_action_replace : svn_node_action_add,
688251881Speter                    is_copy,
689251881Speter                    is_copy ? copyfrom_path : NULL,
690251881Speter                    is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
691251881Speter                    pool));
692251881Speter
693251881Speter  if (val)
694251881Speter    /* Delete the path, it's now been dumped. */
695251881Speter    svn_hash_sets(pb->deleted_entries, path, NULL);
696251881Speter
697251881Speter  new_db->written_out = TRUE;
698251881Speter
699251881Speter  *child_baton = new_db;
700251881Speter  return SVN_NO_ERROR;
701251881Speter}
702251881Speter
703251881Speter
704251881Speterstatic svn_error_t *
705251881Speteropen_directory(const char *path,
706251881Speter               void *parent_baton,
707251881Speter               svn_revnum_t base_revision,
708251881Speter               apr_pool_t *pool,
709251881Speter               void **child_baton)
710251881Speter{
711251881Speter  struct dir_baton *pb = parent_baton;
712251881Speter  struct edit_baton *eb = pb->edit_baton;
713251881Speter  struct dir_baton *new_db;
714251881Speter  const char *cmp_path = NULL;
715251881Speter  svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
716251881Speter
717251881Speter  /* If the parent directory has explicit comparison path and rev,
718251881Speter     record the same for this one. */
719251881Speter  if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
720251881Speter    {
721251881Speter      cmp_path = svn_relpath_join(pb->cmp_path,
722251881Speter                                  svn_relpath_basename(path, pool), pool);
723251881Speter      cmp_rev = pb->cmp_rev;
724251881Speter    }
725251881Speter
726251881Speter  new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, FALSE, pool);
727251881Speter  *child_baton = new_db;
728251881Speter  return SVN_NO_ERROR;
729251881Speter}
730251881Speter
731251881Speter
732251881Speterstatic svn_error_t *
733251881Speterclose_directory(void *dir_baton,
734251881Speter                apr_pool_t *pool)
735251881Speter{
736251881Speter  struct dir_baton *db = dir_baton;
737251881Speter  struct edit_baton *eb = db->edit_baton;
738251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
739251881Speter  int i;
740251881Speter  apr_array_header_t *sorted_entries;
741251881Speter
742251881Speter  /* Sort entries lexically instead of as paths. Even though the entries
743251881Speter   * are full paths they're all in the same directory (see comment in struct
744251881Speter   * dir_baton definition). So we really want to sort by basename, in which
745251881Speter   * case the lexical sort function is more efficient. */
746251881Speter  sorted_entries = svn_sort__hash(db->deleted_entries,
747251881Speter                                  svn_sort_compare_items_lexically, pool);
748251881Speter  for (i = 0; i < sorted_entries->nelts; i++)
749251881Speter    {
750251881Speter      const char *path = APR_ARRAY_IDX(sorted_entries, i,
751251881Speter                                       svn_sort__item_t).key;
752251881Speter
753251881Speter      svn_pool_clear(subpool);
754251881Speter
755251881Speter      /* By sending 'svn_node_unknown', the Node-kind: header simply won't
756251881Speter         be written out.  No big deal at all, really.  The loader
757251881Speter         shouldn't care.  */
758251881Speter      SVN_ERR(dump_node(eb, path,
759251881Speter                        svn_node_unknown, svn_node_action_delete,
760251881Speter                        FALSE, NULL, SVN_INVALID_REVNUM, subpool));
761251881Speter    }
762251881Speter
763251881Speter  svn_pool_destroy(subpool);
764251881Speter  return SVN_NO_ERROR;
765251881Speter}
766251881Speter
767251881Speter
768251881Speterstatic svn_error_t *
769251881Speteradd_file(const char *path,
770251881Speter         void *parent_baton,
771251881Speter         const char *copyfrom_path,
772251881Speter         svn_revnum_t copyfrom_rev,
773251881Speter         apr_pool_t *pool,
774251881Speter         void **file_baton)
775251881Speter{
776251881Speter  struct dir_baton *pb = parent_baton;
777251881Speter  struct edit_baton *eb = pb->edit_baton;
778251881Speter  void *val;
779251881Speter  svn_boolean_t is_copy = FALSE;
780251881Speter
781251881Speter  /* This might be a replacement -- is the path already deleted? */
782251881Speter  val = svn_hash_gets(pb->deleted_entries, path);
783251881Speter
784251881Speter  /* Detect add-with-history. */
785251881Speter  is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
786251881Speter
787251881Speter  /* Dump the node. */
788251881Speter  SVN_ERR(dump_node(eb, path,
789251881Speter                    svn_node_file,
790251881Speter                    val ? svn_node_action_replace : svn_node_action_add,
791251881Speter                    is_copy,
792251881Speter                    is_copy ? copyfrom_path : NULL,
793251881Speter                    is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
794251881Speter                    pool));
795251881Speter
796251881Speter  if (val)
797251881Speter    /* delete the path, it's now been dumped. */
798251881Speter    svn_hash_sets(pb->deleted_entries, path, NULL);
799251881Speter
800251881Speter  *file_baton = NULL;  /* muhahahaha */
801251881Speter  return SVN_NO_ERROR;
802251881Speter}
803251881Speter
804251881Speter
805251881Speterstatic svn_error_t *
806251881Speteropen_file(const char *path,
807251881Speter          void *parent_baton,
808251881Speter          svn_revnum_t ancestor_revision,
809251881Speter          apr_pool_t *pool,
810251881Speter          void **file_baton)
811251881Speter{
812251881Speter  struct dir_baton *pb = parent_baton;
813251881Speter  struct edit_baton *eb = pb->edit_baton;
814251881Speter  const char *cmp_path = NULL;
815251881Speter  svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
816251881Speter
817251881Speter  /* If the parent directory has explicit comparison path and rev,
818251881Speter     record the same for this one. */
819251881Speter  if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
820251881Speter    {
821251881Speter      cmp_path = svn_relpath_join(pb->cmp_path,
822251881Speter                                  svn_relpath_basename(path, pool), pool);
823251881Speter      cmp_rev = pb->cmp_rev;
824251881Speter    }
825251881Speter
826251881Speter  SVN_ERR(dump_node(eb, path,
827251881Speter                    svn_node_file, svn_node_action_change,
828251881Speter                    FALSE, cmp_path, cmp_rev, pool));
829251881Speter
830251881Speter  *file_baton = NULL;  /* muhahahaha again */
831251881Speter  return SVN_NO_ERROR;
832251881Speter}
833251881Speter
834251881Speter
835251881Speterstatic svn_error_t *
836251881Speterchange_dir_prop(void *parent_baton,
837251881Speter                const char *name,
838251881Speter                const svn_string_t *value,
839251881Speter                apr_pool_t *pool)
840251881Speter{
841251881Speter  struct dir_baton *db = parent_baton;
842251881Speter  struct edit_baton *eb = db->edit_baton;
843251881Speter
844251881Speter  /* This function is what distinguishes between a directory that is
845251881Speter     opened to merely get somewhere, vs. one that is opened because it
846251881Speter     *actually* changed by itself.  */
847251881Speter  if (! db->written_out)
848251881Speter    {
849251881Speter      SVN_ERR(dump_node(eb, db->path,
850251881Speter                        svn_node_dir, svn_node_action_change,
851251881Speter                        FALSE, db->cmp_path, db->cmp_rev, pool));
852251881Speter      db->written_out = TRUE;
853251881Speter    }
854251881Speter  return SVN_NO_ERROR;
855251881Speter}
856251881Speter
857251881Speterstatic svn_error_t *
858251881Speterfetch_props_func(apr_hash_t **props,
859251881Speter                 void *baton,
860251881Speter                 const char *path,
861251881Speter                 svn_revnum_t base_revision,
862251881Speter                 apr_pool_t *result_pool,
863251881Speter                 apr_pool_t *scratch_pool)
864251881Speter{
865251881Speter  struct edit_baton *eb = baton;
866251881Speter  svn_error_t *err;
867251881Speter  svn_fs_root_t *fs_root;
868251881Speter
869251881Speter  if (!SVN_IS_VALID_REVNUM(base_revision))
870251881Speter    base_revision = eb->current_rev - 1;
871251881Speter
872251881Speter  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
873251881Speter
874251881Speter  err = svn_fs_node_proplist(props, fs_root, path, result_pool);
875251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
876251881Speter    {
877251881Speter      svn_error_clear(err);
878251881Speter      *props = apr_hash_make(result_pool);
879251881Speter      return SVN_NO_ERROR;
880251881Speter    }
881251881Speter  else if (err)
882251881Speter    return svn_error_trace(err);
883251881Speter
884251881Speter  return SVN_NO_ERROR;
885251881Speter}
886251881Speter
887251881Speterstatic svn_error_t *
888251881Speterfetch_kind_func(svn_node_kind_t *kind,
889251881Speter                void *baton,
890251881Speter                const char *path,
891251881Speter                svn_revnum_t base_revision,
892251881Speter                apr_pool_t *scratch_pool)
893251881Speter{
894251881Speter  struct edit_baton *eb = baton;
895251881Speter  svn_fs_root_t *fs_root;
896251881Speter
897251881Speter  if (!SVN_IS_VALID_REVNUM(base_revision))
898251881Speter    base_revision = eb->current_rev - 1;
899251881Speter
900251881Speter  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
901251881Speter
902251881Speter  SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
903251881Speter
904251881Speter  return SVN_NO_ERROR;
905251881Speter}
906251881Speter
907251881Speterstatic svn_error_t *
908251881Speterfetch_base_func(const char **filename,
909251881Speter                void *baton,
910251881Speter                const char *path,
911251881Speter                svn_revnum_t base_revision,
912251881Speter                apr_pool_t *result_pool,
913251881Speter                apr_pool_t *scratch_pool)
914251881Speter{
915251881Speter  struct edit_baton *eb = baton;
916251881Speter  svn_stream_t *contents;
917251881Speter  svn_stream_t *file_stream;
918251881Speter  const char *tmp_filename;
919251881Speter  svn_error_t *err;
920251881Speter  svn_fs_root_t *fs_root;
921251881Speter
922251881Speter  if (!SVN_IS_VALID_REVNUM(base_revision))
923251881Speter    base_revision = eb->current_rev - 1;
924251881Speter
925251881Speter  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
926251881Speter
927251881Speter  err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
928251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
929251881Speter    {
930251881Speter      svn_error_clear(err);
931251881Speter      *filename = NULL;
932251881Speter      return SVN_NO_ERROR;
933251881Speter    }
934251881Speter  else if (err)
935251881Speter    return svn_error_trace(err);
936251881Speter  SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
937251881Speter                                 svn_io_file_del_on_pool_cleanup,
938251881Speter                                 scratch_pool, scratch_pool));
939251881Speter  SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
940251881Speter
941251881Speter  *filename = apr_pstrdup(result_pool, tmp_filename);
942251881Speter
943251881Speter  return SVN_NO_ERROR;
944251881Speter}
945251881Speter
946251881Speter
947251881Speterstatic svn_error_t *
948251881Speterget_dump_editor(const svn_delta_editor_t **editor,
949251881Speter                void **edit_baton,
950251881Speter                svn_fs_t *fs,
951251881Speter                svn_revnum_t to_rev,
952251881Speter                const char *root_path,
953251881Speter                svn_stream_t *stream,
954251881Speter                svn_boolean_t *found_old_reference,
955251881Speter                svn_boolean_t *found_old_mergeinfo,
956251881Speter                svn_error_t *(*custom_close_directory)(void *dir_baton,
957251881Speter                                  apr_pool_t *scratch_pool),
958251881Speter                svn_repos_notify_func_t notify_func,
959251881Speter                void *notify_baton,
960251881Speter                svn_revnum_t oldest_dumped_rev,
961251881Speter                svn_boolean_t use_deltas,
962251881Speter                svn_boolean_t verify,
963251881Speter                apr_pool_t *pool)
964251881Speter{
965251881Speter  /* Allocate an edit baton to be stored in every directory baton.
966251881Speter     Set it up for the directory baton we create here, which is the
967251881Speter     root baton. */
968251881Speter  struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
969251881Speter  svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
970251881Speter  svn_delta_shim_callbacks_t *shim_callbacks =
971251881Speter                                svn_delta_shim_callbacks_default(pool);
972251881Speter
973251881Speter  /* Set up the edit baton. */
974251881Speter  eb->stream = stream;
975251881Speter  eb->notify_func = notify_func;
976251881Speter  eb->notify_baton = notify_baton;
977251881Speter  eb->oldest_dumped_rev = oldest_dumped_rev;
978251881Speter  eb->bufsize = sizeof(eb->buffer);
979251881Speter  eb->path = apr_pstrdup(pool, root_path);
980251881Speter  SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
981251881Speter  eb->fs = fs;
982251881Speter  eb->current_rev = to_rev;
983251881Speter  eb->use_deltas = use_deltas;
984251881Speter  eb->verify = verify;
985251881Speter  eb->found_old_reference = found_old_reference;
986251881Speter  eb->found_old_mergeinfo = found_old_mergeinfo;
987251881Speter
988251881Speter  /* Set up the editor. */
989251881Speter  dump_editor->open_root = open_root;
990251881Speter  dump_editor->delete_entry = delete_entry;
991251881Speter  dump_editor->add_directory = add_directory;
992251881Speter  dump_editor->open_directory = open_directory;
993251881Speter  if (custom_close_directory)
994251881Speter    dump_editor->close_directory = custom_close_directory;
995251881Speter  else
996251881Speter    dump_editor->close_directory = close_directory;
997251881Speter  dump_editor->change_dir_prop = change_dir_prop;
998251881Speter  dump_editor->add_file = add_file;
999251881Speter  dump_editor->open_file = open_file;
1000251881Speter
1001251881Speter  *edit_baton = eb;
1002251881Speter  *editor = dump_editor;
1003251881Speter
1004251881Speter  shim_callbacks->fetch_kind_func = fetch_kind_func;
1005251881Speter  shim_callbacks->fetch_props_func = fetch_props_func;
1006251881Speter  shim_callbacks->fetch_base_func = fetch_base_func;
1007251881Speter  shim_callbacks->fetch_baton = eb;
1008251881Speter
1009251881Speter  SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1010251881Speter                                   NULL, NULL, shim_callbacks, pool, pool));
1011251881Speter
1012251881Speter  return SVN_NO_ERROR;
1013251881Speter}
1014251881Speter
1015251881Speter/*----------------------------------------------------------------------*/
1016251881Speter
1017251881Speter/** The main dumping routine, svn_repos_dump_fs. **/
1018251881Speter
1019251881Speter
1020251881Speter/* Helper for svn_repos_dump_fs.
1021251881Speter
1022251881Speter   Write a revision record of REV in FS to writable STREAM, using POOL.
1023251881Speter */
1024251881Speterstatic svn_error_t *
1025251881Speterwrite_revision_record(svn_stream_t *stream,
1026251881Speter                      svn_fs_t *fs,
1027251881Speter                      svn_revnum_t rev,
1028251881Speter                      apr_pool_t *pool)
1029251881Speter{
1030251881Speter  apr_size_t len;
1031251881Speter  apr_hash_t *props;
1032251881Speter  svn_stringbuf_t *encoded_prophash;
1033251881Speter  apr_time_t timetemp;
1034251881Speter  svn_string_t *datevalue;
1035251881Speter  svn_stream_t *propstream;
1036251881Speter
1037251881Speter  /* Read the revision props even if we're aren't going to dump
1038251881Speter     them for verification purposes */
1039251881Speter  SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
1040251881Speter
1041251881Speter  /* Run revision date properties through the time conversion to
1042251881Speter     canonicalize them. */
1043251881Speter  /* ### Remove this when it is no longer needed for sure. */
1044251881Speter  datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
1045251881Speter  if (datevalue)
1046251881Speter    {
1047251881Speter      SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
1048251881Speter      datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
1049251881Speter                                    pool);
1050251881Speter      svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
1051251881Speter    }
1052251881Speter
1053251881Speter  encoded_prophash = svn_stringbuf_create_ensure(0, pool);
1054251881Speter  propstream = svn_stream_from_stringbuf(encoded_prophash, pool);
1055251881Speter  SVN_ERR(svn_hash_write2(props, propstream, "PROPS-END", pool));
1056251881Speter  SVN_ERR(svn_stream_close(propstream));
1057251881Speter
1058251881Speter  /* ### someday write a revision-content-checksum */
1059251881Speter
1060251881Speter  SVN_ERR(svn_stream_printf(stream, pool,
1061251881Speter                            SVN_REPOS_DUMPFILE_REVISION_NUMBER
1062251881Speter                            ": %ld\n", rev));
1063251881Speter  SVN_ERR(svn_stream_printf(stream, pool,
1064251881Speter                            SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
1065251881Speter                            ": %" APR_SIZE_T_FMT "\n",
1066251881Speter                            encoded_prophash->len));
1067251881Speter
1068251881Speter  /* Write out a regular Content-length header for the benefit of
1069251881Speter     non-Subversion RFC-822 parsers. */
1070251881Speter  SVN_ERR(svn_stream_printf(stream, pool,
1071251881Speter                            SVN_REPOS_DUMPFILE_CONTENT_LENGTH
1072251881Speter                            ": %" APR_SIZE_T_FMT "\n\n",
1073251881Speter                            encoded_prophash->len));
1074251881Speter
1075251881Speter  len = encoded_prophash->len;
1076251881Speter  SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len));
1077251881Speter
1078251881Speter  len = 1;
1079251881Speter  return svn_stream_write(stream, "\n", &len);
1080251881Speter}
1081251881Speter
1082251881Speter
1083251881Speter
1084251881Speter/* The main dumper. */
1085251881Spetersvn_error_t *
1086251881Spetersvn_repos_dump_fs3(svn_repos_t *repos,
1087251881Speter                   svn_stream_t *stream,
1088251881Speter                   svn_revnum_t start_rev,
1089251881Speter                   svn_revnum_t end_rev,
1090251881Speter                   svn_boolean_t incremental,
1091251881Speter                   svn_boolean_t use_deltas,
1092251881Speter                   svn_repos_notify_func_t notify_func,
1093251881Speter                   void *notify_baton,
1094251881Speter                   svn_cancel_func_t cancel_func,
1095251881Speter                   void *cancel_baton,
1096251881Speter                   apr_pool_t *pool)
1097251881Speter{
1098251881Speter  const svn_delta_editor_t *dump_editor;
1099251881Speter  void *dump_edit_baton = NULL;
1100251881Speter  svn_revnum_t i;
1101251881Speter  svn_fs_t *fs = svn_repos_fs(repos);
1102251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
1103251881Speter  svn_revnum_t youngest;
1104251881Speter  const char *uuid;
1105251881Speter  int version;
1106251881Speter  svn_boolean_t found_old_reference = FALSE;
1107251881Speter  svn_boolean_t found_old_mergeinfo = FALSE;
1108251881Speter  svn_repos_notify_t *notify;
1109251881Speter
1110251881Speter  /* Determine the current youngest revision of the filesystem. */
1111251881Speter  SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1112251881Speter
1113251881Speter  /* Use default vals if necessary. */
1114251881Speter  if (! SVN_IS_VALID_REVNUM(start_rev))
1115251881Speter    start_rev = 0;
1116251881Speter  if (! SVN_IS_VALID_REVNUM(end_rev))
1117251881Speter    end_rev = youngest;
1118251881Speter  if (! stream)
1119251881Speter    stream = svn_stream_empty(pool);
1120251881Speter
1121251881Speter  /* Validate the revisions. */
1122251881Speter  if (start_rev > end_rev)
1123251881Speter    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1124251881Speter                             _("Start revision %ld"
1125251881Speter                               " is greater than end revision %ld"),
1126251881Speter                             start_rev, end_rev);
1127251881Speter  if (end_rev > youngest)
1128251881Speter    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1129251881Speter                             _("End revision %ld is invalid "
1130251881Speter                               "(youngest revision is %ld)"),
1131251881Speter                             end_rev, youngest);
1132251881Speter  if ((start_rev == 0) && incremental)
1133251881Speter    incremental = FALSE; /* revision 0 looks the same regardless of
1134251881Speter                            whether or not this is an incremental
1135251881Speter                            dump, so just simplify things. */
1136251881Speter
1137251881Speter  /* Write out the UUID. */
1138251881Speter  SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
1139251881Speter
1140251881Speter  /* If we're not using deltas, use the previous version, for
1141251881Speter     compatibility with svn 1.0.x. */
1142251881Speter  version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
1143251881Speter  if (!use_deltas)
1144251881Speter    version--;
1145251881Speter
1146251881Speter  /* Write out "general" metadata for the dumpfile.  In this case, a
1147251881Speter     magic header followed by a dumpfile format version. */
1148251881Speter  SVN_ERR(svn_stream_printf(stream, pool,
1149251881Speter                            SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
1150251881Speter                            version));
1151251881Speter  SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
1152251881Speter                            ": %s\n\n", uuid));
1153251881Speter
1154251881Speter  /* Create a notify object that we can reuse in the loop. */
1155251881Speter  if (notify_func)
1156251881Speter    notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
1157251881Speter                                     pool);
1158251881Speter
1159251881Speter  /* Main loop:  we're going to dump revision i.  */
1160251881Speter  for (i = start_rev; i <= end_rev; i++)
1161251881Speter    {
1162251881Speter      svn_revnum_t from_rev, to_rev;
1163251881Speter      svn_fs_root_t *to_root;
1164251881Speter      svn_boolean_t use_deltas_for_rev;
1165251881Speter
1166251881Speter      svn_pool_clear(subpool);
1167251881Speter
1168251881Speter      /* Check for cancellation. */
1169251881Speter      if (cancel_func)
1170251881Speter        SVN_ERR(cancel_func(cancel_baton));
1171251881Speter
1172251881Speter      /* Special-case the initial revision dump: it needs to contain
1173251881Speter         *all* nodes, because it's the foundation of all future
1174251881Speter         revisions in the dumpfile. */
1175251881Speter      if ((i == start_rev) && (! incremental))
1176251881Speter        {
1177251881Speter          /* Special-special-case a dump of revision 0. */
1178251881Speter          if (i == 0)
1179251881Speter            {
1180251881Speter              /* Just write out the one revision 0 record and move on.
1181251881Speter                 The parser might want to use its properties. */
1182251881Speter              SVN_ERR(write_revision_record(stream, fs, 0, subpool));
1183251881Speter              to_rev = 0;
1184251881Speter              goto loop_end;
1185251881Speter            }
1186251881Speter
1187251881Speter          /* Compare START_REV to revision 0, so that everything
1188251881Speter             appears to be added.  */
1189251881Speter          from_rev = 0;
1190251881Speter          to_rev = i;
1191251881Speter        }
1192251881Speter      else
1193251881Speter        {
1194251881Speter          /* In the normal case, we want to compare consecutive revs. */
1195251881Speter          from_rev = i - 1;
1196251881Speter          to_rev = i;
1197251881Speter        }
1198251881Speter
1199251881Speter      /* Write the revision record. */
1200251881Speter      SVN_ERR(write_revision_record(stream, fs, to_rev, subpool));
1201251881Speter
1202251881Speter      /* Fetch the editor which dumps nodes to a file.  Regardless of
1203251881Speter         what we've been told, don't use deltas for the first rev of a
1204251881Speter         non-incremental dump. */
1205251881Speter      use_deltas_for_rev = use_deltas && (incremental || i != start_rev);
1206251881Speter      SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, to_rev,
1207251881Speter                              "", stream, &found_old_reference,
1208251881Speter                              &found_old_mergeinfo, NULL,
1209251881Speter                              notify_func, notify_baton,
1210251881Speter                              start_rev, use_deltas_for_rev, FALSE, subpool));
1211251881Speter
1212251881Speter      /* Drive the editor in one way or another. */
1213251881Speter      SVN_ERR(svn_fs_revision_root(&to_root, fs, to_rev, subpool));
1214251881Speter
1215251881Speter      /* If this is the first revision of a non-incremental dump,
1216251881Speter         we're in for a full tree dump.  Otherwise, we want to simply
1217251881Speter         replay the revision.  */
1218251881Speter      if ((i == start_rev) && (! incremental))
1219251881Speter        {
1220251881Speter          svn_fs_root_t *from_root;
1221251881Speter          SVN_ERR(svn_fs_revision_root(&from_root, fs, from_rev, subpool));
1222251881Speter          SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
1223251881Speter                                       to_root, "",
1224251881Speter                                       dump_editor, dump_edit_baton,
1225251881Speter                                       NULL,
1226251881Speter                                       NULL,
1227251881Speter                                       FALSE, /* don't send text-deltas */
1228251881Speter                                       svn_depth_infinity,
1229251881Speter                                       FALSE, /* don't send entry props */
1230251881Speter                                       FALSE, /* don't ignore ancestry */
1231251881Speter                                       subpool));
1232251881Speter        }
1233251881Speter      else
1234251881Speter        {
1235251881Speter          SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
1236251881Speter                                    dump_editor, dump_edit_baton,
1237251881Speter                                    NULL, NULL, subpool));
1238251881Speter
1239251881Speter          /* While our editor close_edit implementation is a no-op, we still
1240251881Speter             do this for completeness. */
1241251881Speter          SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool));
1242251881Speter        }
1243251881Speter
1244251881Speter    loop_end:
1245251881Speter      if (notify_func)
1246251881Speter        {
1247251881Speter          notify->revision = to_rev;
1248251881Speter          notify_func(notify_baton, notify, subpool);
1249251881Speter        }
1250251881Speter    }
1251251881Speter
1252251881Speter  if (notify_func)
1253251881Speter    {
1254251881Speter      /* Did we issue any warnings about references to revisions older than
1255251881Speter         the oldest dumped revision?  If so, then issue a final generic
1256251881Speter         warning, since the inline warnings already issued might easily be
1257251881Speter         missed. */
1258251881Speter
1259251881Speter      notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool);
1260251881Speter      notify_func(notify_baton, notify, subpool);
1261251881Speter
1262251881Speter      if (found_old_reference)
1263251881Speter        {
1264251881Speter          notify = svn_repos_notify_create(svn_repos_notify_warning, subpool);
1265251881Speter
1266251881Speter          notify->warning = svn_repos_notify_warning_found_old_reference;
1267251881Speter          notify->warning_str = _("The range of revisions dumped "
1268251881Speter                                  "contained references to "
1269251881Speter                                  "copy sources outside that "
1270251881Speter                                  "range.");
1271251881Speter          notify_func(notify_baton, notify, subpool);
1272251881Speter        }
1273251881Speter
1274251881Speter      /* Ditto if we issued any warnings about old revisions referenced
1275251881Speter         in dumped mergeinfo. */
1276251881Speter      if (found_old_mergeinfo)
1277251881Speter        {
1278251881Speter          notify = svn_repos_notify_create(svn_repos_notify_warning, subpool);
1279251881Speter
1280251881Speter          notify->warning = svn_repos_notify_warning_found_old_mergeinfo;
1281251881Speter          notify->warning_str = _("The range of revisions dumped "
1282251881Speter                                  "contained mergeinfo "
1283251881Speter                                  "which reference revisions outside "
1284251881Speter                                  "that range.");
1285251881Speter          notify_func(notify_baton, notify, subpool);
1286251881Speter        }
1287251881Speter    }
1288251881Speter
1289251881Speter  svn_pool_destroy(subpool);
1290251881Speter
1291251881Speter  return SVN_NO_ERROR;
1292251881Speter}
1293251881Speter
1294251881Speter
1295251881Speter/*----------------------------------------------------------------------*/
1296251881Speter
1297251881Speter/* verify, based on dump */
1298251881Speter
1299251881Speter
1300251881Speter/* Creating a new revision that changes /A/B/E/bravo means creating new
1301251881Speter   directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
1302251881Speter   each entry not changed in the new revision a link back to the entry in a
1303251881Speter   previous revision.  svn_repos_replay()ing a revision does not verify that
1304251881Speter   those links are correct.
1305251881Speter
1306251881Speter   For paths actually changed in the revision we verify, we get directory
1307251881Speter   contents or file length twice: once in the dump editor, and once here.
1308251881Speter   We could create a new verify baton, store in it the changed paths, and
1309251881Speter   skip those here, but that means building an entire wrapper editor and
1310251881Speter   managing two levels of batons.  The impact from checking these entries
1311251881Speter   twice should be minimal, while the code to avoid it is not.
1312251881Speter*/
1313251881Speter
1314251881Speterstatic svn_error_t *
1315251881Speterverify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
1316251881Speter                       void *val, apr_pool_t *pool)
1317251881Speter{
1318251881Speter  struct dir_baton *db = baton;
1319251881Speter  svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
1320251881Speter  char *path = svn_relpath_join(db->path, (const char *)key, pool);
1321251881Speter  apr_hash_t *dirents;
1322251881Speter  svn_filesize_t len;
1323251881Speter
1324251881Speter  /* since we can't access the directory entries directly by their ID,
1325251881Speter     we need to navigate from the FS_ROOT to them (relatively expensive
1326251881Speter     because we may start at a never rev than the last change to node). */
1327251881Speter  switch (dirent->kind) {
1328251881Speter  case svn_node_dir:
1329251881Speter    /* Getting this directory's contents is enough to ensure that our
1330251881Speter       link to it is correct. */
1331251881Speter    SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, path, pool));
1332251881Speter    break;
1333251881Speter  case svn_node_file:
1334251881Speter    /* Getting this file's size is enough to ensure that our link to it
1335251881Speter       is correct. */
1336251881Speter    SVN_ERR(svn_fs_file_length(&len, db->edit_baton->fs_root, path, pool));
1337251881Speter    break;
1338251881Speter  default:
1339251881Speter    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1340251881Speter                             _("Unexpected node kind %d for '%s'"),
1341251881Speter                             dirent->kind, path);
1342251881Speter  }
1343251881Speter
1344251881Speter  return SVN_NO_ERROR;
1345251881Speter}
1346251881Speter
1347251881Speterstatic svn_error_t *
1348251881Speterverify_close_directory(void *dir_baton,
1349251881Speter                apr_pool_t *pool)
1350251881Speter{
1351251881Speter  struct dir_baton *db = dir_baton;
1352251881Speter  apr_hash_t *dirents;
1353251881Speter  SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root,
1354251881Speter                             db->path, pool));
1355251881Speter  SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
1356251881Speter                            dir_baton, pool));
1357251881Speter  return close_directory(dir_baton, pool);
1358251881Speter}
1359251881Speter
1360251881Speter/* Baton type used for forwarding notifications from FS API to REPOS API. */
1361251881Speterstruct verify_fs2_notify_func_baton_t
1362251881Speter{
1363251881Speter   /* notification function to call (must not be NULL) */
1364251881Speter   svn_repos_notify_func_t notify_func;
1365251881Speter
1366251881Speter   /* baton to use for it */
1367251881Speter   void *notify_baton;
1368251881Speter
1369251881Speter   /* type of notification to send (we will simply plug in the revision) */
1370251881Speter   svn_repos_notify_t *notify;
1371251881Speter};
1372251881Speter
1373251881Speter/* Forward the notification to BATON. */
1374251881Speterstatic void
1375251881Speterverify_fs2_notify_func(svn_revnum_t revision,
1376251881Speter                       void *baton,
1377251881Speter                       apr_pool_t *pool)
1378251881Speter{
1379251881Speter  struct verify_fs2_notify_func_baton_t *notify_baton = baton;
1380251881Speter
1381251881Speter  notify_baton->notify->revision = revision;
1382251881Speter  notify_baton->notify_func(notify_baton->notify_baton,
1383251881Speter                            notify_baton->notify, pool);
1384251881Speter}
1385251881Speter
1386251881Spetersvn_error_t *
1387251881Spetersvn_repos_verify_fs2(svn_repos_t *repos,
1388251881Speter                     svn_revnum_t start_rev,
1389251881Speter                     svn_revnum_t end_rev,
1390251881Speter                     svn_repos_notify_func_t notify_func,
1391251881Speter                     void *notify_baton,
1392251881Speter                     svn_cancel_func_t cancel_func,
1393251881Speter                     void *cancel_baton,
1394251881Speter                     apr_pool_t *pool)
1395251881Speter{
1396251881Speter  svn_fs_t *fs = svn_repos_fs(repos);
1397251881Speter  svn_revnum_t youngest;
1398251881Speter  svn_revnum_t rev;
1399251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
1400251881Speter  svn_repos_notify_t *notify;
1401251881Speter  svn_fs_progress_notify_func_t verify_notify = NULL;
1402251881Speter  struct verify_fs2_notify_func_baton_t *verify_notify_baton = NULL;
1403251881Speter
1404251881Speter  /* Determine the current youngest revision of the filesystem. */
1405251881Speter  SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1406251881Speter
1407251881Speter  /* Use default vals if necessary. */
1408251881Speter  if (! SVN_IS_VALID_REVNUM(start_rev))
1409251881Speter    start_rev = 0;
1410251881Speter  if (! SVN_IS_VALID_REVNUM(end_rev))
1411251881Speter    end_rev = youngest;
1412251881Speter
1413251881Speter  /* Validate the revisions. */
1414251881Speter  if (start_rev > end_rev)
1415251881Speter    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1416251881Speter                             _("Start revision %ld"
1417251881Speter                               " is greater than end revision %ld"),
1418251881Speter                             start_rev, end_rev);
1419251881Speter  if (end_rev > youngest)
1420251881Speter    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1421251881Speter                             _("End revision %ld is invalid "
1422251881Speter                               "(youngest revision is %ld)"),
1423251881Speter                             end_rev, youngest);
1424251881Speter
1425251881Speter  /* Create a notify object that we can reuse within the loop and a
1426251881Speter     forwarding structure for notifications from inside svn_fs_verify(). */
1427251881Speter  if (notify_func)
1428251881Speter    {
1429251881Speter      notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end,
1430251881Speter                                       pool);
1431251881Speter
1432251881Speter      verify_notify = verify_fs2_notify_func;
1433251881Speter      verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
1434251881Speter      verify_notify_baton->notify_func = notify_func;
1435251881Speter      verify_notify_baton->notify_baton = notify_baton;
1436251881Speter      verify_notify_baton->notify
1437251881Speter        = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool);
1438251881Speter    }
1439251881Speter
1440251881Speter  /* Verify global metadata and backend-specific data first. */
1441251881Speter  SVN_ERR(svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
1442251881Speter                        start_rev, end_rev,
1443251881Speter                        verify_notify, verify_notify_baton,
1444251881Speter                        cancel_func, cancel_baton, pool));
1445251881Speter
1446251881Speter  for (rev = start_rev; rev <= end_rev; rev++)
1447251881Speter    {
1448251881Speter      const svn_delta_editor_t *dump_editor;
1449251881Speter      void *dump_edit_baton;
1450251881Speter      const svn_delta_editor_t *cancel_editor;
1451251881Speter      void *cancel_edit_baton;
1452251881Speter      svn_fs_root_t *to_root;
1453251881Speter      apr_hash_t *props;
1454251881Speter
1455251881Speter      svn_pool_clear(iterpool);
1456251881Speter
1457251881Speter      /* Get cancellable dump editor, but with our close_directory handler. */
1458251881Speter      SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
1459251881Speter                              fs, rev, "",
1460251881Speter                              svn_stream_empty(iterpool),
1461251881Speter                              NULL, NULL,
1462251881Speter                              verify_close_directory,
1463251881Speter                              notify_func, notify_baton,
1464251881Speter                              start_rev,
1465251881Speter                              FALSE, TRUE, /* use_deltas, verify */
1466251881Speter                              iterpool));
1467251881Speter      SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1468251881Speter                                                dump_editor, dump_edit_baton,
1469251881Speter                                                &cancel_editor,
1470251881Speter                                                &cancel_edit_baton,
1471251881Speter                                                iterpool));
1472251881Speter
1473251881Speter      SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool));
1474251881Speter      SVN_ERR(svn_fs_verify_root(to_root, iterpool));
1475251881Speter
1476251881Speter      SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
1477251881Speter                                cancel_editor, cancel_edit_baton,
1478251881Speter                                NULL, NULL, iterpool));
1479251881Speter      /* While our editor close_edit implementation is a no-op, we still
1480251881Speter         do this for completeness. */
1481251881Speter      SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, iterpool));
1482251881Speter
1483251881Speter      SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, iterpool));
1484251881Speter
1485251881Speter      if (notify_func)
1486251881Speter        {
1487251881Speter          notify->revision = rev;
1488251881Speter          notify_func(notify_baton, notify, iterpool);
1489251881Speter        }
1490251881Speter    }
1491251881Speter
1492251881Speter  /* We're done. */
1493251881Speter  if (notify_func)
1494251881Speter    {
1495251881Speter      notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool);
1496251881Speter      notify_func(notify_baton, notify, iterpool);
1497251881Speter    }
1498251881Speter
1499251881Speter  /* Per-backend verification. */
1500251881Speter  svn_pool_destroy(iterpool);
1501251881Speter
1502251881Speter  return SVN_NO_ERROR;
1503251881Speter}
1504