]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_repos/dump.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.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 <stdarg.h>
25
26 #include "svn_private_config.h"
27 #include "svn_pools.h"
28 #include "svn_error.h"
29 #include "svn_fs.h"
30 #include "svn_hash.h"
31 #include "svn_iter.h"
32 #include "svn_repos.h"
33 #include "svn_string.h"
34 #include "svn_dirent_uri.h"
35 #include "svn_path.h"
36 #include "svn_time.h"
37 #include "svn_checksum.h"
38 #include "svn_props.h"
39 #include "svn_sorts.h"
40
41 #include "private/svn_repos_private.h"
42 #include "private/svn_mergeinfo_private.h"
43 #include "private/svn_fs_private.h"
44 #include "private/svn_sorts_private.h"
45 #include "private/svn_utf_private.h"
46 #include "private/svn_cache.h"
47
48 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
49
50 /*----------------------------------------------------------------------*/
51 \f
52
53 /* To be able to check whether a path exists in the current revision
54    (as changes come in), we need to track the relevant tree changes.
55
56    In particular, we remember deletions, additions and copies including
57    their copy-from info.  Since the dump performs a pre-order tree walk,
58    we only need to store the data for the stack of parent folders.
59
60    The problem that we are trying to solve is that the dump receives
61    transforming operations whose validity depends on previous operations
62    in the same revision but cannot be checked against the final state
63    as stored in the repository as that is the state *after* we applied
64    the respective tree changes.
65
66    Note that the tracker functions don't perform any sanity or validity
67    checks.  Those higher-level tests have to be done in the calling code.
68    However, there is no way to corrupt the data structure using the
69    provided functions.
70  */
71
72 /* Single entry in the path tracker.  Not all levels along the path
73    hierarchy do need to have an instance of this struct but only those
74    that got changed by a tree modification.
75
76    Please note that the path info in this struct is stored in re-usable
77    stringbuf objects such that we don't need to allocate more memory than
78    the longest path we encounter.
79  */
80 typedef struct path_tracker_entry_t
81 {
82   /* path in the current tree */
83   svn_stringbuf_t *path;
84
85   /* copy-from path (must be empty if COPYFROM_REV is SVN_INVALID_REVNUM) */
86   svn_stringbuf_t *copyfrom_path;
87
88   /* copy-from revision (SVN_INVALID_REVNUM for additions / replacements
89      that don't copy history, i.e. with no sub-tree) */
90   svn_revnum_t copyfrom_rev;
91
92   /* if FALSE, PATH has been deleted */
93   svn_boolean_t exists;
94 } path_tracker_entry_t;
95
96 /* Tracks all tree modifications above the current path.
97  */
98 typedef struct path_tracker_t
99 {
100   /* Container for all relevant tree changes in depth order.
101      May contain more entries than DEPTH to allow for reusing memory.
102      Only entries 0 .. DEPTH-1 are valid.
103    */
104   apr_array_header_t *stack;
105
106   /* Number of relevant entries in STACK.  May be 0 */
107   int depth;
108
109   /* Revision that we current track.  If DEPTH is 0, paths are exist in
110      REVISION exactly when they exist in REVISION-1.  This applies only
111      to the current state of our tree walk.
112    */
113   svn_revnum_t revision;
114
115   /* Allocate container entries here. */
116   apr_pool_t *pool;
117 } path_tracker_t;
118
119 /* Return a new path tracker object for REVISION, allocated in POOL.
120  */
121 static path_tracker_t *
122 tracker_create(svn_revnum_t revision,
123                apr_pool_t *pool)
124 {
125   path_tracker_t *result = apr_pcalloc(pool, sizeof(*result));
126   result->stack = apr_array_make(pool, 16, sizeof(path_tracker_entry_t));
127   result->revision = revision;
128   result->pool = pool;
129
130   return result;
131 }
132
133 /* Remove all entries from TRACKER that are not relevant to PATH anymore.
134  * If ALLOW_EXACT_MATCH is FALSE, keep only entries that pertain to
135  * parent folders but not to PATH itself.
136  *
137  * This internal function implicitly updates the tracker state during the
138  * tree by removing "past" entries.  Other functions will add entries when
139  * we encounter a new tree change.
140  */
141 static void
142 tracker_trim(path_tracker_t *tracker,
143              const char *path,
144              svn_boolean_t allow_exact_match)
145 {
146   /* remove everything that is unrelated to PATH.
147      Note that TRACKER->STACK is depth-ordered,
148      i.e. stack[N] is a (maybe indirect) parent of stack[N+1]
149      for N+1 < DEPTH.
150    */
151   for (; tracker->depth; --tracker->depth)
152     {
153       path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack,
154                                                     tracker->depth - 1,
155                                                     path_tracker_entry_t);
156       const char *rel_path
157         = svn_dirent_skip_ancestor(parent->path->data, path);
158
159       /* always keep parents.  Keep exact matches when allowed. */
160       if (rel_path && (allow_exact_match || *rel_path != '\0'))
161         break;
162     }
163 }
164
165 /* Using TRACKER, check what path at what revision in the repository must
166    be checked to decide that whether PATH exists.  Return the info in
167    *ORIG_PATH and *ORIG_REV, respectively.
168
169    If the path is known to not exist, *ORIG_PATH will be NULL and *ORIG_REV
170    will be SVN_INVALID_REVNUM.  If *ORIG_REV is SVN_INVALID_REVNUM, PATH
171    has just been added in the revision currently being tracked.
172
173    Use POOL for allocations.  Note that *ORIG_PATH may be allocated in POOL,
174    a reference to internal data with the same lifetime as TRACKER or just
175    PATH.
176  */
177 static void
178 tracker_lookup(const char **orig_path,
179                svn_revnum_t *orig_rev,
180                path_tracker_t *tracker,
181                const char *path,
182                apr_pool_t *pool)
183 {
184   tracker_trim(tracker, path, TRUE);
185   if (tracker->depth == 0)
186     {
187       /* no tree changes -> paths are the same as in the previous rev. */
188       *orig_path = path;
189       *orig_rev = tracker->revision - 1;
190     }
191   else
192     {
193       path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack,
194                                                     tracker->depth - 1,
195                                                     path_tracker_entry_t);
196       if (parent->exists)
197         {
198           const char *rel_path
199             = svn_dirent_skip_ancestor(parent->path->data, path);
200
201           if (parent->copyfrom_rev != SVN_INVALID_REVNUM)
202             {
203               /* parent is a copy with history. Translate path. */
204               *orig_path = svn_dirent_join(parent->copyfrom_path->data,
205                                            rel_path, pool);
206               *orig_rev = parent->copyfrom_rev;
207             }
208           else if (*rel_path == '\0')
209             {
210               /* added in this revision with no history */
211               *orig_path = path;
212               *orig_rev = tracker->revision;
213             }
214           else
215             {
216               /* parent got added but not this path */
217               *orig_path = NULL;
218               *orig_rev = SVN_INVALID_REVNUM;
219             }
220         }
221       else
222         {
223           /* (maybe parent) path has been deleted */
224           *orig_path = NULL;
225           *orig_rev = SVN_INVALID_REVNUM;
226         }
227     }
228 }
229
230 /* Return a reference to the stack entry in TRACKER for PATH.  If no
231    suitable entry exists, add one.  Implicitly updates the tracked tree
232    location.
233
234    Only the PATH member of the result is being updated.  All other members
235    will have undefined values.
236  */
237 static path_tracker_entry_t *
238 tracker_add_entry(path_tracker_t *tracker,
239                   const char *path)
240 {
241   path_tracker_entry_t *entry;
242   tracker_trim(tracker, path, FALSE);
243
244   if (tracker->depth == tracker->stack->nelts)
245     {
246       entry = apr_array_push(tracker->stack);
247       entry->path = svn_stringbuf_create_empty(tracker->pool);
248       entry->copyfrom_path = svn_stringbuf_create_empty(tracker->pool);
249     }
250   else
251     {
252       entry = &APR_ARRAY_IDX(tracker->stack, tracker->depth,
253                              path_tracker_entry_t);
254     }
255
256   svn_stringbuf_set(entry->path, path);
257   ++tracker->depth;
258
259   return entry;
260 }
261
262 /* Update the TRACKER with a copy from COPYFROM_PATH@COPYFROM_REV to
263    PATH in the tracked revision.
264  */
265 static void
266 tracker_path_copy(path_tracker_t *tracker,
267                   const char *path,
268                   const char *copyfrom_path,
269                   svn_revnum_t copyfrom_rev)
270 {
271   path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
272
273   svn_stringbuf_set(entry->copyfrom_path, copyfrom_path);
274   entry->copyfrom_rev = copyfrom_rev;
275   entry->exists = TRUE;
276 }
277
278 /* Update the TRACKER with a plain addition of PATH (without history).
279  */
280 static void
281 tracker_path_add(path_tracker_t *tracker,
282                  const char *path)
283 {
284   path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
285
286   svn_stringbuf_setempty(entry->copyfrom_path);
287   entry->copyfrom_rev = SVN_INVALID_REVNUM;
288   entry->exists = TRUE;
289 }
290
291 /* Update the TRACKER with a replacement of PATH with a plain addition
292    (without history).
293  */
294 static void
295 tracker_path_replace(path_tracker_t *tracker,
296                      const char *path)
297 {
298   /* this will implicitly purge all previous sub-tree info from STACK.
299      Thus, no need to tack the deletion explicitly. */
300   tracker_path_add(tracker, path);
301 }
302
303 /* Update the TRACKER with a deletion of PATH.
304  */
305 static void
306 tracker_path_delete(path_tracker_t *tracker,
307                     const char *path)
308 {
309   path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
310
311   svn_stringbuf_setempty(entry->copyfrom_path);
312   entry->copyfrom_rev = SVN_INVALID_REVNUM;
313   entry->exists = FALSE;
314 }
315
316
317 /* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and
318    store it into a new temporary file *TEMPFILE.  OLDROOT may be NULL,
319    in which case the delta will be computed against an empty file, as
320    per the svn_fs_get_file_delta_stream docstring.  Record the length
321    of the temporary file in *LEN, and rewind the file before
322    returning. */
323 static svn_error_t *
324 store_delta(apr_file_t **tempfile, svn_filesize_t *len,
325             svn_fs_root_t *oldroot, const char *oldpath,
326             svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool)
327 {
328   svn_stream_t *temp_stream;
329   apr_off_t offset;
330   svn_txdelta_stream_t *delta_stream;
331   svn_txdelta_window_handler_t wh;
332   void *whb;
333
334   /* Create a temporary file and open a stream to it. Note that we need
335      the file handle in order to rewind it. */
336   SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL,
337                                    svn_io_file_del_on_pool_cleanup,
338                                    pool, pool));
339   temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool);
340
341   /* Compute the delta and send it to the temporary file. */
342   SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath,
343                                        newroot, newpath, pool));
344   svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0,
345                           SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
346   SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool));
347
348   /* Get the length of the temporary file and rewind it. */
349   SVN_ERR(svn_io_file_get_offset(&offset, *tempfile, pool));
350   *len = offset;
351   offset = 0;
352   return svn_io_file_seek(*tempfile, APR_SET, &offset, pool);
353 }
354
355
356 /* Send a notification of type #svn_repos_notify_warning, subtype WARNING,
357    with message WARNING_FMT formatted with the remaining variable arguments.
358    Send it by calling NOTIFY_FUNC (if not null) with NOTIFY_BATON.
359  */
360 __attribute__((format(printf, 5, 6)))
361 static void
362 notify_warning(apr_pool_t *scratch_pool,
363                svn_repos_notify_func_t notify_func,
364                void *notify_baton,
365                svn_repos_notify_warning_t warning,
366                const char *warning_fmt,
367                ...)
368 {
369   va_list va;
370   svn_repos_notify_t *notify;
371
372   if (notify_func == NULL)
373     return;
374
375   notify = svn_repos_notify_create(svn_repos_notify_warning, scratch_pool);
376   notify->warning = warning;
377   va_start(va, warning_fmt);
378   notify->warning_str = apr_pvsprintf(scratch_pool, warning_fmt, va);
379   va_end(va);
380
381   notify_func(notify_baton, notify, scratch_pool);
382 }
383 \f
384
385 /*----------------------------------------------------------------------*/
386 \f
387 /* Write to STREAM the header in HEADERS named KEY, if present.
388  */
389 static svn_error_t *
390 write_header(svn_stream_t *stream,
391              apr_hash_t *headers,
392              const char *key,
393              apr_pool_t *scratch_pool)
394 {
395   const char *val = svn_hash_gets(headers, key);
396
397   if (val)
398     {
399       SVN_ERR(svn_stream_printf(stream, scratch_pool,
400                                 "%s: %s\n", key, val));
401     }
402   return SVN_NO_ERROR;
403 }
404
405 /* Write headers, in arbitrary order.
406  * ### TODO: use a stable order
407  * ### Modifies HEADERS.
408  */
409 static svn_error_t *
410 write_revision_headers(svn_stream_t *stream,
411                        apr_hash_t *headers,
412                        apr_pool_t *scratch_pool)
413 {
414   const char **h;
415   apr_hash_index_t *hi;
416
417   static const char *revision_headers_order[] =
418   {
419     SVN_REPOS_DUMPFILE_REVISION_NUMBER,  /* must be first */
420     NULL
421   };
422
423   /* Write some headers in a given order */
424   for (h = revision_headers_order; *h; h++)
425     {
426       SVN_ERR(write_header(stream, headers, *h, scratch_pool));
427       svn_hash_sets(headers, *h, NULL);
428     }
429
430   /* Write any and all remaining headers except Content-length.
431    * ### TODO: use a stable order
432    */
433   for (hi = apr_hash_first(scratch_pool, headers); hi; hi = apr_hash_next(hi))
434     {
435       const char *key = apr_hash_this_key(hi);
436
437       if (strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH) != 0)
438         SVN_ERR(write_header(stream, headers, key, scratch_pool));
439     }
440
441   /* Content-length must be last */
442   SVN_ERR(write_header(stream, headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
443                        scratch_pool));
444
445   return SVN_NO_ERROR;
446 }
447
448 /* A header entry: the element type of the apr_array_header_t which is
449  * the real type of svn_repos__dumpfile_headers_t.
450  */
451 typedef struct svn_repos__dumpfile_header_entry_t {
452   const char *key, *val;
453 } svn_repos__dumpfile_header_entry_t;
454
455 svn_repos__dumpfile_headers_t *
456 svn_repos__dumpfile_headers_create(apr_pool_t *pool)
457 {
458   svn_repos__dumpfile_headers_t *headers
459     = apr_array_make(pool, 5, sizeof(svn_repos__dumpfile_header_entry_t));
460
461   return headers;
462 }
463
464 void
465 svn_repos__dumpfile_header_push(svn_repos__dumpfile_headers_t *headers,
466                                 const char *key,
467                                 const char *val)
468 {
469   svn_repos__dumpfile_header_entry_t *h
470     = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t);
471
472   h->key = apr_pstrdup(headers->pool, key);
473   h->val = apr_pstrdup(headers->pool, val);
474 }
475
476 void
477 svn_repos__dumpfile_header_pushf(svn_repos__dumpfile_headers_t *headers,
478                                  const char *key,
479                                  const char *val_fmt,
480                                  ...)
481 {
482   va_list ap;
483   svn_repos__dumpfile_header_entry_t *h
484     = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t);
485
486   h->key = apr_pstrdup(headers->pool, key);
487   va_start(ap, val_fmt);
488   h->val = apr_pvsprintf(headers->pool, val_fmt, ap);
489   va_end(ap);
490 }
491
492 svn_error_t *
493 svn_repos__dump_headers(svn_stream_t *stream,
494                         svn_repos__dumpfile_headers_t *headers,
495                         apr_pool_t *scratch_pool)
496 {
497   int i;
498
499   for (i = 0; i < headers->nelts; i++)
500     {
501       svn_repos__dumpfile_header_entry_t *h
502         = &APR_ARRAY_IDX(headers, i, svn_repos__dumpfile_header_entry_t);
503
504       SVN_ERR(svn_stream_printf(stream, scratch_pool,
505                                 "%s: %s\n", h->key, h->val));
506     }
507
508   /* End of headers */
509   SVN_ERR(svn_stream_puts(stream, "\n"));
510
511   return SVN_NO_ERROR;
512 }
513
514 svn_error_t *
515 svn_repos__dump_revision_record(svn_stream_t *dump_stream,
516                                 svn_revnum_t revision,
517                                 apr_hash_t *extra_headers,
518                                 apr_hash_t *revprops,
519                                 svn_boolean_t props_section_always,
520                                 apr_pool_t *scratch_pool)
521 {
522   svn_stringbuf_t *propstring = NULL;
523   apr_hash_t *headers;
524
525   if (extra_headers)
526     headers = apr_hash_copy(scratch_pool, extra_headers);
527   else
528     headers = apr_hash_make(scratch_pool);
529
530   /* ### someday write a revision-content-checksum */
531
532   svn_hash_sets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER,
533                 apr_psprintf(scratch_pool, "%ld", revision));
534
535   if (apr_hash_count(revprops) || props_section_always)
536     {
537       svn_stream_t *propstream;
538
539       propstring = svn_stringbuf_create_empty(scratch_pool);
540       propstream = svn_stream_from_stringbuf(propstring, scratch_pool);
541       SVN_ERR(svn_hash_write2(revprops, propstream, "PROPS-END", scratch_pool));
542       SVN_ERR(svn_stream_close(propstream));
543
544       svn_hash_sets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
545                     apr_psprintf(scratch_pool,
546                                  "%" APR_SIZE_T_FMT, propstring->len));
547     }
548
549   if (propstring)
550     {
551       /* Write out a regular Content-length header for the benefit of
552          non-Subversion RFC-822 parsers. */
553       svn_hash_sets(headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
554                     apr_psprintf(scratch_pool,
555                                  "%" APR_SIZE_T_FMT, propstring->len));
556     }
557
558   SVN_ERR(write_revision_headers(dump_stream, headers, scratch_pool));
559
560   /* End of headers */
561   SVN_ERR(svn_stream_puts(dump_stream, "\n"));
562
563   /* Property data. */
564   if (propstring)
565     {
566       SVN_ERR(svn_stream_write(dump_stream, propstring->data, &propstring->len));
567     }
568
569   /* put an end to revision */
570   SVN_ERR(svn_stream_puts(dump_stream, "\n"));
571
572   return SVN_NO_ERROR;
573 }
574
575 svn_error_t *
576 svn_repos__dump_node_record(svn_stream_t *dump_stream,
577                             svn_repos__dumpfile_headers_t *headers,
578                             svn_stringbuf_t *props_str,
579                             svn_boolean_t has_text,
580                             svn_filesize_t text_content_length,
581                             svn_boolean_t content_length_always,
582                             apr_pool_t *scratch_pool)
583 {
584   svn_filesize_t content_length = 0;
585
586   /* add content-length headers */
587   if (props_str)
588     {
589       svn_repos__dumpfile_header_pushf(
590         headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
591         "%" APR_SIZE_T_FMT, props_str->len);
592       content_length += props_str->len;
593     }
594   if (has_text)
595     {
596       svn_repos__dumpfile_header_pushf(
597         headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH,
598         "%" SVN_FILESIZE_T_FMT, text_content_length);
599       content_length += text_content_length;
600     }
601   if (content_length_always || props_str || has_text)
602     {
603       svn_repos__dumpfile_header_pushf(
604         headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
605         "%" SVN_FILESIZE_T_FMT, content_length);
606     }
607
608   /* write the headers */
609   SVN_ERR(svn_repos__dump_headers(dump_stream, headers, scratch_pool));
610
611   /* write the props */
612   if (props_str)
613     {
614       SVN_ERR(svn_stream_write(dump_stream, props_str->data, &props_str->len));
615     }
616   return SVN_NO_ERROR;
617 }
618
619 /*----------------------------------------------------------------------*/
620 \f
621 /** An editor which dumps node-data in 'dumpfile format' to a file. **/
622
623 /* Look, mom!  No file batons! */
624
625 struct edit_baton
626 {
627   /* The relpath which implicitly prepends all full paths coming into
628      this editor.  This will almost always be "".  */
629   const char *path;
630
631   /* The stream to dump to. */
632   svn_stream_t *stream;
633
634   /* Send feedback here, if non-NULL */
635   svn_repos_notify_func_t notify_func;
636   void *notify_baton;
637
638   /* The fs revision root, so we can read the contents of paths. */
639   svn_fs_root_t *fs_root;
640   svn_revnum_t current_rev;
641
642   /* The fs, so we can grab historic information if needed. */
643   svn_fs_t *fs;
644
645   /* True if dumped nodes should output deltas instead of full text. */
646   svn_boolean_t use_deltas;
647
648   /* True if this "dump" is in fact a verify. */
649   svn_boolean_t verify;
650
651   /* True if checking UCS normalization during a verify. */
652   svn_boolean_t check_normalization;
653
654   /* The first revision dumped in this dumpstream. */
655   svn_revnum_t oldest_dumped_rev;
656
657   /* If not NULL, set to true if any references to revisions older than
658      OLDEST_DUMPED_REV were found in the dumpstream. */
659   svn_boolean_t *found_old_reference;
660
661   /* If not NULL, set to true if any mergeinfo was dumped which contains
662      revisions older than OLDEST_DUMPED_REV. */
663   svn_boolean_t *found_old_mergeinfo;
664
665   /* Structure allows us to verify the paths currently being dumped.
666      If NULL, validity checks are being skipped. */
667   path_tracker_t *path_tracker;
668 };
669
670 struct dir_baton
671 {
672   struct edit_baton *edit_baton;
673
674   /* has this directory been written to the output stream? */
675   svn_boolean_t written_out;
676
677   /* the repository relpath associated with this directory */
678   const char *path;
679
680   /* The comparison repository relpath and revision of this directory.
681      If both of these are valid, use them as a source against which to
682      compare the directory instead of the default comparison source of
683      PATH in the previous revision. */
684   const char *cmp_path;
685   svn_revnum_t cmp_rev;
686
687   /* hash of paths that need to be deleted, though some -might- be
688      replaced.  maps const char * paths to this dir_baton.  (they're
689      full paths, because that's what the editor driver gives us.  but
690      really, they're all within this directory.) */
691   apr_hash_t *deleted_entries;
692
693   /* A flag indicating that new entries have been added to this
694      directory in this revision. Used to optimize detection of UCS
695      representation collisions; we will only check for that in
696      revisions where new names appear in the directory. */
697   svn_boolean_t check_name_collision;
698
699   /* pool to be used for deleting the hash items */
700   apr_pool_t *pool;
701 };
702
703
704 /* Make a directory baton to represent the directory was path
705    (relative to EDIT_BATON's path) is PATH.
706
707    CMP_PATH/CMP_REV are the path/revision against which this directory
708    should be compared for changes.  If either is omitted (NULL for the
709    path, SVN_INVALID_REVNUM for the rev), just compare this directory
710    PATH against itself in the previous revision.
711
712    PB is the directory baton of this directory's parent,
713    or NULL if this is the top-level directory of the edit.
714
715    Perform all allocations in POOL.  */
716 static struct dir_baton *
717 make_dir_baton(const char *path,
718                const char *cmp_path,
719                svn_revnum_t cmp_rev,
720                void *edit_baton,
721                struct dir_baton *pb,
722                apr_pool_t *pool)
723 {
724   struct edit_baton *eb = edit_baton;
725   struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
726   const char *full_path;
727
728   /* A path relative to nothing?  I don't think so. */
729   SVN_ERR_ASSERT_NO_RETURN(!path || pb);
730
731   /* Construct the full path of this node. */
732   if (pb)
733     full_path = svn_relpath_join(eb->path, path, pool);
734   else
735     full_path = apr_pstrdup(pool, eb->path);
736
737   /* Remove leading slashes from copyfrom paths. */
738   if (cmp_path)
739     cmp_path = svn_relpath_canonicalize(cmp_path, pool);
740
741   new_db->edit_baton = eb;
742   new_db->path = full_path;
743   new_db->cmp_path = cmp_path;
744   new_db->cmp_rev = cmp_rev;
745   new_db->written_out = FALSE;
746   new_db->deleted_entries = apr_hash_make(pool);
747   new_db->check_name_collision = FALSE;
748   new_db->pool = pool;
749
750   return new_db;
751 }
752
753 static svn_error_t *
754 fetch_kind_func(svn_node_kind_t *kind,
755                 void *baton,
756                 const char *path,
757                 svn_revnum_t base_revision,
758                 apr_pool_t *scratch_pool);
759
760 /* Return an error when PATH in REVISION does not exist or is of a
761    different kind than EXPECTED_KIND.  If the latter is svn_node_unknown,
762    skip that check.  Use EB for context information.  If REVISION is the
763    current revision, use EB's path tracker to follow renames, deletions,
764    etc.
765
766    Use SCRATCH_POOL for temporary allocations.
767    No-op if EB's path tracker has not been initialized.
768  */
769 static svn_error_t *
770 node_must_exist(struct edit_baton *eb,
771                 const char *path,
772                 svn_revnum_t revision,
773                 svn_node_kind_t expected_kind,
774                 apr_pool_t *scratch_pool)
775 {
776   svn_node_kind_t kind = svn_node_none;
777
778   /* in case the caller is trying something stupid ... */
779   if (eb->path_tracker == NULL)
780     return SVN_NO_ERROR;
781
782   /* paths pertaining to the revision currently being processed must
783      be translated / checked using our path tracker. */
784   if (revision == eb->path_tracker->revision)
785     tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
786
787   /* determine the node type (default: no such node) */
788   if (path)
789     SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
790
791   /* check results */
792   if (kind == svn_node_none)
793     return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
794                              _("Path '%s' not found in r%ld."),
795                              path, revision);
796
797   if (expected_kind != kind && expected_kind != svn_node_unknown)
798     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
799                              _("Unexpected node kind %d for '%s' at r%ld. "
800                                "Expected kind was %d."),
801                              kind, path, revision, expected_kind);
802
803   return SVN_NO_ERROR;
804 }
805
806 /* Return an error when PATH exists in REVISION.  Use EB for context
807    information.  If REVISION is the current revision, use EB's path
808    tracker to follow renames, deletions, etc.
809
810    Use SCRATCH_POOL for temporary allocations.
811    No-op if EB's path tracker has not been initialized.
812  */
813 static svn_error_t *
814 node_must_not_exist(struct edit_baton *eb,
815                     const char *path,
816                     svn_revnum_t revision,
817                     apr_pool_t *scratch_pool)
818 {
819   svn_node_kind_t kind = svn_node_none;
820
821   /* in case the caller is trying something stupid ... */
822   if (eb->path_tracker == NULL)
823     return SVN_NO_ERROR;
824
825   /* paths pertaining to the revision currently being processed must
826      be translated / checked using our path tracker. */
827   if (revision == eb->path_tracker->revision)
828     tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
829
830   /* determine the node type (default: no such node) */
831   if (path)
832     SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
833
834   /* check results */
835   if (kind != svn_node_none)
836     return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
837                              _("Path '%s' exists in r%ld."),
838                              path, revision);
839
840   return SVN_NO_ERROR;
841 }
842
843 /* If the mergeinfo in MERGEINFO_STR refers to any revisions older than
844  * OLDEST_DUMPED_REV, issue a warning and set *FOUND_OLD_MERGEINFO to TRUE,
845  * otherwise leave *FOUND_OLD_MERGEINFO unchanged.
846  */
847 static svn_error_t *
848 verify_mergeinfo_revisions(svn_boolean_t *found_old_mergeinfo,
849                            const char *mergeinfo_str,
850                            svn_revnum_t oldest_dumped_rev,
851                            svn_repos_notify_func_t notify_func,
852                            void *notify_baton,
853                            apr_pool_t *pool)
854 {
855   svn_mergeinfo_t mergeinfo, old_mergeinfo;
856
857   SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str, pool));
858   SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
859             &old_mergeinfo, mergeinfo,
860             oldest_dumped_rev - 1, 0,
861             TRUE, pool, pool));
862
863   if (apr_hash_count(old_mergeinfo))
864     {
865       notify_warning(pool, notify_func, notify_baton,
866                      svn_repos_notify_warning_found_old_mergeinfo,
867                      _("Mergeinfo referencing revision(s) prior "
868                        "to the oldest dumped revision (r%ld). "
869                        "Loading this dump may result in invalid "
870                        "mergeinfo."),
871                      oldest_dumped_rev);
872
873       if (found_old_mergeinfo)
874         *found_old_mergeinfo = TRUE;
875     }
876
877   return SVN_NO_ERROR;
878 }
879
880 /* Unique string pointers used by verify_mergeinfo_normalization()
881    and check_name_collision() */
882 static const char normalized_unique[] = "normalized_unique";
883 static const char normalized_collision[] = "normalized_collision";
884
885
886 /* Baton for extract_mergeinfo_paths */
887 struct extract_mergeinfo_paths_baton
888 {
889   apr_hash_t *result;
890   svn_boolean_t normalize;
891   svn_membuf_t buffer;
892 };
893
894 /* Hash iterator that uniquifies all keys into a single hash table,
895    optionally normalizing them first. */
896 static svn_error_t *
897 extract_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
898                          void *val, apr_pool_t *iterpool)
899 {
900   struct extract_mergeinfo_paths_baton *const xb = baton;
901   if (xb->normalize)
902     {
903       const char *normkey;
904       SVN_ERR(svn_utf__normalize(&normkey, key, klen, &xb->buffer));
905       svn_hash_sets(xb->result,
906                     apr_pstrdup(xb->buffer.pool, normkey),
907                     normalized_unique);
908     }
909   else
910     apr_hash_set(xb->result,
911                  apr_pmemdup(xb->buffer.pool, key, klen + 1), klen,
912                  normalized_unique);
913   return SVN_NO_ERROR;
914 }
915
916 /* Baton for filter_mergeinfo_paths */
917 struct filter_mergeinfo_paths_baton
918 {
919   apr_hash_t *paths;
920 };
921
922 /* Compare two sets of denormalized paths from mergeinfo entries,
923    removing duplicates. */
924 static svn_error_t *
925 filter_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
926                        void *val, apr_pool_t *iterpool)
927 {
928   struct filter_mergeinfo_paths_baton *const fb = baton;
929
930   if (apr_hash_get(fb->paths, key, klen))
931     apr_hash_set(fb->paths, key, klen, NULL);
932
933   return SVN_NO_ERROR;
934 }
935
936 /* Baton used by the check_mergeinfo_normalization hash iterator. */
937 struct verify_mergeinfo_normalization_baton
938 {
939   const char* path;
940   apr_hash_t *normalized_paths;
941   svn_membuf_t buffer;
942   svn_repos_notify_func_t notify_func;
943   void *notify_baton;
944 };
945
946 /* Hash iterator that verifies normalization and collision of paths in
947    an svn:mergeinfo property. */
948 static svn_error_t *
949 verify_mergeinfo_normalization(void *baton, const void *key, apr_ssize_t klen,
950                                void *val, apr_pool_t *iterpool)
951 {
952   struct verify_mergeinfo_normalization_baton *const vb = baton;
953
954   const char *const path = key;
955   const char *normpath;
956   const char *found;
957
958   SVN_ERR(svn_utf__normalize(&normpath, path, klen, &vb->buffer));
959   found = svn_hash_gets(vb->normalized_paths, normpath);
960   if (!found)
961       svn_hash_sets(vb->normalized_paths,
962                     apr_pstrdup(vb->buffer.pool, normpath),
963                     normalized_unique);
964   else if (found == normalized_collision)
965     /* Skip already reported collision */;
966   else
967     {
968       /* Report path collision in mergeinfo */
969       svn_hash_sets(vb->normalized_paths,
970                     apr_pstrdup(vb->buffer.pool, normpath),
971                     normalized_collision);
972
973       notify_warning(iterpool, vb->notify_func, vb->notify_baton,
974                      svn_repos_notify_warning_mergeinfo_collision,
975                      _("Duplicate representation of path '%s'"
976                        " in %s property of '%s'"),
977                      normpath, SVN_PROP_MERGEINFO, vb->path);
978     }
979   return SVN_NO_ERROR;
980 }
981
982 /* Check UCS normalization of mergeinfo for PATH. NEW_MERGEINFO is the
983    svn:mergeinfo property value being set; OLD_MERGEINFO is the
984    previous property value, which may be NULL. Only the paths that
985    were added in are checked, including collision checks. This
986    minimizes the number of notifications we generate for a given
987    mergeinfo property. */
988 static svn_error_t *
989 check_mergeinfo_normalization(const char *path,
990                               const char *new_mergeinfo,
991                               const char *old_mergeinfo,
992                               svn_repos_notify_func_t notify_func,
993                               void *notify_baton,
994                               apr_pool_t *pool)
995 {
996   svn_mergeinfo_t mergeinfo;
997   apr_hash_t *normalized_paths;
998   apr_hash_t *added_paths;
999   struct extract_mergeinfo_paths_baton extract_baton;
1000   struct verify_mergeinfo_normalization_baton verify_baton;
1001
1002   SVN_ERR(svn_mergeinfo_parse(&mergeinfo, new_mergeinfo, pool));
1003
1004   extract_baton.result = apr_hash_make(pool);
1005   extract_baton.normalize = FALSE;
1006   svn_membuf__create(&extract_baton.buffer, 0, pool);
1007   SVN_ERR(svn_iter_apr_hash(NULL, mergeinfo,
1008                             extract_mergeinfo_paths,
1009                             &extract_baton, pool));
1010   added_paths = extract_baton.result;
1011
1012   if (old_mergeinfo)
1013     {
1014       struct filter_mergeinfo_paths_baton filter_baton;
1015       svn_mergeinfo_t oldinfo;
1016
1017       extract_baton.result = apr_hash_make(pool);
1018       extract_baton.normalize = TRUE;
1019       SVN_ERR(svn_mergeinfo_parse(&oldinfo, old_mergeinfo, pool));
1020       SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
1021                                 extract_mergeinfo_paths,
1022                                 &extract_baton, pool));
1023       normalized_paths = extract_baton.result;
1024
1025       filter_baton.paths = added_paths;
1026       SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
1027                                 filter_mergeinfo_paths,
1028                                 &filter_baton, pool));
1029     }
1030   else
1031       normalized_paths = apr_hash_make(pool);
1032
1033   verify_baton.path = path;
1034   verify_baton.normalized_paths = normalized_paths;
1035   verify_baton.buffer = extract_baton.buffer;
1036   verify_baton.notify_func = notify_func;
1037   verify_baton.notify_baton = notify_baton;
1038   SVN_ERR(svn_iter_apr_hash(NULL, added_paths,
1039                             verify_mergeinfo_normalization,
1040                             &verify_baton, pool));
1041
1042   return SVN_NO_ERROR;
1043 }
1044
1045
1046 /* A special case of dump_node(), for a delete record.
1047  *
1048  * The only thing special about this version is it only writes one blank
1049  * line, not two, after the headers. Why? Historical precedent for the
1050  * case where a delete record is used as part of a (delete + add-with-history)
1051  * in implementing a replacement.
1052  *
1053  * Also it doesn't do a path-tracker check.
1054  */
1055 static svn_error_t *
1056 dump_node_delete(svn_stream_t *stream,
1057                  const char *node_relpath,
1058                  apr_pool_t *pool)
1059 {
1060   svn_repos__dumpfile_headers_t *headers
1061     = svn_repos__dumpfile_headers_create(pool);
1062
1063   /* Node-path: ... */
1064   svn_repos__dumpfile_header_push(
1065     headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
1066
1067   /* Node-action: delete */
1068   svn_repos__dumpfile_header_push(
1069     headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1070
1071   SVN_ERR(svn_repos__dump_headers(stream, headers, pool));
1072   return SVN_NO_ERROR;
1073 }
1074
1075 /* This helper is the main "meat" of the editor -- it does all the
1076    work of writing a node record.
1077
1078    Write out a node record for PATH of type KIND under EB->FS_ROOT.
1079    ACTION describes what is happening to the node (see enum svn_node_action).
1080    Write record to writable EB->STREAM.
1081
1082    If the node was itself copied, IS_COPY is TRUE and the
1083    path/revision of the copy source are in CMP_PATH/CMP_REV.  If
1084    IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
1085    of a copied subtree.
1086   */
1087 static svn_error_t *
1088 dump_node(struct edit_baton *eb,
1089           const char *path,
1090           svn_node_kind_t kind,
1091           enum svn_node_action action,
1092           svn_boolean_t is_copy,
1093           const char *cmp_path,
1094           svn_revnum_t cmp_rev,
1095           apr_pool_t *pool)
1096 {
1097   svn_stringbuf_t *propstring;
1098   apr_size_t len;
1099   svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
1100   const char *compare_path = path;
1101   svn_revnum_t compare_rev = eb->current_rev - 1;
1102   svn_fs_root_t *compare_root = NULL;
1103   apr_file_t *delta_file = NULL;
1104   svn_repos__dumpfile_headers_t *headers
1105     = svn_repos__dumpfile_headers_create(pool);
1106   svn_filesize_t textlen;
1107
1108   /* Maybe validate the path. */
1109   if (eb->verify || eb->notify_func)
1110     {
1111       svn_error_t *err = svn_fs__path_valid(path, pool);
1112
1113       if (err)
1114         {
1115           if (eb->notify_func)
1116             {
1117               char errbuf[512]; /* ### svn_strerror() magic number  */
1118
1119               notify_warning(pool, eb->notify_func, eb->notify_baton,
1120                              svn_repos_notify_warning_invalid_fspath,
1121                              _("E%06d: While validating fspath '%s': %s"),
1122                              err->apr_err, path,
1123                              svn_err_best_message(err, errbuf, sizeof(errbuf)));
1124             }
1125
1126           /* Return the error in addition to notifying about it. */
1127           if (eb->verify)
1128             return svn_error_trace(err);
1129           else
1130             svn_error_clear(err);
1131         }
1132     }
1133
1134   /* Write out metadata headers for this file node. */
1135   svn_repos__dumpfile_header_push(
1136     headers, SVN_REPOS_DUMPFILE_NODE_PATH, path);
1137   if (kind == svn_node_file)
1138     svn_repos__dumpfile_header_push(
1139       headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file");
1140   else if (kind == svn_node_dir)
1141     svn_repos__dumpfile_header_push(
1142       headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir");
1143
1144   /* Remove leading slashes from copyfrom paths. */
1145   if (cmp_path)
1146     cmp_path = svn_relpath_canonicalize(cmp_path, pool);
1147
1148   /* Validate the comparison path/rev. */
1149   if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
1150     {
1151       compare_path = cmp_path;
1152       compare_rev = cmp_rev;
1153     }
1154
1155   switch (action)
1156     {
1157     case svn_node_action_change:
1158       if (eb->path_tracker)
1159         SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
1160                   apr_psprintf(pool, _("Change invalid path '%s' in r%ld"),
1161                                path, eb->current_rev));
1162
1163       svn_repos__dumpfile_header_push(
1164         headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change");
1165
1166       /* either the text or props changed, or possibly both. */
1167       SVN_ERR(svn_fs_revision_root(&compare_root,
1168                                    svn_fs_root_fs(eb->fs_root),
1169                                    compare_rev, pool));
1170
1171       SVN_ERR(svn_fs_props_changed(&must_dump_props,
1172                                    compare_root, compare_path,
1173                                    eb->fs_root, path, pool));
1174       if (kind == svn_node_file)
1175         SVN_ERR(svn_fs_contents_changed(&must_dump_text,
1176                                         compare_root, compare_path,
1177                                         eb->fs_root, path, pool));
1178       break;
1179
1180     case svn_node_action_delete:
1181       if (eb->path_tracker)
1182         {
1183           SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
1184                     apr_psprintf(pool, _("Deleting invalid path '%s' in r%ld"),
1185                                  path, eb->current_rev));
1186           tracker_path_delete(eb->path_tracker, path);
1187         }
1188
1189       svn_repos__dumpfile_header_push(
1190         headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1191
1192       /* we can leave this routine quietly now, don't need to dump
1193          any content. */
1194       must_dump_text = FALSE;
1195       must_dump_props = FALSE;
1196       break;
1197
1198     case svn_node_action_replace:
1199       if (eb->path_tracker)
1200         SVN_ERR_W(node_must_exist(eb, path, eb->current_rev,
1201                                   svn_node_unknown, pool),
1202                   apr_psprintf(pool,
1203                                _("Replacing non-existent path '%s' in r%ld"),
1204                                path, eb->current_rev));
1205
1206       if (! is_copy)
1207         {
1208           if (eb->path_tracker)
1209             tracker_path_replace(eb->path_tracker, path);
1210
1211           /* a simple delete+add, implied by a single 'replace' action. */
1212           svn_repos__dumpfile_header_push(
1213             headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace");
1214
1215           /* definitely need to dump all content for a replace. */
1216           if (kind == svn_node_file)
1217             must_dump_text = TRUE;
1218           must_dump_props = TRUE;
1219           break;
1220         }
1221       else
1222         {
1223           /* more complex:  delete original, then add-with-history.  */
1224           /* ### Why not write a 'replace' record? Don't know. */
1225
1226           if (eb->path_tracker)
1227             {
1228               tracker_path_delete(eb->path_tracker, path);
1229             }
1230
1231           /* ### Unusually, we end this 'delete' node record with only a single
1232                  blank line after the header block -- no extra blank line. */
1233           SVN_ERR(dump_node_delete(eb->stream, path, pool));
1234
1235           /* The remaining action is a non-replacing add-with-history */
1236           /* action = svn_node_action_add; */
1237         }
1238       /* FALL THROUGH to 'add' */
1239
1240     case svn_node_action_add:
1241       if (eb->path_tracker)
1242         SVN_ERR_W(node_must_not_exist(eb, path, eb->current_rev, pool),
1243                   apr_psprintf(pool,
1244                                _("Adding already existing path '%s' in r%ld"),
1245                                path, eb->current_rev));
1246
1247       svn_repos__dumpfile_header_push(
1248         headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
1249
1250       if (! is_copy)
1251         {
1252           if (eb->path_tracker)
1253             tracker_path_add(eb->path_tracker, path);
1254
1255           /* Dump all contents for a simple 'add'. */
1256           if (kind == svn_node_file)
1257             must_dump_text = TRUE;
1258           must_dump_props = TRUE;
1259         }
1260       else
1261         {
1262           if (eb->path_tracker)
1263             {
1264               SVN_ERR_W(node_must_exist(eb, compare_path, compare_rev,
1265                                         kind, pool),
1266                         apr_psprintf(pool,
1267                                      _("Copying from invalid path to "
1268                                        "'%s' in r%ld"),
1269                                      path, eb->current_rev));
1270               tracker_path_copy(eb->path_tracker, path, compare_path,
1271                                 compare_rev);
1272             }
1273
1274           if (!eb->verify && cmp_rev < eb->oldest_dumped_rev
1275               && eb->notify_func)
1276             {
1277               notify_warning(pool, eb->notify_func, eb->notify_baton,
1278                              svn_repos_notify_warning_found_old_reference,
1279                              _("Referencing data in revision %ld,"
1280                                " which is older than the oldest"
1281                                " dumped revision (r%ld).  Loading this dump"
1282                                " into an empty repository"
1283                                " will fail."),
1284                              cmp_rev, eb->oldest_dumped_rev);
1285               if (eb->found_old_reference)
1286                 *eb->found_old_reference = TRUE;
1287             }
1288
1289           svn_repos__dumpfile_header_pushf(
1290             headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", cmp_rev);
1291           svn_repos__dumpfile_header_push(
1292             headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, cmp_path);
1293
1294           SVN_ERR(svn_fs_revision_root(&compare_root,
1295                                        svn_fs_root_fs(eb->fs_root),
1296                                        compare_rev, pool));
1297
1298           /* Need to decide if the copied node had any extra textual or
1299              property mods as well.  */
1300           SVN_ERR(svn_fs_props_changed(&must_dump_props,
1301                                        compare_root, compare_path,
1302                                        eb->fs_root, path, pool));
1303           if (kind == svn_node_file)
1304             {
1305               svn_checksum_t *checksum;
1306               const char *hex_digest;
1307               SVN_ERR(svn_fs_contents_changed(&must_dump_text,
1308                                               compare_root, compare_path,
1309                                               eb->fs_root, path, pool));
1310
1311               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1312                                            compare_root, compare_path,
1313                                            FALSE, pool));
1314               hex_digest = svn_checksum_to_cstring(checksum, pool);
1315               if (hex_digest)
1316                 svn_repos__dumpfile_header_push(
1317                   headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5, hex_digest);
1318
1319               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1320                                            compare_root, compare_path,
1321                                            FALSE, pool));
1322               hex_digest = svn_checksum_to_cstring(checksum, pool);
1323               if (hex_digest)
1324                 svn_repos__dumpfile_header_push(
1325                   headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1, hex_digest);
1326             }
1327         }
1328       break;
1329     }
1330
1331   if ((! must_dump_text) && (! must_dump_props))
1332     {
1333       /* If we're not supposed to dump text or props, so be it, we can
1334          just go home.  However, if either one needs to be dumped,
1335          then our dumpstream format demands that at a *minimum*, we
1336          see a lone "PROPS-END" as a divider between text and props
1337          content within the content-block. */
1338       SVN_ERR(svn_repos__dump_headers(eb->stream, headers, pool));
1339       len = 1;
1340       return svn_stream_write(eb->stream, "\n", &len); /* ### needed? */
1341     }
1342
1343   /*** Start prepping content to dump... ***/
1344
1345   /* If we are supposed to dump properties, write out a property
1346      length header and generate a stringbuf that contains those
1347      property values here. */
1348   if (must_dump_props)
1349     {
1350       apr_hash_t *prophash, *oldhash = NULL;
1351       svn_stream_t *propstream;
1352
1353       SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
1354
1355       /* If this is a partial dump, then issue a warning if we dump mergeinfo
1356          properties that refer to revisions older than the first revision
1357          dumped. */
1358       if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1)
1359         {
1360           svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
1361                                                       SVN_PROP_MERGEINFO);
1362           if (mergeinfo_str)
1363             {
1364               /* An error in verifying the mergeinfo must not prevent dumping
1365                  the data. Ignore any such error. */
1366               svn_error_clear(verify_mergeinfo_revisions(
1367                                 eb->found_old_mergeinfo,
1368                                 mergeinfo_str->data, eb->oldest_dumped_rev,
1369                                 eb->notify_func, eb->notify_baton,
1370                                 pool));
1371             }
1372         }
1373
1374       /* If we're checking UCS normalization, also parse any changed
1375          mergeinfo and warn about denormalized paths and name
1376          collisions there. */
1377       if (eb->verify && eb->check_normalization && eb->notify_func)
1378         {
1379           /* N.B.: This hash lookup happens only once; the conditions
1380              for verifying historic mergeinfo references and checking
1381              UCS normalization are mutually exclusive. */
1382           svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
1383                                                       SVN_PROP_MERGEINFO);
1384           if (mergeinfo_str)
1385             {
1386               svn_string_t *oldinfo_str = NULL;
1387               if (compare_root)
1388                 {
1389                   SVN_ERR(svn_fs_node_proplist(&oldhash,
1390                                                compare_root, compare_path,
1391                                                pool));
1392                   oldinfo_str = svn_hash_gets(oldhash, SVN_PROP_MERGEINFO);
1393                 }
1394               SVN_ERR(check_mergeinfo_normalization(
1395                           path, mergeinfo_str->data,
1396                           (oldinfo_str ? oldinfo_str->data : NULL),
1397                           eb->notify_func, eb->notify_baton, pool));
1398             }
1399         }
1400
1401       if (eb->use_deltas && compare_root)
1402         {
1403           /* Fetch the old property hash to diff against and output a header
1404              saying that our property contents are a delta. */
1405           if (!oldhash)         /* May have been set for normalization check */
1406             SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
1407                                          pool));
1408           svn_repos__dumpfile_header_push(
1409             headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true");
1410         }
1411       else
1412         oldhash = apr_hash_make(pool);
1413       propstring = svn_stringbuf_create_ensure(0, pool);
1414       propstream = svn_stream_from_stringbuf(propstring, pool);
1415       SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream,
1416                                          "PROPS-END", pool));
1417       SVN_ERR(svn_stream_close(propstream));
1418     }
1419
1420   /* If we are supposed to dump text, write out a text length header
1421      here, and an MD5 checksum (if available). */
1422   if (must_dump_text && (kind == svn_node_file))
1423     {
1424       svn_checksum_t *checksum;
1425       const char *hex_digest;
1426
1427       if (eb->use_deltas)
1428         {
1429           /* Compute the text delta now and write it into a temporary
1430              file, so that we can find its length.  Output a header
1431              saying our text contents are a delta. */
1432           SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
1433                               compare_path, eb->fs_root, path, pool));
1434           svn_repos__dumpfile_header_push(
1435             headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true");
1436
1437           if (compare_root)
1438             {
1439               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1440                                            compare_root, compare_path,
1441                                            FALSE, pool));
1442               hex_digest = svn_checksum_to_cstring(checksum, pool);
1443               if (hex_digest)
1444                 svn_repos__dumpfile_header_push(
1445                   headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, hex_digest);
1446
1447               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1448                                            compare_root, compare_path,
1449                                            FALSE, pool));
1450               hex_digest = svn_checksum_to_cstring(checksum, pool);
1451               if (hex_digest)
1452                 svn_repos__dumpfile_header_push(
1453                   headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1, hex_digest);
1454             }
1455         }
1456       else
1457         {
1458           /* Just fetch the length of the file. */
1459           SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
1460         }
1461
1462       SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1463                                    eb->fs_root, path, FALSE, pool));
1464       hex_digest = svn_checksum_to_cstring(checksum, pool);
1465       if (hex_digest)
1466         svn_repos__dumpfile_header_push(
1467           headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, hex_digest);
1468
1469       SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1470                                    eb->fs_root, path, FALSE, pool));
1471       hex_digest = svn_checksum_to_cstring(checksum, pool);
1472       if (hex_digest)
1473         svn_repos__dumpfile_header_push(
1474           headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1, hex_digest);
1475     }
1476
1477   /* 'Content-length:' is the last header before we dump the content,
1478      and is the sum of the text and prop contents lengths.  We write
1479      this only for the benefit of non-Subversion RFC-822 parsers. */
1480   SVN_ERR(svn_repos__dump_node_record(eb->stream, headers,
1481                                       must_dump_props ? propstring : NULL,
1482                                       must_dump_text,
1483                                       must_dump_text ? textlen : 0,
1484                                       TRUE /*content_length_always*/,
1485                                       pool));
1486
1487   /* Dump text content */
1488   if (must_dump_text && (kind == svn_node_file))
1489     {
1490       svn_stream_t *contents;
1491
1492       if (delta_file)
1493         {
1494           /* Make sure to close the underlying file when the stream is
1495              closed. */
1496           contents = svn_stream_from_aprfile2(delta_file, FALSE, pool);
1497         }
1498       else
1499         SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
1500
1501       SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool),
1502                                NULL, NULL, pool));
1503     }
1504
1505   len = 2;
1506   return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
1507 }
1508
1509
1510 static svn_error_t *
1511 open_root(void *edit_baton,
1512           svn_revnum_t base_revision,
1513           apr_pool_t *pool,
1514           void **root_baton)
1515 {
1516   *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
1517                                edit_baton, NULL, pool);
1518   return SVN_NO_ERROR;
1519 }
1520
1521
1522 static svn_error_t *
1523 delete_entry(const char *path,
1524              svn_revnum_t revision,
1525              void *parent_baton,
1526              apr_pool_t *pool)
1527 {
1528   struct dir_baton *pb = parent_baton;
1529   const char *mypath = apr_pstrdup(pb->pool, path);
1530
1531   /* remember this path needs to be deleted. */
1532   svn_hash_sets(pb->deleted_entries, mypath, pb);
1533
1534   return SVN_NO_ERROR;
1535 }
1536
1537
1538 static svn_error_t *
1539 add_directory(const char *path,
1540               void *parent_baton,
1541               const char *copyfrom_path,
1542               svn_revnum_t copyfrom_rev,
1543               apr_pool_t *pool,
1544               void **child_baton)
1545 {
1546   struct dir_baton *pb = parent_baton;
1547   struct edit_baton *eb = pb->edit_baton;
1548   void *was_deleted;
1549   svn_boolean_t is_copy = FALSE;
1550   struct dir_baton *new_db
1551     = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, pool);
1552
1553   /* This might be a replacement -- is the path already deleted? */
1554   was_deleted = svn_hash_gets(pb->deleted_entries, path);
1555
1556   /* Detect an add-with-history. */
1557   is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1558
1559   /* Dump the node. */
1560   SVN_ERR(dump_node(eb, path,
1561                     svn_node_dir,
1562                     was_deleted ? svn_node_action_replace : svn_node_action_add,
1563                     is_copy,
1564                     is_copy ? copyfrom_path : NULL,
1565                     is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1566                     pool));
1567
1568   if (was_deleted)
1569     /* Delete the path, it's now been dumped. */
1570     svn_hash_sets(pb->deleted_entries, path, NULL);
1571
1572   /* Check for normalized name clashes, but only if this is actually a
1573      new name in the parent, not a replacement. */
1574   if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
1575     {
1576       pb->check_name_collision = TRUE;
1577     }
1578
1579   new_db->written_out = TRUE;
1580
1581   *child_baton = new_db;
1582   return SVN_NO_ERROR;
1583 }
1584
1585
1586 static svn_error_t *
1587 open_directory(const char *path,
1588                void *parent_baton,
1589                svn_revnum_t base_revision,
1590                apr_pool_t *pool,
1591                void **child_baton)
1592 {
1593   struct dir_baton *pb = parent_baton;
1594   struct edit_baton *eb = pb->edit_baton;
1595   struct dir_baton *new_db;
1596   const char *cmp_path = NULL;
1597   svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
1598
1599   /* If the parent directory has explicit comparison path and rev,
1600      record the same for this one. */
1601   if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
1602     {
1603       cmp_path = svn_relpath_join(pb->cmp_path,
1604                                   svn_relpath_basename(path, pool), pool);
1605       cmp_rev = pb->cmp_rev;
1606     }
1607
1608   new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, pool);
1609   *child_baton = new_db;
1610   return SVN_NO_ERROR;
1611 }
1612
1613
1614 static svn_error_t *
1615 close_directory(void *dir_baton,
1616                 apr_pool_t *pool)
1617 {
1618   struct dir_baton *db = dir_baton;
1619   struct edit_baton *eb = db->edit_baton;
1620   apr_pool_t *subpool = svn_pool_create(pool);
1621   int i;
1622   apr_array_header_t *sorted_entries;
1623
1624   /* Sort entries lexically instead of as paths. Even though the entries
1625    * are full paths they're all in the same directory (see comment in struct
1626    * dir_baton definition). So we really want to sort by basename, in which
1627    * case the lexical sort function is more efficient. */
1628   sorted_entries = svn_sort__hash(db->deleted_entries,
1629                                   svn_sort_compare_items_lexically, pool);
1630   for (i = 0; i < sorted_entries->nelts; i++)
1631     {
1632       const char *path = APR_ARRAY_IDX(sorted_entries, i,
1633                                        svn_sort__item_t).key;
1634
1635       svn_pool_clear(subpool);
1636
1637       /* By sending 'svn_node_unknown', the Node-kind: header simply won't
1638          be written out.  No big deal at all, really.  The loader
1639          shouldn't care.  */
1640       SVN_ERR(dump_node(eb, path,
1641                         svn_node_unknown, svn_node_action_delete,
1642                         FALSE, NULL, SVN_INVALID_REVNUM, subpool));
1643     }
1644
1645   svn_pool_destroy(subpool);
1646   return SVN_NO_ERROR;
1647 }
1648
1649
1650 static svn_error_t *
1651 add_file(const char *path,
1652          void *parent_baton,
1653          const char *copyfrom_path,
1654          svn_revnum_t copyfrom_rev,
1655          apr_pool_t *pool,
1656          void **file_baton)
1657 {
1658   struct dir_baton *pb = parent_baton;
1659   struct edit_baton *eb = pb->edit_baton;
1660   void *was_deleted;
1661   svn_boolean_t is_copy = FALSE;
1662
1663   /* This might be a replacement -- is the path already deleted? */
1664   was_deleted = svn_hash_gets(pb->deleted_entries, path);
1665
1666   /* Detect add-with-history. */
1667   is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1668
1669   /* Dump the node. */
1670   SVN_ERR(dump_node(eb, path,
1671                     svn_node_file,
1672                     was_deleted ? svn_node_action_replace : svn_node_action_add,
1673                     is_copy,
1674                     is_copy ? copyfrom_path : NULL,
1675                     is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1676                     pool));
1677
1678   if (was_deleted)
1679     /* delete the path, it's now been dumped. */
1680     svn_hash_sets(pb->deleted_entries, path, NULL);
1681
1682   /* Check for normalized name clashes, but only if this is actually a
1683      new name in the parent, not a replacement. */
1684   if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
1685     {
1686       pb->check_name_collision = TRUE;
1687     }
1688
1689   *file_baton = NULL;  /* muhahahaha */
1690   return SVN_NO_ERROR;
1691 }
1692
1693
1694 static svn_error_t *
1695 open_file(const char *path,
1696           void *parent_baton,
1697           svn_revnum_t ancestor_revision,
1698           apr_pool_t *pool,
1699           void **file_baton)
1700 {
1701   struct dir_baton *pb = parent_baton;
1702   struct edit_baton *eb = pb->edit_baton;
1703   const char *cmp_path = NULL;
1704   svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
1705
1706   /* If the parent directory has explicit comparison path and rev,
1707      record the same for this one. */
1708   if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
1709     {
1710       cmp_path = svn_relpath_join(pb->cmp_path,
1711                                   svn_relpath_basename(path, pool), pool);
1712       cmp_rev = pb->cmp_rev;
1713     }
1714
1715   SVN_ERR(dump_node(eb, path,
1716                     svn_node_file, svn_node_action_change,
1717                     FALSE, cmp_path, cmp_rev, pool));
1718
1719   *file_baton = NULL;  /* muhahahaha again */
1720   return SVN_NO_ERROR;
1721 }
1722
1723
1724 static svn_error_t *
1725 change_dir_prop(void *parent_baton,
1726                 const char *name,
1727                 const svn_string_t *value,
1728                 apr_pool_t *pool)
1729 {
1730   struct dir_baton *db = parent_baton;
1731   struct edit_baton *eb = db->edit_baton;
1732
1733   /* This function is what distinguishes between a directory that is
1734      opened to merely get somewhere, vs. one that is opened because it
1735      *actually* changed by itself.
1736
1737      Instead of recording the prop changes here, we just use this method
1738      to trigger writing the node; dump_node() finds all the changes. */
1739   if (! db->written_out)
1740     {
1741       SVN_ERR(dump_node(eb, db->path,
1742                         svn_node_dir, svn_node_action_change,
1743                         /* ### We pass is_copy=FALSE; this might be wrong
1744                            but the parameter isn't used when action=change. */
1745                         FALSE, db->cmp_path, db->cmp_rev, pool));
1746       db->written_out = TRUE;
1747     }
1748   return SVN_NO_ERROR;
1749 }
1750
1751 static svn_error_t *
1752 fetch_props_func(apr_hash_t **props,
1753                  void *baton,
1754                  const char *path,
1755                  svn_revnum_t base_revision,
1756                  apr_pool_t *result_pool,
1757                  apr_pool_t *scratch_pool)
1758 {
1759   struct edit_baton *eb = baton;
1760   svn_error_t *err;
1761   svn_fs_root_t *fs_root;
1762
1763   if (!SVN_IS_VALID_REVNUM(base_revision))
1764     base_revision = eb->current_rev - 1;
1765
1766   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1767
1768   err = svn_fs_node_proplist(props, fs_root, path, result_pool);
1769   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1770     {
1771       svn_error_clear(err);
1772       *props = apr_hash_make(result_pool);
1773       return SVN_NO_ERROR;
1774     }
1775   else if (err)
1776     return svn_error_trace(err);
1777
1778   return SVN_NO_ERROR;
1779 }
1780
1781 static svn_error_t *
1782 fetch_kind_func(svn_node_kind_t *kind,
1783                 void *baton,
1784                 const char *path,
1785                 svn_revnum_t base_revision,
1786                 apr_pool_t *scratch_pool)
1787 {
1788   struct edit_baton *eb = baton;
1789   svn_fs_root_t *fs_root;
1790
1791   if (!SVN_IS_VALID_REVNUM(base_revision))
1792     base_revision = eb->current_rev - 1;
1793
1794   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1795
1796   SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
1797
1798   return SVN_NO_ERROR;
1799 }
1800
1801 static svn_error_t *
1802 fetch_base_func(const char **filename,
1803                 void *baton,
1804                 const char *path,
1805                 svn_revnum_t base_revision,
1806                 apr_pool_t *result_pool,
1807                 apr_pool_t *scratch_pool)
1808 {
1809   struct edit_baton *eb = baton;
1810   svn_stream_t *contents;
1811   svn_stream_t *file_stream;
1812   const char *tmp_filename;
1813   svn_error_t *err;
1814   svn_fs_root_t *fs_root;
1815
1816   if (!SVN_IS_VALID_REVNUM(base_revision))
1817     base_revision = eb->current_rev - 1;
1818
1819   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1820
1821   err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
1822   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1823     {
1824       svn_error_clear(err);
1825       *filename = NULL;
1826       return SVN_NO_ERROR;
1827     }
1828   else if (err)
1829     return svn_error_trace(err);
1830   SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
1831                                  svn_io_file_del_on_pool_cleanup,
1832                                  scratch_pool, scratch_pool));
1833   SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
1834
1835   *filename = apr_pstrdup(result_pool, tmp_filename);
1836
1837   return SVN_NO_ERROR;
1838 }
1839
1840
1841 static svn_error_t *
1842 get_dump_editor(const svn_delta_editor_t **editor,
1843                 void **edit_baton,
1844                 svn_fs_t *fs,
1845                 svn_revnum_t to_rev,
1846                 const char *root_path,
1847                 svn_stream_t *stream,
1848                 svn_boolean_t *found_old_reference,
1849                 svn_boolean_t *found_old_mergeinfo,
1850                 svn_error_t *(*custom_close_directory)(void *dir_baton,
1851                                   apr_pool_t *scratch_pool),
1852                 svn_repos_notify_func_t notify_func,
1853                 void *notify_baton,
1854                 svn_revnum_t oldest_dumped_rev,
1855                 svn_boolean_t use_deltas,
1856                 svn_boolean_t verify,
1857                 svn_boolean_t check_normalization,
1858                 apr_pool_t *pool)
1859 {
1860   /* Allocate an edit baton to be stored in every directory baton.
1861      Set it up for the directory baton we create here, which is the
1862      root baton. */
1863   struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1864   svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
1865   svn_delta_shim_callbacks_t *shim_callbacks =
1866                                 svn_delta_shim_callbacks_default(pool);
1867
1868   /* Set up the edit baton. */
1869   eb->stream = stream;
1870   eb->notify_func = notify_func;
1871   eb->notify_baton = notify_baton;
1872   eb->oldest_dumped_rev = oldest_dumped_rev;
1873   eb->path = apr_pstrdup(pool, root_path);
1874   SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
1875   eb->fs = fs;
1876   eb->current_rev = to_rev;
1877   eb->use_deltas = use_deltas;
1878   eb->verify = verify;
1879   eb->check_normalization = check_normalization;
1880   eb->found_old_reference = found_old_reference;
1881   eb->found_old_mergeinfo = found_old_mergeinfo;
1882
1883   /* In non-verification mode, we will allow anything to be dumped because
1884      it might be an incremental dump with possible manual intervention.
1885      Also, this might be the last resort when it comes to data recovery.
1886
1887      Else, make sure that all paths exists at their respective revisions.
1888   */
1889   eb->path_tracker = verify ? tracker_create(to_rev, pool) : NULL;
1890
1891   /* Set up the editor. */
1892   dump_editor->open_root = open_root;
1893   dump_editor->delete_entry = delete_entry;
1894   dump_editor->add_directory = add_directory;
1895   dump_editor->open_directory = open_directory;
1896   if (custom_close_directory)
1897     dump_editor->close_directory = custom_close_directory;
1898   else
1899     dump_editor->close_directory = close_directory;
1900   dump_editor->change_dir_prop = change_dir_prop;
1901   dump_editor->add_file = add_file;
1902   dump_editor->open_file = open_file;
1903
1904   *edit_baton = eb;
1905   *editor = dump_editor;
1906
1907   shim_callbacks->fetch_kind_func = fetch_kind_func;
1908   shim_callbacks->fetch_props_func = fetch_props_func;
1909   shim_callbacks->fetch_base_func = fetch_base_func;
1910   shim_callbacks->fetch_baton = eb;
1911
1912   SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1913                                    NULL, NULL, shim_callbacks, pool, pool));
1914
1915   return SVN_NO_ERROR;
1916 }
1917
1918 /*----------------------------------------------------------------------*/
1919 \f
1920 /** The main dumping routine, svn_repos_dump_fs. **/
1921
1922
1923 /* Helper for svn_repos_dump_fs.
1924
1925    Write a revision record of REV in REPOS to writable STREAM, using POOL.
1926    Dump revision properties as well if INCLUDE_REVPROPS has been set.
1927    AUTHZ_FUNC and AUTHZ_BATON are passed directly to the repos layer.
1928  */
1929 static svn_error_t *
1930 write_revision_record(svn_stream_t *stream,
1931                       svn_repos_t *repos,
1932                       svn_revnum_t rev,
1933                       svn_boolean_t include_revprops,
1934                       svn_repos_authz_func_t authz_func,
1935                       void *authz_baton,
1936                       apr_pool_t *pool)
1937 {
1938   apr_hash_t *props;
1939   apr_time_t timetemp;
1940   svn_string_t *datevalue;
1941
1942   if (include_revprops)
1943     {
1944       SVN_ERR(svn_repos_fs_revision_proplist(&props, repos, rev,
1945                                              authz_func, authz_baton, pool));
1946
1947       /* Run revision date properties through the time conversion to
1948         canonicalize them. */
1949       /* ### Remove this when it is no longer needed for sure. */
1950       datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
1951       if (datevalue)
1952         {
1953           SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
1954           datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
1955                                         pool);
1956           svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
1957         }
1958     }
1959    else
1960     {
1961       /* Although we won't use it, we still need this container for the
1962          call below. */
1963       props = apr_hash_make(pool);
1964     }
1965
1966   SVN_ERR(svn_repos__dump_revision_record(stream, rev, NULL, props,
1967                                           include_revprops,
1968                                           pool));
1969   return SVN_NO_ERROR;
1970 }
1971
1972 /* Baton for dump_filter_authz_func(). */
1973 typedef struct dump_filter_baton_t
1974 {
1975   svn_repos_dump_filter_func_t filter_func;
1976   void *filter_baton;
1977 } dump_filter_baton_t;
1978
1979 /* Implements svn_repos_authz_func_t. */
1980 static svn_error_t *
1981 dump_filter_authz_func(svn_boolean_t *allowed,
1982                        svn_fs_root_t *root,
1983                        const char *path,
1984                        void *baton,
1985                        apr_pool_t *pool)
1986 {
1987   dump_filter_baton_t *b = baton;
1988
1989   return svn_error_trace(b->filter_func(allowed, root, path, b->filter_baton,
1990                                         pool));
1991 }
1992
1993
1994
1995 /* The main dumper. */
1996 svn_error_t *
1997 svn_repos_dump_fs4(svn_repos_t *repos,
1998                    svn_stream_t *stream,
1999                    svn_revnum_t start_rev,
2000                    svn_revnum_t end_rev,
2001                    svn_boolean_t incremental,
2002                    svn_boolean_t use_deltas,
2003                    svn_boolean_t include_revprops,
2004                    svn_boolean_t include_changes,
2005                    svn_repos_notify_func_t notify_func,
2006                    void *notify_baton,
2007                    svn_repos_dump_filter_func_t filter_func,
2008                    void *filter_baton,
2009                    svn_cancel_func_t cancel_func,
2010                    void *cancel_baton,
2011                    apr_pool_t *pool)
2012 {
2013   const svn_delta_editor_t *dump_editor;
2014   void *dump_edit_baton = NULL;
2015   svn_revnum_t rev;
2016   svn_fs_t *fs = svn_repos_fs(repos);
2017   apr_pool_t *iterpool = svn_pool_create(pool);
2018   svn_revnum_t youngest;
2019   const char *uuid;
2020   int version;
2021   svn_boolean_t found_old_reference = FALSE;
2022   svn_boolean_t found_old_mergeinfo = FALSE;
2023   svn_repos_notify_t *notify;
2024   svn_repos_authz_func_t authz_func;
2025   dump_filter_baton_t authz_baton = {0};
2026
2027   /* Make sure we catch up on the latest revprop changes.  This is the only
2028    * time we will refresh the revprop data in this query. */
2029   SVN_ERR(svn_fs_refresh_revision_props(fs, pool));
2030
2031   /* Determine the current youngest revision of the filesystem. */
2032   SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2033
2034   /* Use default vals if necessary. */
2035   if (! SVN_IS_VALID_REVNUM(start_rev))
2036     start_rev = 0;
2037   if (! SVN_IS_VALID_REVNUM(end_rev))
2038     end_rev = youngest;
2039   if (! stream)
2040     stream = svn_stream_empty(pool);
2041
2042   /* Validate the revisions. */
2043   if (start_rev > end_rev)
2044     return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2045                              _("Start revision %ld"
2046                                " is greater than end revision %ld"),
2047                              start_rev, end_rev);
2048   if (end_rev > youngest)
2049     return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2050                              _("End revision %ld is invalid "
2051                                "(youngest revision is %ld)"),
2052                              end_rev, youngest);
2053
2054   /* We use read authz callback to implement dump filtering. If there is no
2055    * read access for some node, it will be excluded from dump as well as
2056    * references to it (e.g. copy source). */
2057   if (filter_func)
2058     {
2059       authz_func = dump_filter_authz_func;
2060       authz_baton.filter_func = filter_func;
2061       authz_baton.filter_baton = filter_baton;
2062     }
2063   else
2064     {
2065       authz_func = NULL;
2066     }
2067
2068   /* Write out the UUID. */
2069   SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
2070
2071   /* If we're not using deltas, use the previous version, for
2072      compatibility with svn 1.0.x. */
2073   version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
2074   if (!use_deltas)
2075     version--;
2076
2077   /* Write out "general" metadata for the dumpfile.  In this case, a
2078      magic header followed by a dumpfile format version. */
2079   SVN_ERR(svn_stream_printf(stream, pool,
2080                             SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
2081                             version));
2082   SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
2083                             ": %s\n\n", uuid));
2084
2085   /* Create a notify object that we can reuse in the loop. */
2086   if (notify_func)
2087     notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
2088                                      pool);
2089
2090   /* Main loop:  we're going to dump revision REV.  */
2091   for (rev = start_rev; rev <= end_rev; rev++)
2092     {
2093       svn_fs_root_t *to_root;
2094       svn_boolean_t use_deltas_for_rev;
2095
2096       svn_pool_clear(iterpool);
2097
2098       /* Check for cancellation. */
2099       if (cancel_func)
2100         SVN_ERR(cancel_func(cancel_baton));
2101
2102       /* Write the revision record. */
2103       SVN_ERR(write_revision_record(stream, repos, rev, include_revprops,
2104                                     authz_func, &authz_baton, iterpool));
2105
2106       /* When dumping revision 0, we just write out the revision record.
2107          The parser might want to use its properties.
2108          If we don't want revision changes at all, skip in any case. */
2109       if (rev == 0 || !include_changes)
2110         goto loop_end;
2111
2112       /* Fetch the editor which dumps nodes to a file.  Regardless of
2113          what we've been told, don't use deltas for the first rev of a
2114          non-incremental dump. */
2115       use_deltas_for_rev = use_deltas && (incremental || rev != start_rev);
2116       SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, rev,
2117                               "", stream, &found_old_reference,
2118                               &found_old_mergeinfo, NULL,
2119                               notify_func, notify_baton,
2120                               start_rev, use_deltas_for_rev, FALSE, FALSE,
2121                               iterpool));
2122
2123       /* Drive the editor in one way or another. */
2124       SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool));
2125
2126       /* If this is the first revision of a non-incremental dump,
2127          we're in for a full tree dump.  Otherwise, we want to simply
2128          replay the revision.  */
2129       if ((rev == start_rev) && (! incremental))
2130         {
2131           /* Compare against revision 0, so everything appears to be added. */
2132           svn_fs_root_t *from_root;
2133           SVN_ERR(svn_fs_revision_root(&from_root, fs, 0, iterpool));
2134           SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
2135                                        to_root, "",
2136                                        dump_editor, dump_edit_baton,
2137                                        authz_func, &authz_baton,
2138                                        FALSE, /* don't send text-deltas */
2139                                        svn_depth_infinity,
2140                                        FALSE, /* don't send entry props */
2141                                        FALSE, /* don't ignore ancestry */
2142                                        iterpool));
2143         }
2144       else
2145         {
2146           /* The normal case: compare consecutive revs. */
2147           SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2148                                     dump_editor, dump_edit_baton,
2149                                     authz_func, &authz_baton, iterpool));
2150
2151           /* While our editor close_edit implementation is a no-op, we still
2152              do this for completeness. */
2153           SVN_ERR(dump_editor->close_edit(dump_edit_baton, iterpool));
2154         }
2155
2156     loop_end:
2157       if (notify_func)
2158         {
2159           notify->revision = rev;
2160           notify_func(notify_baton, notify, iterpool);
2161         }
2162     }
2163
2164   if (notify_func)
2165     {
2166       /* Did we issue any warnings about references to revisions older than
2167          the oldest dumped revision?  If so, then issue a final generic
2168          warning, since the inline warnings already issued might easily be
2169          missed. */
2170
2171       notify = svn_repos_notify_create(svn_repos_notify_dump_end, iterpool);
2172       notify_func(notify_baton, notify, iterpool);
2173
2174       if (found_old_reference)
2175         {
2176           notify_warning(iterpool, notify_func, notify_baton,
2177                          svn_repos_notify_warning_found_old_reference,
2178                          _("The range of revisions dumped "
2179                            "contained references to "
2180                            "copy sources outside that "
2181                            "range."));
2182         }
2183
2184       /* Ditto if we issued any warnings about old revisions referenced
2185          in dumped mergeinfo. */
2186       if (found_old_mergeinfo)
2187         {
2188           notify_warning(iterpool, notify_func, notify_baton,
2189                          svn_repos_notify_warning_found_old_mergeinfo,
2190                          _("The range of revisions dumped "
2191                            "contained mergeinfo "
2192                            "which reference revisions outside "
2193                            "that range."));
2194         }
2195     }
2196
2197   svn_pool_destroy(iterpool);
2198
2199   return SVN_NO_ERROR;
2200 }
2201
2202
2203 /*----------------------------------------------------------------------*/
2204 \f
2205 /* verify, based on dump */
2206
2207
2208 /* Creating a new revision that changes /A/B/E/bravo means creating new
2209    directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
2210    each entry not changed in the new revision a link back to the entry in a
2211    previous revision.  svn_repos_replay()ing a revision does not verify that
2212    those links are correct.
2213
2214    For paths actually changed in the revision we verify, we get directory
2215    contents or file length twice: once in the dump editor, and once here.
2216    We could create a new verify baton, store in it the changed paths, and
2217    skip those here, but that means building an entire wrapper editor and
2218    managing two levels of batons.  The impact from checking these entries
2219    twice should be minimal, while the code to avoid it is not.
2220 */
2221
2222 static svn_error_t *
2223 verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
2224                        void *val, apr_pool_t *pool)
2225 {
2226   struct dir_baton *db = baton;
2227   svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
2228   char *path;
2229   svn_boolean_t right_kind;
2230
2231   path = svn_relpath_join(db->path, (const char *)key, pool);
2232
2233   /* since we can't access the directory entries directly by their ID,
2234      we need to navigate from the FS_ROOT to them (relatively expensive
2235      because we may start at a never rev than the last change to node).
2236      We check that the node kind stored in the noderev matches the dir
2237      entry.  This also ensures that all entries point to valid noderevs.
2238    */
2239   switch (dirent->kind) {
2240   case svn_node_dir:
2241     SVN_ERR(svn_fs_is_dir(&right_kind, db->edit_baton->fs_root, path, pool));
2242     if (!right_kind)
2243       return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2244                                _("Node '%s' is not a directory."),
2245                                path);
2246
2247     break;
2248   case svn_node_file:
2249     SVN_ERR(svn_fs_is_file(&right_kind, db->edit_baton->fs_root, path, pool));
2250     if (!right_kind)
2251       return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2252                                _("Node '%s' is not a file."),
2253                                path);
2254     break;
2255   default:
2256     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2257                              _("Unexpected node kind %d for '%s'"),
2258                              dirent->kind, path);
2259   }
2260
2261   return SVN_NO_ERROR;
2262 }
2263
2264 /* Baton used by the check_name_collision hash iterator. */
2265 struct check_name_collision_baton
2266 {
2267   struct dir_baton *dir_baton;
2268   apr_hash_t *normalized;
2269   svn_membuf_t buffer;
2270 };
2271
2272 /* Scan the directory and report all entry names that differ only in
2273    Unicode character representation. */
2274 static svn_error_t *
2275 check_name_collision(void *baton, const void *key, apr_ssize_t klen,
2276                      void *val, apr_pool_t *iterpool)
2277 {
2278   struct check_name_collision_baton *const cb = baton;
2279   const char *name;
2280   const char *found;
2281
2282   SVN_ERR(svn_utf__normalize(&name, key, klen, &cb->buffer));
2283
2284   found = svn_hash_gets(cb->normalized, name);
2285   if (!found)
2286     svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2287                   normalized_unique);
2288   else if (found == normalized_collision)
2289     /* Skip already reported collision */;
2290   else
2291     {
2292       struct dir_baton *const db = cb->dir_baton;
2293       struct edit_baton *const eb = db->edit_baton;
2294       const char* normpath;
2295
2296       svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2297                     normalized_collision);
2298
2299       SVN_ERR(svn_utf__normalize(
2300                   &normpath, svn_relpath_join(db->path, name, iterpool),
2301                   SVN_UTF__UNKNOWN_LENGTH, &cb->buffer));
2302       notify_warning(iterpool, eb->notify_func, eb->notify_baton,
2303                      svn_repos_notify_warning_name_collision,
2304                      _("Duplicate representation of path '%s'"), normpath);
2305     }
2306   return SVN_NO_ERROR;
2307 }
2308
2309
2310 static svn_error_t *
2311 verify_close_directory(void *dir_baton, apr_pool_t *pool)
2312 {
2313   struct dir_baton *db = dir_baton;
2314   apr_hash_t *dirents;
2315   SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root,
2316                              db->path, pool));
2317   SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
2318                             dir_baton, pool));
2319
2320   if (db->check_name_collision)
2321     {
2322       struct check_name_collision_baton check_baton;
2323       check_baton.dir_baton = db;
2324       check_baton.normalized = apr_hash_make(pool);
2325       svn_membuf__create(&check_baton.buffer, 0, pool);
2326       SVN_ERR(svn_iter_apr_hash(NULL, dirents, check_name_collision,
2327                                 &check_baton, pool));
2328     }
2329
2330   return close_directory(dir_baton, pool);
2331 }
2332
2333 /* Verify revision REV in file system FS. */
2334 static svn_error_t *
2335 verify_one_revision(svn_fs_t *fs,
2336                     svn_revnum_t rev,
2337                     svn_repos_notify_func_t notify_func,
2338                     void *notify_baton,
2339                     svn_revnum_t start_rev,
2340                     svn_boolean_t check_normalization,
2341                     svn_cancel_func_t cancel_func,
2342                     void *cancel_baton,
2343                     apr_pool_t *scratch_pool)
2344 {
2345   const svn_delta_editor_t *dump_editor;
2346   void *dump_edit_baton;
2347   svn_fs_root_t *to_root;
2348   apr_hash_t *props;
2349   const svn_delta_editor_t *cancel_editor;
2350   void *cancel_edit_baton;
2351
2352   /* Get cancellable dump editor, but with our close_directory handler.*/
2353   SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
2354                           fs, rev, "",
2355                           svn_stream_empty(scratch_pool),
2356                           NULL, NULL,
2357                           verify_close_directory,
2358                           notify_func, notify_baton,
2359                           start_rev,
2360                           FALSE, TRUE, /* use_deltas, verify */
2361                           check_normalization,
2362                           scratch_pool));
2363   SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2364                                             dump_editor, dump_edit_baton,
2365                                             &cancel_editor,
2366                                             &cancel_edit_baton,
2367                                             scratch_pool));
2368   SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, scratch_pool));
2369   SVN_ERR(svn_fs_verify_root(to_root, scratch_pool));
2370   SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2371                             cancel_editor, cancel_edit_baton,
2372                             NULL, NULL, scratch_pool));
2373
2374   /* While our editor close_edit implementation is a no-op, we still
2375      do this for completeness. */
2376   SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, scratch_pool));
2377
2378   SVN_ERR(svn_fs_revision_proplist2(&props, fs, rev, FALSE, scratch_pool,
2379                                     scratch_pool));
2380
2381   return SVN_NO_ERROR;
2382 }
2383
2384 /* Baton type used for forwarding notifications from FS API to REPOS API. */
2385 struct verify_fs_notify_func_baton_t
2386 {
2387    /* notification function to call (must not be NULL) */
2388    svn_repos_notify_func_t notify_func;
2389
2390    /* baton to use for it */
2391    void *notify_baton;
2392
2393    /* type of notification to send (we will simply plug in the revision) */
2394    svn_repos_notify_t *notify;
2395 };
2396
2397 /* Forward the notification to BATON. */
2398 static void
2399 verify_fs_notify_func(svn_revnum_t revision,
2400                        void *baton,
2401                        apr_pool_t *pool)
2402 {
2403   struct verify_fs_notify_func_baton_t *notify_baton = baton;
2404
2405   notify_baton->notify->revision = revision;
2406   notify_baton->notify_func(notify_baton->notify_baton,
2407                             notify_baton->notify, pool);
2408 }
2409
2410 static svn_error_t *
2411 report_error(svn_revnum_t revision,
2412              svn_error_t *verify_err,
2413              svn_repos_verify_callback_t verify_callback,
2414              void *verify_baton,
2415              apr_pool_t *pool)
2416 {
2417   if (verify_callback)
2418     {
2419       svn_error_t *cb_err;
2420
2421       /* The caller provided us with a callback, so make him responsible
2422          for what's going to happen with the error. */
2423       cb_err = verify_callback(verify_baton, revision, verify_err, pool);
2424       svn_error_clear(verify_err);
2425       SVN_ERR(cb_err);
2426
2427       return SVN_NO_ERROR;
2428     }
2429   else
2430     {
2431       /* No callback -- no second guessing.  Just return the error. */
2432       return svn_error_trace(verify_err);
2433     }
2434 }
2435
2436 svn_error_t *
2437 svn_repos_verify_fs3(svn_repos_t *repos,
2438                      svn_revnum_t start_rev,
2439                      svn_revnum_t end_rev,
2440                      svn_boolean_t check_normalization,
2441                      svn_boolean_t metadata_only,
2442                      svn_repos_notify_func_t notify_func,
2443                      void *notify_baton,
2444                      svn_repos_verify_callback_t verify_callback,
2445                      void *verify_baton,
2446                      svn_cancel_func_t cancel_func,
2447                      void *cancel_baton,
2448                      apr_pool_t *pool)
2449 {
2450   svn_fs_t *fs = svn_repos_fs(repos);
2451   svn_revnum_t youngest;
2452   svn_revnum_t rev;
2453   apr_pool_t *iterpool = svn_pool_create(pool);
2454   svn_repos_notify_t *notify;
2455   svn_fs_progress_notify_func_t verify_notify = NULL;
2456   struct verify_fs_notify_func_baton_t *verify_notify_baton = NULL;
2457   svn_error_t *err;
2458
2459   /* Make sure we catch up on the latest revprop changes.  This is the only
2460    * time we will refresh the revprop data in this query. */
2461   SVN_ERR(svn_fs_refresh_revision_props(fs, pool));
2462
2463   /* Determine the current youngest revision of the filesystem. */
2464   SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2465
2466   /* Use default vals if necessary. */
2467   if (! SVN_IS_VALID_REVNUM(start_rev))
2468     start_rev = 0;
2469   if (! SVN_IS_VALID_REVNUM(end_rev))
2470     end_rev = youngest;
2471
2472   /* Validate the revisions. */
2473   if (start_rev > end_rev)
2474     return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2475                              _("Start revision %ld"
2476                                " is greater than end revision %ld"),
2477                              start_rev, end_rev);
2478   if (end_rev > youngest)
2479     return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2480                              _("End revision %ld is invalid "
2481                                "(youngest revision is %ld)"),
2482                              end_rev, youngest);
2483
2484   /* Create a notify object that we can reuse within the loop and a
2485      forwarding structure for notifications from inside svn_fs_verify(). */
2486   if (notify_func)
2487     {
2488       notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, pool);
2489
2490       verify_notify = verify_fs_notify_func;
2491       verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
2492       verify_notify_baton->notify_func = notify_func;
2493       verify_notify_baton->notify_baton = notify_baton;
2494       verify_notify_baton->notify
2495         = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool);
2496     }
2497
2498   /* Verify global metadata and backend-specific data first. */
2499   err = svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
2500                       start_rev, end_rev,
2501                       verify_notify, verify_notify_baton,
2502                       cancel_func, cancel_baton, pool);
2503
2504   if (err && err->apr_err == SVN_ERR_CANCELLED)
2505     {
2506       return svn_error_trace(err);
2507     }
2508   else if (err)
2509     {
2510       SVN_ERR(report_error(SVN_INVALID_REVNUM, err, verify_callback,
2511                            verify_baton, iterpool));
2512     }
2513
2514   if (!metadata_only)
2515     for (rev = start_rev; rev <= end_rev; rev++)
2516       {
2517         svn_pool_clear(iterpool);
2518
2519         /* Wrapper function to catch the possible errors. */
2520         err = verify_one_revision(fs, rev, notify_func, notify_baton,
2521                                   start_rev, check_normalization,
2522                                   cancel_func, cancel_baton,
2523                                   iterpool);
2524
2525         if (err && err->apr_err == SVN_ERR_CANCELLED)
2526           {
2527             return svn_error_trace(err);
2528           }
2529         else if (err)
2530           {
2531             SVN_ERR(report_error(rev, err, verify_callback, verify_baton,
2532                                  iterpool));
2533           }
2534         else if (notify_func)
2535           {
2536             /* Tell the caller that we're done with this revision. */
2537             notify->revision = rev;
2538             notify_func(notify_baton, notify, iterpool);
2539           }
2540       }
2541
2542   /* We're done. */
2543   if (notify_func)
2544     {
2545       notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool);
2546       notify_func(notify_baton, notify, iterpool);
2547     }
2548
2549   svn_pool_destroy(iterpool);
2550
2551   return SVN_NO_ERROR;
2552 }