]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_repos/dump.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_repos / dump.c
1 /* dump.c --- writing filesystem contents into a portable 'dumpfile' format.
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22
23
24 #include "svn_private_config.h"
25 #include "svn_pools.h"
26 #include "svn_error.h"
27 #include "svn_fs.h"
28 #include "svn_hash.h"
29 #include "svn_iter.h"
30 #include "svn_repos.h"
31 #include "svn_string.h"
32 #include "svn_dirent_uri.h"
33 #include "svn_path.h"
34 #include "svn_time.h"
35 #include "svn_checksum.h"
36 #include "svn_props.h"
37 #include "svn_sorts.h"
38
39 #include "private/svn_mergeinfo_private.h"
40 #include "private/svn_fs_private.h"
41
42 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
43
44 /*----------------------------------------------------------------------*/
45 \f
46
47
48 /* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and
49    store it into a new temporary file *TEMPFILE.  OLDROOT may be NULL,
50    in which case the delta will be computed against an empty file, as
51    per the svn_fs_get_file_delta_stream docstring.  Record the length
52    of the temporary file in *LEN, and rewind the file before
53    returning. */
54 static svn_error_t *
55 store_delta(apr_file_t **tempfile, svn_filesize_t *len,
56             svn_fs_root_t *oldroot, const char *oldpath,
57             svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool)
58 {
59   svn_stream_t *temp_stream;
60   apr_off_t offset = 0;
61   svn_txdelta_stream_t *delta_stream;
62   svn_txdelta_window_handler_t wh;
63   void *whb;
64
65   /* Create a temporary file and open a stream to it. Note that we need
66      the file handle in order to rewind it. */
67   SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL,
68                                    svn_io_file_del_on_pool_cleanup,
69                                    pool, pool));
70   temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool);
71
72   /* Compute the delta and send it to the temporary file. */
73   SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath,
74                                        newroot, newpath, pool));
75   svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0,
76                           SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
77   SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool));
78
79   /* Get the length of the temporary file and rewind it. */
80   SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool));
81   *len = offset;
82   offset = 0;
83   return svn_io_file_seek(*tempfile, APR_SET, &offset, pool);
84 }
85
86
87 /*----------------------------------------------------------------------*/
88 \f
89 /** An editor which dumps node-data in 'dumpfile format' to a file. **/
90
91 /* Look, mom!  No file batons! */
92
93 struct edit_baton
94 {
95   /* The relpath which implicitly prepends all full paths coming into
96      this editor.  This will almost always be "".  */
97   const char *path;
98
99   /* The stream to dump to. */
100   svn_stream_t *stream;
101
102   /* Send feedback here, if non-NULL */
103   svn_repos_notify_func_t notify_func;
104   void *notify_baton;
105
106   /* The fs revision root, so we can read the contents of paths. */
107   svn_fs_root_t *fs_root;
108   svn_revnum_t current_rev;
109
110   /* The fs, so we can grab historic information if needed. */
111   svn_fs_t *fs;
112
113   /* True if dumped nodes should output deltas instead of full text. */
114   svn_boolean_t use_deltas;
115
116   /* True if this "dump" is in fact a verify. */
117   svn_boolean_t verify;
118
119   /* The first revision dumped in this dumpstream. */
120   svn_revnum_t oldest_dumped_rev;
121
122   /* If not NULL, set to true if any references to revisions older than
123      OLDEST_DUMPED_REV were found in the dumpstream. */
124   svn_boolean_t *found_old_reference;
125
126   /* If not NULL, set to true if any mergeinfo was dumped which contains
127      revisions older than OLDEST_DUMPED_REV. */
128   svn_boolean_t *found_old_mergeinfo;
129
130   /* reusable buffer for writing file contents */
131   char buffer[SVN__STREAM_CHUNK_SIZE];
132   apr_size_t bufsize;
133 };
134
135 struct dir_baton
136 {
137   struct edit_baton *edit_baton;
138   struct dir_baton *parent_dir_baton;
139
140   /* is this directory a new addition to this revision? */
141   svn_boolean_t added;
142
143   /* has this directory been written to the output stream? */
144   svn_boolean_t written_out;
145
146   /* the repository relpath associated with this directory */
147   const char *path;
148
149   /* The comparison repository relpath and revision of this directory.
150      If both of these are valid, use them as a source against which to
151      compare the directory instead of the default comparison source of
152      PATH in the previous revision. */
153   const char *cmp_path;
154   svn_revnum_t cmp_rev;
155
156   /* hash of paths that need to be deleted, though some -might- be
157      replaced.  maps const char * paths to this dir_baton.  (they're
158      full paths, because that's what the editor driver gives us.  but
159      really, they're all within this directory.) */
160   apr_hash_t *deleted_entries;
161
162   /* pool to be used for deleting the hash items */
163   apr_pool_t *pool;
164 };
165
166
167 /* Make a directory baton to represent the directory was path
168    (relative to EDIT_BATON's path) is PATH.
169
170    CMP_PATH/CMP_REV are the path/revision against which this directory
171    should be compared for changes.  If either is omitted (NULL for the
172    path, SVN_INVALID_REVNUM for the rev), just compare this directory
173    PATH against itself in the previous revision.
174
175    PARENT_DIR_BATON is the directory baton of this directory's parent,
176    or NULL if this is the top-level directory of the edit.  ADDED
177    indicated if this directory is newly added in this revision.
178    Perform all allocations in POOL.  */
179 static struct dir_baton *
180 make_dir_baton(const char *path,
181                const char *cmp_path,
182                svn_revnum_t cmp_rev,
183                void *edit_baton,
184                void *parent_dir_baton,
185                svn_boolean_t added,
186                apr_pool_t *pool)
187 {
188   struct edit_baton *eb = edit_baton;
189   struct dir_baton *pb = parent_dir_baton;
190   struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
191   const char *full_path;
192
193   /* A path relative to nothing?  I don't think so. */
194   SVN_ERR_ASSERT_NO_RETURN(!path || pb);
195
196   /* Construct the full path of this node. */
197   if (pb)
198     full_path = svn_relpath_join(eb->path, path, pool);
199   else
200     full_path = apr_pstrdup(pool, eb->path);
201
202   /* Remove leading slashes from copyfrom paths. */
203   if (cmp_path)
204     cmp_path = svn_relpath_canonicalize(cmp_path, pool);
205
206   new_db->edit_baton = eb;
207   new_db->parent_dir_baton = pb;
208   new_db->path = full_path;
209   new_db->cmp_path = cmp_path;
210   new_db->cmp_rev = cmp_rev;
211   new_db->added = added;
212   new_db->written_out = FALSE;
213   new_db->deleted_entries = apr_hash_make(pool);
214   new_db->pool = pool;
215
216   return new_db;
217 }
218
219
220 /* This helper is the main "meat" of the editor -- it does all the
221    work of writing a node record.
222
223    Write out a node record for PATH of type KIND under EB->FS_ROOT.
224    ACTION describes what is happening to the node (see enum svn_node_action).
225    Write record to writable EB->STREAM, using EB->BUFFER to write in chunks.
226
227    If the node was itself copied, IS_COPY is TRUE and the
228    path/revision of the copy source are in CMP_PATH/CMP_REV.  If
229    IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
230    of a copied subtree.
231   */
232 static svn_error_t *
233 dump_node(struct edit_baton *eb,
234           const char *path,
235           svn_node_kind_t kind,
236           enum svn_node_action action,
237           svn_boolean_t is_copy,
238           const char *cmp_path,
239           svn_revnum_t cmp_rev,
240           apr_pool_t *pool)
241 {
242   svn_stringbuf_t *propstring;
243   svn_filesize_t content_length = 0;
244   apr_size_t len;
245   svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
246   const char *compare_path = path;
247   svn_revnum_t compare_rev = eb->current_rev - 1;
248   svn_fs_root_t *compare_root = NULL;
249   apr_file_t *delta_file = NULL;
250
251   /* Maybe validate the path. */
252   if (eb->verify || eb->notify_func)
253     {
254       svn_error_t *err = svn_fs__path_valid(path, pool);
255
256       if (err)
257         {
258           if (eb->notify_func)
259             {
260               char errbuf[512]; /* ### svn_strerror() magic number  */
261               svn_repos_notify_t *notify;
262               notify = svn_repos_notify_create(svn_repos_notify_warning, pool);
263
264               notify->warning = svn_repos_notify_warning_invalid_fspath;
265               notify->warning_str = apr_psprintf(
266                      pool,
267                      _("E%06d: While validating fspath '%s': %s"),
268                      err->apr_err, path,
269                      svn_err_best_message(err, errbuf, sizeof(errbuf)));
270
271               eb->notify_func(eb->notify_baton, notify, pool);
272             }
273
274           /* Return the error in addition to notifying about it. */
275           if (eb->verify)
276             return svn_error_trace(err);
277           else
278             svn_error_clear(err);
279         }
280     }
281
282   /* Write out metadata headers for this file node. */
283   SVN_ERR(svn_stream_printf(eb->stream, pool,
284                             SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
285                             path));
286   if (kind == svn_node_file)
287     SVN_ERR(svn_stream_puts(eb->stream,
288                             SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
289   else if (kind == svn_node_dir)
290     SVN_ERR(svn_stream_puts(eb->stream,
291                             SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
292
293   /* Remove leading slashes from copyfrom paths. */
294   if (cmp_path)
295     cmp_path = svn_relpath_canonicalize(cmp_path, pool);
296
297   /* Validate the comparison path/rev. */
298   if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
299     {
300       compare_path = cmp_path;
301       compare_rev = cmp_rev;
302     }
303
304   if (action == svn_node_action_change)
305     {
306       SVN_ERR(svn_stream_puts(eb->stream,
307                               SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n"));
308
309       /* either the text or props changed, or possibly both. */
310       SVN_ERR(svn_fs_revision_root(&compare_root,
311                                    svn_fs_root_fs(eb->fs_root),
312                                    compare_rev, pool));
313
314       SVN_ERR(svn_fs_props_changed(&must_dump_props,
315                                    compare_root, compare_path,
316                                    eb->fs_root, path, pool));
317       if (kind == svn_node_file)
318         SVN_ERR(svn_fs_contents_changed(&must_dump_text,
319                                         compare_root, compare_path,
320                                         eb->fs_root, path, pool));
321     }
322   else if (action == svn_node_action_replace)
323     {
324       if (! is_copy)
325         {
326           /* a simple delete+add, implied by a single 'replace' action. */
327           SVN_ERR(svn_stream_puts(eb->stream,
328                                   SVN_REPOS_DUMPFILE_NODE_ACTION
329                                   ": replace\n"));
330
331           /* definitely need to dump all content for a replace. */
332           if (kind == svn_node_file)
333             must_dump_text = TRUE;
334           must_dump_props = TRUE;
335         }
336       else
337         {
338           /* more complex:  delete original, then add-with-history.  */
339
340           /* the path & kind headers have already been printed;  just
341              add a delete action, and end the current record.*/
342           SVN_ERR(svn_stream_puts(eb->stream,
343                                   SVN_REPOS_DUMPFILE_NODE_ACTION
344                                   ": delete\n\n"));
345
346           /* recurse:  print an additional add-with-history record. */
347           SVN_ERR(dump_node(eb, path, kind, svn_node_action_add,
348                             is_copy, compare_path, compare_rev, pool));
349
350           /* we can leave this routine quietly now, don't need to dump
351              any content;  that was already done in the second record. */
352           must_dump_text = FALSE;
353           must_dump_props = FALSE;
354         }
355     }
356   else if (action == svn_node_action_delete)
357     {
358       SVN_ERR(svn_stream_puts(eb->stream,
359                               SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n"));
360
361       /* we can leave this routine quietly now, don't need to dump
362          any content. */
363       must_dump_text = FALSE;
364       must_dump_props = FALSE;
365     }
366   else if (action == svn_node_action_add)
367     {
368       SVN_ERR(svn_stream_puts(eb->stream,
369                               SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
370
371       if (! is_copy)
372         {
373           /* Dump all contents for a simple 'add'. */
374           if (kind == svn_node_file)
375             must_dump_text = TRUE;
376           must_dump_props = TRUE;
377         }
378       else
379         {
380           if (!eb->verify && cmp_rev < eb->oldest_dumped_rev
381               && eb->notify_func)
382             {
383               svn_repos_notify_t *notify =
384                     svn_repos_notify_create(svn_repos_notify_warning, pool);
385
386               notify->warning = svn_repos_notify_warning_found_old_reference;
387               notify->warning_str = apr_psprintf(
388                      pool,
389                      _("Referencing data in revision %ld,"
390                        " which is older than the oldest"
391                        " dumped revision (r%ld).  Loading this dump"
392                        " into an empty repository"
393                        " will fail."),
394                      cmp_rev, eb->oldest_dumped_rev);
395               if (eb->found_old_reference)
396                 *eb->found_old_reference = TRUE;
397               eb->notify_func(eb->notify_baton, notify, pool);
398             }
399
400           SVN_ERR(svn_stream_printf(eb->stream, pool,
401                                     SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
402                                     ": %ld\n"
403                                     SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
404                                     ": %s\n",
405                                     cmp_rev, cmp_path));
406
407           SVN_ERR(svn_fs_revision_root(&compare_root,
408                                        svn_fs_root_fs(eb->fs_root),
409                                        compare_rev, pool));
410
411           /* Need to decide if the copied node had any extra textual or
412              property mods as well.  */
413           SVN_ERR(svn_fs_props_changed(&must_dump_props,
414                                        compare_root, compare_path,
415                                        eb->fs_root, path, pool));
416           if (kind == svn_node_file)
417             {
418               svn_checksum_t *checksum;
419               const char *hex_digest;
420               SVN_ERR(svn_fs_contents_changed(&must_dump_text,
421                                               compare_root, compare_path,
422                                               eb->fs_root, path, pool));
423
424               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
425                                            compare_root, compare_path,
426                                            FALSE, pool));
427               hex_digest = svn_checksum_to_cstring(checksum, pool);
428               if (hex_digest)
429                 SVN_ERR(svn_stream_printf(eb->stream, pool,
430                                       SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5
431                                       ": %s\n", hex_digest));
432
433               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
434                                            compare_root, compare_path,
435                                            FALSE, pool));
436               hex_digest = svn_checksum_to_cstring(checksum, pool);
437               if (hex_digest)
438                 SVN_ERR(svn_stream_printf(eb->stream, pool,
439                                       SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1
440                                       ": %s\n", hex_digest));
441             }
442         }
443     }
444
445   if ((! must_dump_text) && (! must_dump_props))
446     {
447       /* If we're not supposed to dump text or props, so be it, we can
448          just go home.  However, if either one needs to be dumped,
449          then our dumpstream format demands that at a *minimum*, we
450          see a lone "PROPS-END" as a divider between text and props
451          content within the content-block. */
452       len = 2;
453       return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
454     }
455
456   /*** Start prepping content to dump... ***/
457
458   /* If we are supposed to dump properties, write out a property
459      length header and generate a stringbuf that contains those
460      property values here. */
461   if (must_dump_props)
462     {
463       apr_hash_t *prophash, *oldhash = NULL;
464       apr_size_t proplen;
465       svn_stream_t *propstream;
466
467       SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
468
469       /* If this is a partial dump, then issue a warning if we dump mergeinfo
470          properties that refer to revisions older than the first revision
471          dumped. */
472       if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1)
473         {
474           svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
475                                                       SVN_PROP_MERGEINFO);
476           if (mergeinfo_str)
477             {
478               svn_mergeinfo_t mergeinfo, old_mergeinfo;
479
480               SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str->data,
481                                           pool));
482               SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
483                 &old_mergeinfo, mergeinfo,
484                 eb->oldest_dumped_rev - 1, 0,
485                 TRUE, pool, pool));
486               if (apr_hash_count(old_mergeinfo))
487                 {
488                   svn_repos_notify_t *notify =
489                     svn_repos_notify_create(svn_repos_notify_warning, pool);
490
491                   notify->warning = svn_repos_notify_warning_found_old_mergeinfo;
492                   notify->warning_str = apr_psprintf(
493                     pool,
494                     _("Mergeinfo referencing revision(s) prior "
495                       "to the oldest dumped revision (r%ld). "
496                       "Loading this dump may result in invalid "
497                       "mergeinfo."),
498                     eb->oldest_dumped_rev);
499
500                   if (eb->found_old_mergeinfo)
501                     *eb->found_old_mergeinfo = TRUE;
502                   eb->notify_func(eb->notify_baton, notify, pool);
503                 }
504             }
505         }
506
507       if (eb->use_deltas && compare_root)
508         {
509           /* Fetch the old property hash to diff against and output a header
510              saying that our property contents are a delta. */
511           SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
512                                        pool));
513           SVN_ERR(svn_stream_puts(eb->stream,
514                                   SVN_REPOS_DUMPFILE_PROP_DELTA ": true\n"));
515         }
516       else
517         oldhash = apr_hash_make(pool);
518       propstring = svn_stringbuf_create_ensure(0, pool);
519       propstream = svn_stream_from_stringbuf(propstring, pool);
520       SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream,
521                                          "PROPS-END", pool));
522       SVN_ERR(svn_stream_close(propstream));
523       proplen = propstring->len;
524       content_length += proplen;
525       SVN_ERR(svn_stream_printf(eb->stream, pool,
526                                 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
527                                 ": %" APR_SIZE_T_FMT "\n", proplen));
528     }
529
530   /* If we are supposed to dump text, write out a text length header
531      here, and an MD5 checksum (if available). */
532   if (must_dump_text && (kind == svn_node_file))
533     {
534       svn_checksum_t *checksum;
535       const char *hex_digest;
536       svn_filesize_t textlen;
537
538       if (eb->use_deltas)
539         {
540           /* Compute the text delta now and write it into a temporary
541              file, so that we can find its length.  Output a header
542              saying our text contents are a delta. */
543           SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
544                               compare_path, eb->fs_root, path, pool));
545           SVN_ERR(svn_stream_puts(eb->stream,
546                                   SVN_REPOS_DUMPFILE_TEXT_DELTA ": true\n"));
547
548           if (compare_root)
549             {
550               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
551                                            compare_root, compare_path,
552                                            FALSE, pool));
553               hex_digest = svn_checksum_to_cstring(checksum, pool);
554               if (hex_digest)
555                 SVN_ERR(svn_stream_printf(eb->stream, pool,
556                                           SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
557                                           ": %s\n", hex_digest));
558
559               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
560                                            compare_root, compare_path,
561                                            FALSE, pool));
562               hex_digest = svn_checksum_to_cstring(checksum, pool);
563               if (hex_digest)
564                 SVN_ERR(svn_stream_printf(eb->stream, pool,
565                                       SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1
566                                       ": %s\n", hex_digest));
567             }
568         }
569       else
570         {
571           /* Just fetch the length of the file. */
572           SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
573         }
574
575       content_length += textlen;
576       SVN_ERR(svn_stream_printf(eb->stream, pool,
577                                 SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
578                                 ": %" SVN_FILESIZE_T_FMT "\n", textlen));
579
580       SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
581                                    eb->fs_root, path, FALSE, pool));
582       hex_digest = svn_checksum_to_cstring(checksum, pool);
583       if (hex_digest)
584         SVN_ERR(svn_stream_printf(eb->stream, pool,
585                                   SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
586                                   ": %s\n", hex_digest));
587
588       SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
589                                    eb->fs_root, path, FALSE, pool));
590       hex_digest = svn_checksum_to_cstring(checksum, pool);
591       if (hex_digest)
592         SVN_ERR(svn_stream_printf(eb->stream, pool,
593                                   SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1
594                                   ": %s\n", hex_digest));
595     }
596
597   /* 'Content-length:' is the last header before we dump the content,
598      and is the sum of the text and prop contents lengths.  We write
599      this only for the benefit of non-Subversion RFC-822 parsers. */
600   SVN_ERR(svn_stream_printf(eb->stream, pool,
601                             SVN_REPOS_DUMPFILE_CONTENT_LENGTH
602                             ": %" SVN_FILESIZE_T_FMT "\n\n",
603                             content_length));
604
605   /* Dump property content if we're supposed to do so. */
606   if (must_dump_props)
607     {
608       len = propstring->len;
609       SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len));
610     }
611
612   /* Dump text content */
613   if (must_dump_text && (kind == svn_node_file))
614     {
615       svn_stream_t *contents;
616
617       if (delta_file)
618         {
619           /* Make sure to close the underlying file when the stream is
620              closed. */
621           contents = svn_stream_from_aprfile2(delta_file, FALSE, pool);
622         }
623       else
624         SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
625
626       SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool),
627                                NULL, NULL, pool));
628     }
629
630   len = 2;
631   return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
632 }
633
634
635 static svn_error_t *
636 open_root(void *edit_baton,
637           svn_revnum_t base_revision,
638           apr_pool_t *pool,
639           void **root_baton)
640 {
641   *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
642                                edit_baton, NULL, FALSE, pool);
643   return SVN_NO_ERROR;
644 }
645
646
647 static svn_error_t *
648 delete_entry(const char *path,
649              svn_revnum_t revision,
650              void *parent_baton,
651              apr_pool_t *pool)
652 {
653   struct dir_baton *pb = parent_baton;
654   const char *mypath = apr_pstrdup(pb->pool, path);
655
656   /* remember this path needs to be deleted. */
657   svn_hash_sets(pb->deleted_entries, mypath, pb);
658
659   return SVN_NO_ERROR;
660 }
661
662
663 static svn_error_t *
664 add_directory(const char *path,
665               void *parent_baton,
666               const char *copyfrom_path,
667               svn_revnum_t copyfrom_rev,
668               apr_pool_t *pool,
669               void **child_baton)
670 {
671   struct dir_baton *pb = parent_baton;
672   struct edit_baton *eb = pb->edit_baton;
673   void *val;
674   svn_boolean_t is_copy = FALSE;
675   struct dir_baton *new_db
676     = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool);
677
678   /* This might be a replacement -- is the path already deleted? */
679   val = svn_hash_gets(pb->deleted_entries, path);
680
681   /* Detect an add-with-history. */
682   is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
683
684   /* Dump the node. */
685   SVN_ERR(dump_node(eb, path,
686                     svn_node_dir,
687                     val ? svn_node_action_replace : svn_node_action_add,
688                     is_copy,
689                     is_copy ? copyfrom_path : NULL,
690                     is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
691                     pool));
692
693   if (val)
694     /* Delete the path, it's now been dumped. */
695     svn_hash_sets(pb->deleted_entries, path, NULL);
696
697   new_db->written_out = TRUE;
698
699   *child_baton = new_db;
700   return SVN_NO_ERROR;
701 }
702
703
704 static svn_error_t *
705 open_directory(const char *path,
706                void *parent_baton,
707                svn_revnum_t base_revision,
708                apr_pool_t *pool,
709                void **child_baton)
710 {
711   struct dir_baton *pb = parent_baton;
712   struct edit_baton *eb = pb->edit_baton;
713   struct dir_baton *new_db;
714   const char *cmp_path = NULL;
715   svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
716
717   /* If the parent directory has explicit comparison path and rev,
718      record the same for this one. */
719   if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
720     {
721       cmp_path = svn_relpath_join(pb->cmp_path,
722                                   svn_relpath_basename(path, pool), pool);
723       cmp_rev = pb->cmp_rev;
724     }
725
726   new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, FALSE, pool);
727   *child_baton = new_db;
728   return SVN_NO_ERROR;
729 }
730
731
732 static svn_error_t *
733 close_directory(void *dir_baton,
734                 apr_pool_t *pool)
735 {
736   struct dir_baton *db = dir_baton;
737   struct edit_baton *eb = db->edit_baton;
738   apr_pool_t *subpool = svn_pool_create(pool);
739   int i;
740   apr_array_header_t *sorted_entries;
741
742   /* Sort entries lexically instead of as paths. Even though the entries
743    * are full paths they're all in the same directory (see comment in struct
744    * dir_baton definition). So we really want to sort by basename, in which
745    * case the lexical sort function is more efficient. */
746   sorted_entries = svn_sort__hash(db->deleted_entries,
747                                   svn_sort_compare_items_lexically, pool);
748   for (i = 0; i < sorted_entries->nelts; i++)
749     {
750       const char *path = APR_ARRAY_IDX(sorted_entries, i,
751                                        svn_sort__item_t).key;
752
753       svn_pool_clear(subpool);
754
755       /* By sending 'svn_node_unknown', the Node-kind: header simply won't
756          be written out.  No big deal at all, really.  The loader
757          shouldn't care.  */
758       SVN_ERR(dump_node(eb, path,
759                         svn_node_unknown, svn_node_action_delete,
760                         FALSE, NULL, SVN_INVALID_REVNUM, subpool));
761     }
762
763   svn_pool_destroy(subpool);
764   return SVN_NO_ERROR;
765 }
766
767
768 static svn_error_t *
769 add_file(const char *path,
770          void *parent_baton,
771          const char *copyfrom_path,
772          svn_revnum_t copyfrom_rev,
773          apr_pool_t *pool,
774          void **file_baton)
775 {
776   struct dir_baton *pb = parent_baton;
777   struct edit_baton *eb = pb->edit_baton;
778   void *val;
779   svn_boolean_t is_copy = FALSE;
780
781   /* This might be a replacement -- is the path already deleted? */
782   val = svn_hash_gets(pb->deleted_entries, path);
783
784   /* Detect add-with-history. */
785   is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
786
787   /* Dump the node. */
788   SVN_ERR(dump_node(eb, path,
789                     svn_node_file,
790                     val ? svn_node_action_replace : svn_node_action_add,
791                     is_copy,
792                     is_copy ? copyfrom_path : NULL,
793                     is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
794                     pool));
795
796   if (val)
797     /* delete the path, it's now been dumped. */
798     svn_hash_sets(pb->deleted_entries, path, NULL);
799
800   *file_baton = NULL;  /* muhahahaha */
801   return SVN_NO_ERROR;
802 }
803
804
805 static svn_error_t *
806 open_file(const char *path,
807           void *parent_baton,
808           svn_revnum_t ancestor_revision,
809           apr_pool_t *pool,
810           void **file_baton)
811 {
812   struct dir_baton *pb = parent_baton;
813   struct edit_baton *eb = pb->edit_baton;
814   const char *cmp_path = NULL;
815   svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
816
817   /* If the parent directory has explicit comparison path and rev,
818      record the same for this one. */
819   if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
820     {
821       cmp_path = svn_relpath_join(pb->cmp_path,
822                                   svn_relpath_basename(path, pool), pool);
823       cmp_rev = pb->cmp_rev;
824     }
825
826   SVN_ERR(dump_node(eb, path,
827                     svn_node_file, svn_node_action_change,
828                     FALSE, cmp_path, cmp_rev, pool));
829
830   *file_baton = NULL;  /* muhahahaha again */
831   return SVN_NO_ERROR;
832 }
833
834
835 static svn_error_t *
836 change_dir_prop(void *parent_baton,
837                 const char *name,
838                 const svn_string_t *value,
839                 apr_pool_t *pool)
840 {
841   struct dir_baton *db = parent_baton;
842   struct edit_baton *eb = db->edit_baton;
843
844   /* This function is what distinguishes between a directory that is
845      opened to merely get somewhere, vs. one that is opened because it
846      *actually* changed by itself.  */
847   if (! db->written_out)
848     {
849       SVN_ERR(dump_node(eb, db->path,
850                         svn_node_dir, svn_node_action_change,
851                         FALSE, db->cmp_path, db->cmp_rev, pool));
852       db->written_out = TRUE;
853     }
854   return SVN_NO_ERROR;
855 }
856
857 static svn_error_t *
858 fetch_props_func(apr_hash_t **props,
859                  void *baton,
860                  const char *path,
861                  svn_revnum_t base_revision,
862                  apr_pool_t *result_pool,
863                  apr_pool_t *scratch_pool)
864 {
865   struct edit_baton *eb = baton;
866   svn_error_t *err;
867   svn_fs_root_t *fs_root;
868
869   if (!SVN_IS_VALID_REVNUM(base_revision))
870     base_revision = eb->current_rev - 1;
871
872   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
873
874   err = svn_fs_node_proplist(props, fs_root, path, result_pool);
875   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
876     {
877       svn_error_clear(err);
878       *props = apr_hash_make(result_pool);
879       return SVN_NO_ERROR;
880     }
881   else if (err)
882     return svn_error_trace(err);
883
884   return SVN_NO_ERROR;
885 }
886
887 static svn_error_t *
888 fetch_kind_func(svn_node_kind_t *kind,
889                 void *baton,
890                 const char *path,
891                 svn_revnum_t base_revision,
892                 apr_pool_t *scratch_pool)
893 {
894   struct edit_baton *eb = baton;
895   svn_fs_root_t *fs_root;
896
897   if (!SVN_IS_VALID_REVNUM(base_revision))
898     base_revision = eb->current_rev - 1;
899
900   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
901
902   SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
903
904   return SVN_NO_ERROR;
905 }
906
907 static svn_error_t *
908 fetch_base_func(const char **filename,
909                 void *baton,
910                 const char *path,
911                 svn_revnum_t base_revision,
912                 apr_pool_t *result_pool,
913                 apr_pool_t *scratch_pool)
914 {
915   struct edit_baton *eb = baton;
916   svn_stream_t *contents;
917   svn_stream_t *file_stream;
918   const char *tmp_filename;
919   svn_error_t *err;
920   svn_fs_root_t *fs_root;
921
922   if (!SVN_IS_VALID_REVNUM(base_revision))
923     base_revision = eb->current_rev - 1;
924
925   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
926
927   err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
928   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
929     {
930       svn_error_clear(err);
931       *filename = NULL;
932       return SVN_NO_ERROR;
933     }
934   else if (err)
935     return svn_error_trace(err);
936   SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
937                                  svn_io_file_del_on_pool_cleanup,
938                                  scratch_pool, scratch_pool));
939   SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
940
941   *filename = apr_pstrdup(result_pool, tmp_filename);
942
943   return SVN_NO_ERROR;
944 }
945
946
947 static svn_error_t *
948 get_dump_editor(const svn_delta_editor_t **editor,
949                 void **edit_baton,
950                 svn_fs_t *fs,
951                 svn_revnum_t to_rev,
952                 const char *root_path,
953                 svn_stream_t *stream,
954                 svn_boolean_t *found_old_reference,
955                 svn_boolean_t *found_old_mergeinfo,
956                 svn_error_t *(*custom_close_directory)(void *dir_baton,
957                                   apr_pool_t *scratch_pool),
958                 svn_repos_notify_func_t notify_func,
959                 void *notify_baton,
960                 svn_revnum_t oldest_dumped_rev,
961                 svn_boolean_t use_deltas,
962                 svn_boolean_t verify,
963                 apr_pool_t *pool)
964 {
965   /* Allocate an edit baton to be stored in every directory baton.
966      Set it up for the directory baton we create here, which is the
967      root baton. */
968   struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
969   svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
970   svn_delta_shim_callbacks_t *shim_callbacks =
971                                 svn_delta_shim_callbacks_default(pool);
972
973   /* Set up the edit baton. */
974   eb->stream = stream;
975   eb->notify_func = notify_func;
976   eb->notify_baton = notify_baton;
977   eb->oldest_dumped_rev = oldest_dumped_rev;
978   eb->bufsize = sizeof(eb->buffer);
979   eb->path = apr_pstrdup(pool, root_path);
980   SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
981   eb->fs = fs;
982   eb->current_rev = to_rev;
983   eb->use_deltas = use_deltas;
984   eb->verify = verify;
985   eb->found_old_reference = found_old_reference;
986   eb->found_old_mergeinfo = found_old_mergeinfo;
987
988   /* Set up the editor. */
989   dump_editor->open_root = open_root;
990   dump_editor->delete_entry = delete_entry;
991   dump_editor->add_directory = add_directory;
992   dump_editor->open_directory = open_directory;
993   if (custom_close_directory)
994     dump_editor->close_directory = custom_close_directory;
995   else
996     dump_editor->close_directory = close_directory;
997   dump_editor->change_dir_prop = change_dir_prop;
998   dump_editor->add_file = add_file;
999   dump_editor->open_file = open_file;
1000
1001   *edit_baton = eb;
1002   *editor = dump_editor;
1003
1004   shim_callbacks->fetch_kind_func = fetch_kind_func;
1005   shim_callbacks->fetch_props_func = fetch_props_func;
1006   shim_callbacks->fetch_base_func = fetch_base_func;
1007   shim_callbacks->fetch_baton = eb;
1008
1009   SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1010                                    NULL, NULL, shim_callbacks, pool, pool));
1011
1012   return SVN_NO_ERROR;
1013 }
1014
1015 /*----------------------------------------------------------------------*/
1016 \f
1017 /** The main dumping routine, svn_repos_dump_fs. **/
1018
1019
1020 /* Helper for svn_repos_dump_fs.
1021
1022    Write a revision record of REV in FS to writable STREAM, using POOL.
1023  */
1024 static svn_error_t *
1025 write_revision_record(svn_stream_t *stream,
1026                       svn_fs_t *fs,
1027                       svn_revnum_t rev,
1028                       apr_pool_t *pool)
1029 {
1030   apr_size_t len;
1031   apr_hash_t *props;
1032   svn_stringbuf_t *encoded_prophash;
1033   apr_time_t timetemp;
1034   svn_string_t *datevalue;
1035   svn_stream_t *propstream;
1036
1037   /* Read the revision props even if we're aren't going to dump
1038      them for verification purposes */
1039   SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
1040
1041   /* Run revision date properties through the time conversion to
1042      canonicalize them. */
1043   /* ### Remove this when it is no longer needed for sure. */
1044   datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
1045   if (datevalue)
1046     {
1047       SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
1048       datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
1049                                     pool);
1050       svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
1051     }
1052
1053   encoded_prophash = svn_stringbuf_create_ensure(0, pool);
1054   propstream = svn_stream_from_stringbuf(encoded_prophash, pool);
1055   SVN_ERR(svn_hash_write2(props, propstream, "PROPS-END", pool));
1056   SVN_ERR(svn_stream_close(propstream));
1057
1058   /* ### someday write a revision-content-checksum */
1059
1060   SVN_ERR(svn_stream_printf(stream, pool,
1061                             SVN_REPOS_DUMPFILE_REVISION_NUMBER
1062                             ": %ld\n", rev));
1063   SVN_ERR(svn_stream_printf(stream, pool,
1064                             SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
1065                             ": %" APR_SIZE_T_FMT "\n",
1066                             encoded_prophash->len));
1067
1068   /* Write out a regular Content-length header for the benefit of
1069      non-Subversion RFC-822 parsers. */
1070   SVN_ERR(svn_stream_printf(stream, pool,
1071                             SVN_REPOS_DUMPFILE_CONTENT_LENGTH
1072                             ": %" APR_SIZE_T_FMT "\n\n",
1073                             encoded_prophash->len));
1074
1075   len = encoded_prophash->len;
1076   SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len));
1077
1078   len = 1;
1079   return svn_stream_write(stream, "\n", &len);
1080 }
1081
1082
1083
1084 /* The main dumper. */
1085 svn_error_t *
1086 svn_repos_dump_fs3(svn_repos_t *repos,
1087                    svn_stream_t *stream,
1088                    svn_revnum_t start_rev,
1089                    svn_revnum_t end_rev,
1090                    svn_boolean_t incremental,
1091                    svn_boolean_t use_deltas,
1092                    svn_repos_notify_func_t notify_func,
1093                    void *notify_baton,
1094                    svn_cancel_func_t cancel_func,
1095                    void *cancel_baton,
1096                    apr_pool_t *pool)
1097 {
1098   const svn_delta_editor_t *dump_editor;
1099   void *dump_edit_baton = NULL;
1100   svn_revnum_t i;
1101   svn_fs_t *fs = svn_repos_fs(repos);
1102   apr_pool_t *subpool = svn_pool_create(pool);
1103   svn_revnum_t youngest;
1104   const char *uuid;
1105   int version;
1106   svn_boolean_t found_old_reference = FALSE;
1107   svn_boolean_t found_old_mergeinfo = FALSE;
1108   svn_repos_notify_t *notify;
1109
1110   /* Determine the current youngest revision of the filesystem. */
1111   SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1112
1113   /* Use default vals if necessary. */
1114   if (! SVN_IS_VALID_REVNUM(start_rev))
1115     start_rev = 0;
1116   if (! SVN_IS_VALID_REVNUM(end_rev))
1117     end_rev = youngest;
1118   if (! stream)
1119     stream = svn_stream_empty(pool);
1120
1121   /* Validate the revisions. */
1122   if (start_rev > end_rev)
1123     return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1124                              _("Start revision %ld"
1125                                " is greater than end revision %ld"),
1126                              start_rev, end_rev);
1127   if (end_rev > youngest)
1128     return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1129                              _("End revision %ld is invalid "
1130                                "(youngest revision is %ld)"),
1131                              end_rev, youngest);
1132   if ((start_rev == 0) && incremental)
1133     incremental = FALSE; /* revision 0 looks the same regardless of
1134                             whether or not this is an incremental
1135                             dump, so just simplify things. */
1136
1137   /* Write out the UUID. */
1138   SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
1139
1140   /* If we're not using deltas, use the previous version, for
1141      compatibility with svn 1.0.x. */
1142   version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
1143   if (!use_deltas)
1144     version--;
1145
1146   /* Write out "general" metadata for the dumpfile.  In this case, a
1147      magic header followed by a dumpfile format version. */
1148   SVN_ERR(svn_stream_printf(stream, pool,
1149                             SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
1150                             version));
1151   SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
1152                             ": %s\n\n", uuid));
1153
1154   /* Create a notify object that we can reuse in the loop. */
1155   if (notify_func)
1156     notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
1157                                      pool);
1158
1159   /* Main loop:  we're going to dump revision i.  */
1160   for (i = start_rev; i <= end_rev; i++)
1161     {
1162       svn_revnum_t from_rev, to_rev;
1163       svn_fs_root_t *to_root;
1164       svn_boolean_t use_deltas_for_rev;
1165
1166       svn_pool_clear(subpool);
1167
1168       /* Check for cancellation. */
1169       if (cancel_func)
1170         SVN_ERR(cancel_func(cancel_baton));
1171
1172       /* Special-case the initial revision dump: it needs to contain
1173          *all* nodes, because it's the foundation of all future
1174          revisions in the dumpfile. */
1175       if ((i == start_rev) && (! incremental))
1176         {
1177           /* Special-special-case a dump of revision 0. */
1178           if (i == 0)
1179             {
1180               /* Just write out the one revision 0 record and move on.
1181                  The parser might want to use its properties. */
1182               SVN_ERR(write_revision_record(stream, fs, 0, subpool));
1183               to_rev = 0;
1184               goto loop_end;
1185             }
1186
1187           /* Compare START_REV to revision 0, so that everything
1188              appears to be added.  */
1189           from_rev = 0;
1190           to_rev = i;
1191         }
1192       else
1193         {
1194           /* In the normal case, we want to compare consecutive revs. */
1195           from_rev = i - 1;
1196           to_rev = i;
1197         }
1198
1199       /* Write the revision record. */
1200       SVN_ERR(write_revision_record(stream, fs, to_rev, subpool));
1201
1202       /* Fetch the editor which dumps nodes to a file.  Regardless of
1203          what we've been told, don't use deltas for the first rev of a
1204          non-incremental dump. */
1205       use_deltas_for_rev = use_deltas && (incremental || i != start_rev);
1206       SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, to_rev,
1207                               "", stream, &found_old_reference,
1208                               &found_old_mergeinfo, NULL,
1209                               notify_func, notify_baton,
1210                               start_rev, use_deltas_for_rev, FALSE, subpool));
1211
1212       /* Drive the editor in one way or another. */
1213       SVN_ERR(svn_fs_revision_root(&to_root, fs, to_rev, subpool));
1214
1215       /* If this is the first revision of a non-incremental dump,
1216          we're in for a full tree dump.  Otherwise, we want to simply
1217          replay the revision.  */
1218       if ((i == start_rev) && (! incremental))
1219         {
1220           svn_fs_root_t *from_root;
1221           SVN_ERR(svn_fs_revision_root(&from_root, fs, from_rev, subpool));
1222           SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
1223                                        to_root, "",
1224                                        dump_editor, dump_edit_baton,
1225                                        NULL,
1226                                        NULL,
1227                                        FALSE, /* don't send text-deltas */
1228                                        svn_depth_infinity,
1229                                        FALSE, /* don't send entry props */
1230                                        FALSE, /* don't ignore ancestry */
1231                                        subpool));
1232         }
1233       else
1234         {
1235           SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
1236                                     dump_editor, dump_edit_baton,
1237                                     NULL, NULL, subpool));
1238
1239           /* While our editor close_edit implementation is a no-op, we still
1240              do this for completeness. */
1241           SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool));
1242         }
1243
1244     loop_end:
1245       if (notify_func)
1246         {
1247           notify->revision = to_rev;
1248           notify_func(notify_baton, notify, subpool);
1249         }
1250     }
1251
1252   if (notify_func)
1253     {
1254       /* Did we issue any warnings about references to revisions older than
1255          the oldest dumped revision?  If so, then issue a final generic
1256          warning, since the inline warnings already issued might easily be
1257          missed. */
1258
1259       notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool);
1260       notify_func(notify_baton, notify, subpool);
1261
1262       if (found_old_reference)
1263         {
1264           notify = svn_repos_notify_create(svn_repos_notify_warning, subpool);
1265
1266           notify->warning = svn_repos_notify_warning_found_old_reference;
1267           notify->warning_str = _("The range of revisions dumped "
1268                                   "contained references to "
1269                                   "copy sources outside that "
1270                                   "range.");
1271           notify_func(notify_baton, notify, subpool);
1272         }
1273
1274       /* Ditto if we issued any warnings about old revisions referenced
1275          in dumped mergeinfo. */
1276       if (found_old_mergeinfo)
1277         {
1278           notify = svn_repos_notify_create(svn_repos_notify_warning, subpool);
1279
1280           notify->warning = svn_repos_notify_warning_found_old_mergeinfo;
1281           notify->warning_str = _("The range of revisions dumped "
1282                                   "contained mergeinfo "
1283                                   "which reference revisions outside "
1284                                   "that range.");
1285           notify_func(notify_baton, notify, subpool);
1286         }
1287     }
1288
1289   svn_pool_destroy(subpool);
1290
1291   return SVN_NO_ERROR;
1292 }
1293
1294
1295 /*----------------------------------------------------------------------*/
1296 \f
1297 /* verify, based on dump */
1298
1299
1300 /* Creating a new revision that changes /A/B/E/bravo means creating new
1301    directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
1302    each entry not changed in the new revision a link back to the entry in a
1303    previous revision.  svn_repos_replay()ing a revision does not verify that
1304    those links are correct.
1305
1306    For paths actually changed in the revision we verify, we get directory
1307    contents or file length twice: once in the dump editor, and once here.
1308    We could create a new verify baton, store in it the changed paths, and
1309    skip those here, but that means building an entire wrapper editor and
1310    managing two levels of batons.  The impact from checking these entries
1311    twice should be minimal, while the code to avoid it is not.
1312 */
1313
1314 static svn_error_t *
1315 verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
1316                        void *val, apr_pool_t *pool)
1317 {
1318   struct dir_baton *db = baton;
1319   svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
1320   char *path = svn_relpath_join(db->path, (const char *)key, pool);
1321   apr_hash_t *dirents;
1322   svn_filesize_t len;
1323
1324   /* since we can't access the directory entries directly by their ID,
1325      we need to navigate from the FS_ROOT to them (relatively expensive
1326      because we may start at a never rev than the last change to node). */
1327   switch (dirent->kind) {
1328   case svn_node_dir:
1329     /* Getting this directory's contents is enough to ensure that our
1330        link to it is correct. */
1331     SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, path, pool));
1332     break;
1333   case svn_node_file:
1334     /* Getting this file's size is enough to ensure that our link to it
1335        is correct. */
1336     SVN_ERR(svn_fs_file_length(&len, db->edit_baton->fs_root, path, pool));
1337     break;
1338   default:
1339     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1340                              _("Unexpected node kind %d for '%s'"),
1341                              dirent->kind, path);
1342   }
1343
1344   return SVN_NO_ERROR;
1345 }
1346
1347 static svn_error_t *
1348 verify_close_directory(void *dir_baton,
1349                 apr_pool_t *pool)
1350 {
1351   struct dir_baton *db = dir_baton;
1352   apr_hash_t *dirents;
1353   SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root,
1354                              db->path, pool));
1355   SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
1356                             dir_baton, pool));
1357   return close_directory(dir_baton, pool);
1358 }
1359
1360 /* Baton type used for forwarding notifications from FS API to REPOS API. */
1361 struct verify_fs2_notify_func_baton_t
1362 {
1363    /* notification function to call (must not be NULL) */
1364    svn_repos_notify_func_t notify_func;
1365
1366    /* baton to use for it */
1367    void *notify_baton;
1368
1369    /* type of notification to send (we will simply plug in the revision) */
1370    svn_repos_notify_t *notify;
1371 };
1372
1373 /* Forward the notification to BATON. */
1374 static void
1375 verify_fs2_notify_func(svn_revnum_t revision,
1376                        void *baton,
1377                        apr_pool_t *pool)
1378 {
1379   struct verify_fs2_notify_func_baton_t *notify_baton = baton;
1380
1381   notify_baton->notify->revision = revision;
1382   notify_baton->notify_func(notify_baton->notify_baton,
1383                             notify_baton->notify, pool);
1384 }
1385
1386 svn_error_t *
1387 svn_repos_verify_fs2(svn_repos_t *repos,
1388                      svn_revnum_t start_rev,
1389                      svn_revnum_t end_rev,
1390                      svn_repos_notify_func_t notify_func,
1391                      void *notify_baton,
1392                      svn_cancel_func_t cancel_func,
1393                      void *cancel_baton,
1394                      apr_pool_t *pool)
1395 {
1396   svn_fs_t *fs = svn_repos_fs(repos);
1397   svn_revnum_t youngest;
1398   svn_revnum_t rev;
1399   apr_pool_t *iterpool = svn_pool_create(pool);
1400   svn_repos_notify_t *notify;
1401   svn_fs_progress_notify_func_t verify_notify = NULL;
1402   struct verify_fs2_notify_func_baton_t *verify_notify_baton = NULL;
1403
1404   /* Determine the current youngest revision of the filesystem. */
1405   SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1406
1407   /* Use default vals if necessary. */
1408   if (! SVN_IS_VALID_REVNUM(start_rev))
1409     start_rev = 0;
1410   if (! SVN_IS_VALID_REVNUM(end_rev))
1411     end_rev = youngest;
1412
1413   /* Validate the revisions. */
1414   if (start_rev > end_rev)
1415     return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1416                              _("Start revision %ld"
1417                                " is greater than end revision %ld"),
1418                              start_rev, end_rev);
1419   if (end_rev > youngest)
1420     return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1421                              _("End revision %ld is invalid "
1422                                "(youngest revision is %ld)"),
1423                              end_rev, youngest);
1424
1425   /* Create a notify object that we can reuse within the loop and a
1426      forwarding structure for notifications from inside svn_fs_verify(). */
1427   if (notify_func)
1428     {
1429       notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end,
1430                                        pool);
1431
1432       verify_notify = verify_fs2_notify_func;
1433       verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
1434       verify_notify_baton->notify_func = notify_func;
1435       verify_notify_baton->notify_baton = notify_baton;
1436       verify_notify_baton->notify
1437         = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool);
1438     }
1439
1440   /* Verify global metadata and backend-specific data first. */
1441   SVN_ERR(svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
1442                         start_rev, end_rev,
1443                         verify_notify, verify_notify_baton,
1444                         cancel_func, cancel_baton, pool));
1445
1446   for (rev = start_rev; rev <= end_rev; rev++)
1447     {
1448       const svn_delta_editor_t *dump_editor;
1449       void *dump_edit_baton;
1450       const svn_delta_editor_t *cancel_editor;
1451       void *cancel_edit_baton;
1452       svn_fs_root_t *to_root;
1453       apr_hash_t *props;
1454
1455       svn_pool_clear(iterpool);
1456
1457       /* Get cancellable dump editor, but with our close_directory handler. */
1458       SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
1459                               fs, rev, "",
1460                               svn_stream_empty(iterpool),
1461                               NULL, NULL,
1462                               verify_close_directory,
1463                               notify_func, notify_baton,
1464                               start_rev,
1465                               FALSE, TRUE, /* use_deltas, verify */
1466                               iterpool));
1467       SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1468                                                 dump_editor, dump_edit_baton,
1469                                                 &cancel_editor,
1470                                                 &cancel_edit_baton,
1471                                                 iterpool));
1472
1473       SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool));
1474       SVN_ERR(svn_fs_verify_root(to_root, iterpool));
1475
1476       SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
1477                                 cancel_editor, cancel_edit_baton,
1478                                 NULL, NULL, iterpool));
1479       /* While our editor close_edit implementation is a no-op, we still
1480          do this for completeness. */
1481       SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, iterpool));
1482
1483       SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, iterpool));
1484
1485       if (notify_func)
1486         {
1487           notify->revision = rev;
1488           notify_func(notify_baton, notify, iterpool);
1489         }
1490     }
1491
1492   /* We're done. */
1493   if (notify_func)
1494     {
1495       notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool);
1496       notify_func(notify_baton, notify, iterpool);
1497     }
1498
1499   /* Per-backend verification. */
1500   svn_pool_destroy(iterpool);
1501
1502   return SVN_NO_ERROR;
1503 }