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