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