]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_repos/dump.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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 = 0;
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_seek(*tempfile, APR_CUR, &offset, 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   /* Write out a regular Content-length header for the benefit of
550      non-Subversion RFC-822 parsers. */
551   svn_hash_sets(headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
552                 apr_psprintf(scratch_pool,
553                              "%" APR_SIZE_T_FMT, propstring->len));
554   SVN_ERR(write_revision_headers(dump_stream, headers, scratch_pool));
555
556   /* End of headers */
557   SVN_ERR(svn_stream_puts(dump_stream, "\n"));
558
559   /* Property data. */
560   if (propstring)
561     {
562       SVN_ERR(svn_stream_write(dump_stream, propstring->data, &propstring->len));
563     }
564
565   /* put an end to revision */
566   SVN_ERR(svn_stream_puts(dump_stream, "\n"));
567
568   return SVN_NO_ERROR;
569 }
570
571 svn_error_t *
572 svn_repos__dump_node_record(svn_stream_t *dump_stream,
573                             svn_repos__dumpfile_headers_t *headers,
574                             svn_stringbuf_t *props_str,
575                             svn_boolean_t has_text,
576                             svn_filesize_t text_content_length,
577                             svn_boolean_t content_length_always,
578                             apr_pool_t *scratch_pool)
579 {
580   svn_filesize_t content_length = 0;
581
582   /* add content-length headers */
583   if (props_str)
584     {
585       svn_repos__dumpfile_header_pushf(
586         headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
587         "%" APR_SIZE_T_FMT, props_str->len);
588       content_length += props_str->len;
589     }
590   if (has_text)
591     {
592       svn_repos__dumpfile_header_pushf(
593         headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH,
594         "%" SVN_FILESIZE_T_FMT, text_content_length);
595       content_length += text_content_length;
596     }
597   if (content_length_always || props_str || has_text)
598     {
599       svn_repos__dumpfile_header_pushf(
600         headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
601         "%" SVN_FILESIZE_T_FMT, content_length);
602     }
603
604   /* write the headers */
605   SVN_ERR(svn_repos__dump_headers(dump_stream, headers, scratch_pool));
606
607   /* write the props */
608   if (props_str)
609     {
610       SVN_ERR(svn_stream_write(dump_stream, props_str->data, &props_str->len));
611     }
612   return SVN_NO_ERROR;
613 }
614
615 /*----------------------------------------------------------------------*/
616 \f
617 /** An editor which dumps node-data in 'dumpfile format' to a file. **/
618
619 /* Look, mom!  No file batons! */
620
621 struct edit_baton
622 {
623   /* The relpath which implicitly prepends all full paths coming into
624      this editor.  This will almost always be "".  */
625   const char *path;
626
627   /* The stream to dump to. */
628   svn_stream_t *stream;
629
630   /* Send feedback here, if non-NULL */
631   svn_repos_notify_func_t notify_func;
632   void *notify_baton;
633
634   /* The fs revision root, so we can read the contents of paths. */
635   svn_fs_root_t *fs_root;
636   svn_revnum_t current_rev;
637
638   /* The fs, so we can grab historic information if needed. */
639   svn_fs_t *fs;
640
641   /* True if dumped nodes should output deltas instead of full text. */
642   svn_boolean_t use_deltas;
643
644   /* True if this "dump" is in fact a verify. */
645   svn_boolean_t verify;
646
647   /* True if checking UCS normalization during a verify. */
648   svn_boolean_t check_normalization;
649
650   /* The first revision dumped in this dumpstream. */
651   svn_revnum_t oldest_dumped_rev;
652
653   /* If not NULL, set to true if any references to revisions older than
654      OLDEST_DUMPED_REV were found in the dumpstream. */
655   svn_boolean_t *found_old_reference;
656
657   /* If not NULL, set to true if any mergeinfo was dumped which contains
658      revisions older than OLDEST_DUMPED_REV. */
659   svn_boolean_t *found_old_mergeinfo;
660
661   /* Structure allows us to verify the paths currently being dumped.
662      If NULL, validity checks are being skipped. */
663   path_tracker_t *path_tracker;
664 };
665
666 struct dir_baton
667 {
668   struct edit_baton *edit_baton;
669
670   /* has this directory been written to the output stream? */
671   svn_boolean_t written_out;
672
673   /* the repository relpath associated with this directory */
674   const char *path;
675
676   /* The comparison repository relpath and revision of this directory.
677      If both of these are valid, use them as a source against which to
678      compare the directory instead of the default comparison source of
679      PATH in the previous revision. */
680   const char *cmp_path;
681   svn_revnum_t cmp_rev;
682
683   /* hash of paths that need to be deleted, though some -might- be
684      replaced.  maps const char * paths to this dir_baton.  (they're
685      full paths, because that's what the editor driver gives us.  but
686      really, they're all within this directory.) */
687   apr_hash_t *deleted_entries;
688
689   /* A flag indicating that new entries have been added to this
690      directory in this revision. Used to optimize detection of UCS
691      representation collisions; we will only check for that in
692      revisions where new names appear in the directory. */
693   svn_boolean_t check_name_collision;
694
695   /* pool to be used for deleting the hash items */
696   apr_pool_t *pool;
697 };
698
699
700 /* Make a directory baton to represent the directory was path
701    (relative to EDIT_BATON's path) is PATH.
702
703    CMP_PATH/CMP_REV are the path/revision against which this directory
704    should be compared for changes.  If either is omitted (NULL for the
705    path, SVN_INVALID_REVNUM for the rev), just compare this directory
706    PATH against itself in the previous revision.
707
708    PB is the directory baton of this directory's parent,
709    or NULL if this is the top-level directory of the edit.
710
711    Perform all allocations in POOL.  */
712 static struct dir_baton *
713 make_dir_baton(const char *path,
714                const char *cmp_path,
715                svn_revnum_t cmp_rev,
716                void *edit_baton,
717                struct dir_baton *pb,
718                apr_pool_t *pool)
719 {
720   struct edit_baton *eb = edit_baton;
721   struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
722   const char *full_path;
723
724   /* A path relative to nothing?  I don't think so. */
725   SVN_ERR_ASSERT_NO_RETURN(!path || pb);
726
727   /* Construct the full path of this node. */
728   if (pb)
729     full_path = svn_relpath_join(eb->path, path, pool);
730   else
731     full_path = apr_pstrdup(pool, eb->path);
732
733   /* Remove leading slashes from copyfrom paths. */
734   if (cmp_path)
735     cmp_path = svn_relpath_canonicalize(cmp_path, pool);
736
737   new_db->edit_baton = eb;
738   new_db->path = full_path;
739   new_db->cmp_path = cmp_path;
740   new_db->cmp_rev = cmp_rev;
741   new_db->written_out = FALSE;
742   new_db->deleted_entries = apr_hash_make(pool);
743   new_db->check_name_collision = FALSE;
744   new_db->pool = pool;
745
746   return new_db;
747 }
748
749 static svn_error_t *
750 fetch_kind_func(svn_node_kind_t *kind,
751                 void *baton,
752                 const char *path,
753                 svn_revnum_t base_revision,
754                 apr_pool_t *scratch_pool);
755
756 /* Return an error when PATH in REVISION does not exist or is of a
757    different kind than EXPECTED_KIND.  If the latter is svn_node_unknown,
758    skip that check.  Use EB for context information.  If REVISION is the
759    current revision, use EB's path tracker to follow renames, deletions,
760    etc.
761
762    Use SCRATCH_POOL for temporary allocations.
763    No-op if EB's path tracker has not been initialized.
764  */
765 static svn_error_t *
766 node_must_exist(struct edit_baton *eb,
767                 const char *path,
768                 svn_revnum_t revision,
769                 svn_node_kind_t expected_kind,
770                 apr_pool_t *scratch_pool)
771 {
772   svn_node_kind_t kind = svn_node_none;
773
774   /* in case the caller is trying something stupid ... */
775   if (eb->path_tracker == NULL)
776     return SVN_NO_ERROR;
777
778   /* paths pertaining to the revision currently being processed must
779      be translated / checked using our path tracker. */
780   if (revision == eb->path_tracker->revision)
781     tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
782
783   /* determine the node type (default: no such node) */
784   if (path)
785     SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
786
787   /* check results */
788   if (kind == svn_node_none)
789     return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
790                              _("Path '%s' not found in r%ld."),
791                              path, revision);
792
793   if (expected_kind != kind && expected_kind != svn_node_unknown)
794     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
795                              _("Unexpected node kind %d for '%s' at r%ld. "
796                                "Expected kind was %d."),
797                              kind, path, revision, expected_kind);
798
799   return SVN_NO_ERROR;
800 }
801
802 /* Return an error when PATH exists in REVISION.  Use EB for context
803    information.  If REVISION is the current revision, use EB's path
804    tracker to follow renames, deletions, etc.
805
806    Use SCRATCH_POOL for temporary allocations.
807    No-op if EB's path tracker has not been initialized.
808  */
809 static svn_error_t *
810 node_must_not_exist(struct edit_baton *eb,
811                     const char *path,
812                     svn_revnum_t revision,
813                     apr_pool_t *scratch_pool)
814 {
815   svn_node_kind_t kind = svn_node_none;
816
817   /* in case the caller is trying something stupid ... */
818   if (eb->path_tracker == NULL)
819     return SVN_NO_ERROR;
820
821   /* paths pertaining to the revision currently being processed must
822      be translated / checked using our path tracker. */
823   if (revision == eb->path_tracker->revision)
824     tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
825
826   /* determine the node type (default: no such node) */
827   if (path)
828     SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
829
830   /* check results */
831   if (kind != svn_node_none)
832     return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
833                              _("Path '%s' exists in r%ld."),
834                              path, revision);
835
836   return SVN_NO_ERROR;
837 }
838
839 /* If the mergeinfo in MERGEINFO_STR refers to any revisions older than
840  * OLDEST_DUMPED_REV, issue a warning and set *FOUND_OLD_MERGEINFO to TRUE,
841  * otherwise leave *FOUND_OLD_MERGEINFO unchanged.
842  */
843 static svn_error_t *
844 verify_mergeinfo_revisions(svn_boolean_t *found_old_mergeinfo,
845                            const char *mergeinfo_str,
846                            svn_revnum_t oldest_dumped_rev,
847                            svn_repos_notify_func_t notify_func,
848                            void *notify_baton,
849                            apr_pool_t *pool)
850 {
851   svn_mergeinfo_t mergeinfo, old_mergeinfo;
852
853   SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str, pool));
854   SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
855             &old_mergeinfo, mergeinfo,
856             oldest_dumped_rev - 1, 0,
857             TRUE, pool, pool));
858
859   if (apr_hash_count(old_mergeinfo))
860     {
861       notify_warning(pool, notify_func, notify_baton,
862                      svn_repos_notify_warning_found_old_mergeinfo,
863                      _("Mergeinfo referencing revision(s) prior "
864                        "to the oldest dumped revision (r%ld). "
865                        "Loading this dump may result in invalid "
866                        "mergeinfo."),
867                      oldest_dumped_rev);
868
869       if (found_old_mergeinfo)
870         *found_old_mergeinfo = TRUE;
871     }
872
873   return SVN_NO_ERROR;
874 }
875
876 /* Unique string pointers used by verify_mergeinfo_normalization()
877    and check_name_collision() */
878 static const char normalized_unique[] = "normalized_unique";
879 static const char normalized_collision[] = "normalized_collision";
880
881
882 /* Baton for extract_mergeinfo_paths */
883 struct extract_mergeinfo_paths_baton
884 {
885   apr_hash_t *result;
886   svn_boolean_t normalize;
887   svn_membuf_t buffer;
888 };
889
890 /* Hash iterator that uniquifies all keys into a single hash table,
891    optionally normalizing them first. */
892 static svn_error_t *
893 extract_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
894                          void *val, apr_pool_t *iterpool)
895 {
896   struct extract_mergeinfo_paths_baton *const xb = baton;
897   if (xb->normalize)
898     {
899       const char *normkey;
900       SVN_ERR(svn_utf__normalize(&normkey, key, klen, &xb->buffer));
901       svn_hash_sets(xb->result,
902                     apr_pstrdup(xb->buffer.pool, normkey),
903                     normalized_unique);
904     }
905   else
906     apr_hash_set(xb->result,
907                  apr_pmemdup(xb->buffer.pool, key, klen + 1), klen,
908                  normalized_unique);
909   return SVN_NO_ERROR;
910 }
911
912 /* Baton for filter_mergeinfo_paths */
913 struct filter_mergeinfo_paths_baton
914 {
915   apr_hash_t *paths;
916 };
917
918 /* Compare two sets of denormalized paths from mergeinfo entries,
919    removing duplicates. */
920 static svn_error_t *
921 filter_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
922                        void *val, apr_pool_t *iterpool)
923 {
924   struct filter_mergeinfo_paths_baton *const fb = baton;
925
926   if (apr_hash_get(fb->paths, key, klen))
927     apr_hash_set(fb->paths, key, klen, NULL);
928
929   return SVN_NO_ERROR;
930 }
931
932 /* Baton used by the check_mergeinfo_normalization hash iterator. */
933 struct verify_mergeinfo_normalization_baton
934 {
935   const char* path;
936   apr_hash_t *normalized_paths;
937   svn_membuf_t buffer;
938   svn_repos_notify_func_t notify_func;
939   void *notify_baton;
940 };
941
942 /* Hash iterator that verifies normalization and collision of paths in
943    an svn:mergeinfo property. */
944 static svn_error_t *
945 verify_mergeinfo_normalization(void *baton, const void *key, apr_ssize_t klen,
946                                void *val, apr_pool_t *iterpool)
947 {
948   struct verify_mergeinfo_normalization_baton *const vb = baton;
949
950   const char *const path = key;
951   const char *normpath;
952   const char *found;
953
954   SVN_ERR(svn_utf__normalize(&normpath, path, klen, &vb->buffer));
955   found = svn_hash_gets(vb->normalized_paths, normpath);
956   if (!found)
957       svn_hash_sets(vb->normalized_paths,
958                     apr_pstrdup(vb->buffer.pool, normpath),
959                     normalized_unique);
960   else if (found == normalized_collision)
961     /* Skip already reported collision */;
962   else
963     {
964       /* Report path collision in mergeinfo */
965       svn_hash_sets(vb->normalized_paths,
966                     apr_pstrdup(vb->buffer.pool, normpath),
967                     normalized_collision);
968
969       notify_warning(iterpool, vb->notify_func, vb->notify_baton,
970                      svn_repos_notify_warning_mergeinfo_collision,
971                      _("Duplicate representation of path '%s'"
972                        " in %s property of '%s'"),
973                      normpath, SVN_PROP_MERGEINFO, vb->path);
974     }
975   return SVN_NO_ERROR;
976 }
977
978 /* Check UCS normalization of mergeinfo for PATH. NEW_MERGEINFO is the
979    svn:mergeinfo property value being set; OLD_MERGEINFO is the
980    previous property value, which may be NULL. Only the paths that
981    were added in are checked, including collision checks. This
982    minimizes the number of notifications we generate for a given
983    mergeinfo property. */
984 static svn_error_t *
985 check_mergeinfo_normalization(const char *path,
986                               const char *new_mergeinfo,
987                               const char *old_mergeinfo,
988                               svn_repos_notify_func_t notify_func,
989                               void *notify_baton,
990                               apr_pool_t *pool)
991 {
992   svn_mergeinfo_t mergeinfo;
993   apr_hash_t *normalized_paths;
994   apr_hash_t *added_paths;
995   struct extract_mergeinfo_paths_baton extract_baton;
996   struct verify_mergeinfo_normalization_baton verify_baton;
997
998   SVN_ERR(svn_mergeinfo_parse(&mergeinfo, new_mergeinfo, pool));
999
1000   extract_baton.result = apr_hash_make(pool);
1001   extract_baton.normalize = FALSE;
1002   svn_membuf__create(&extract_baton.buffer, 0, pool);
1003   SVN_ERR(svn_iter_apr_hash(NULL, mergeinfo,
1004                             extract_mergeinfo_paths,
1005                             &extract_baton, pool));
1006   added_paths = extract_baton.result;
1007
1008   if (old_mergeinfo)
1009     {
1010       struct filter_mergeinfo_paths_baton filter_baton;
1011       svn_mergeinfo_t oldinfo;
1012
1013       extract_baton.result = apr_hash_make(pool);
1014       extract_baton.normalize = TRUE;
1015       SVN_ERR(svn_mergeinfo_parse(&oldinfo, old_mergeinfo, pool));
1016       SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
1017                                 extract_mergeinfo_paths,
1018                                 &extract_baton, pool));
1019       normalized_paths = extract_baton.result;
1020
1021       filter_baton.paths = added_paths;
1022       SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
1023                                 filter_mergeinfo_paths,
1024                                 &filter_baton, pool));
1025     }
1026   else
1027       normalized_paths = apr_hash_make(pool);
1028
1029   verify_baton.path = path;
1030   verify_baton.normalized_paths = normalized_paths;
1031   verify_baton.buffer = extract_baton.buffer;
1032   verify_baton.notify_func = notify_func;
1033   verify_baton.notify_baton = notify_baton;
1034   SVN_ERR(svn_iter_apr_hash(NULL, added_paths,
1035                             verify_mergeinfo_normalization,
1036                             &verify_baton, pool));
1037
1038   return SVN_NO_ERROR;
1039 }
1040
1041
1042 /* A special case of dump_node(), for a delete record.
1043  *
1044  * The only thing special about this version is it only writes one blank
1045  * line, not two, after the headers. Why? Historical precedent for the
1046  * case where a delete record is used as part of a (delete + add-with-history)
1047  * in implementing a replacement.
1048  *
1049  * Also it doesn't do a path-tracker check.
1050  */
1051 static svn_error_t *
1052 dump_node_delete(svn_stream_t *stream,
1053                  const char *node_relpath,
1054                  apr_pool_t *pool)
1055 {
1056   svn_repos__dumpfile_headers_t *headers
1057     = svn_repos__dumpfile_headers_create(pool);
1058
1059   /* Node-path: ... */
1060   svn_repos__dumpfile_header_push(
1061     headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
1062
1063   /* Node-action: delete */
1064   svn_repos__dumpfile_header_push(
1065     headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1066
1067   SVN_ERR(svn_repos__dump_headers(stream, headers, pool));
1068   return SVN_NO_ERROR;
1069 }
1070
1071 /* This helper is the main "meat" of the editor -- it does all the
1072    work of writing a node record.
1073
1074    Write out a node record for PATH of type KIND under EB->FS_ROOT.
1075    ACTION describes what is happening to the node (see enum svn_node_action).
1076    Write record to writable EB->STREAM.
1077
1078    If the node was itself copied, IS_COPY is TRUE and the
1079    path/revision of the copy source are in CMP_PATH/CMP_REV.  If
1080    IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
1081    of a copied subtree.
1082   */
1083 static svn_error_t *
1084 dump_node(struct edit_baton *eb,
1085           const char *path,
1086           svn_node_kind_t kind,
1087           enum svn_node_action action,
1088           svn_boolean_t is_copy,
1089           const char *cmp_path,
1090           svn_revnum_t cmp_rev,
1091           apr_pool_t *pool)
1092 {
1093   svn_stringbuf_t *propstring;
1094   apr_size_t len;
1095   svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
1096   const char *compare_path = path;
1097   svn_revnum_t compare_rev = eb->current_rev - 1;
1098   svn_fs_root_t *compare_root = NULL;
1099   apr_file_t *delta_file = NULL;
1100   svn_repos__dumpfile_headers_t *headers
1101     = svn_repos__dumpfile_headers_create(pool);
1102   svn_filesize_t textlen;
1103
1104   /* Maybe validate the path. */
1105   if (eb->verify || eb->notify_func)
1106     {
1107       svn_error_t *err = svn_fs__path_valid(path, pool);
1108
1109       if (err)
1110         {
1111           if (eb->notify_func)
1112             {
1113               char errbuf[512]; /* ### svn_strerror() magic number  */
1114
1115               notify_warning(pool, eb->notify_func, eb->notify_baton,
1116                              svn_repos_notify_warning_invalid_fspath,
1117                              _("E%06d: While validating fspath '%s': %s"),
1118                              err->apr_err, path,
1119                              svn_err_best_message(err, errbuf, sizeof(errbuf)));
1120             }
1121
1122           /* Return the error in addition to notifying about it. */
1123           if (eb->verify)
1124             return svn_error_trace(err);
1125           else
1126             svn_error_clear(err);
1127         }
1128     }
1129
1130   /* Write out metadata headers for this file node. */
1131   svn_repos__dumpfile_header_push(
1132     headers, SVN_REPOS_DUMPFILE_NODE_PATH, path);
1133   if (kind == svn_node_file)
1134     svn_repos__dumpfile_header_push(
1135       headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file");
1136   else if (kind == svn_node_dir)
1137     svn_repos__dumpfile_header_push(
1138       headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir");
1139
1140   /* Remove leading slashes from copyfrom paths. */
1141   if (cmp_path)
1142     cmp_path = svn_relpath_canonicalize(cmp_path, pool);
1143
1144   /* Validate the comparison path/rev. */
1145   if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
1146     {
1147       compare_path = cmp_path;
1148       compare_rev = cmp_rev;
1149     }
1150
1151   switch (action)
1152     {
1153     case svn_node_action_change:
1154       if (eb->path_tracker)
1155         SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
1156                   apr_psprintf(pool, _("Change invalid path '%s' in r%ld"),
1157                                path, eb->current_rev));
1158
1159       svn_repos__dumpfile_header_push(
1160         headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change");
1161
1162       /* either the text or props changed, or possibly both. */
1163       SVN_ERR(svn_fs_revision_root(&compare_root,
1164                                    svn_fs_root_fs(eb->fs_root),
1165                                    compare_rev, pool));
1166
1167       SVN_ERR(svn_fs_props_changed(&must_dump_props,
1168                                    compare_root, compare_path,
1169                                    eb->fs_root, path, pool));
1170       if (kind == svn_node_file)
1171         SVN_ERR(svn_fs_contents_changed(&must_dump_text,
1172                                         compare_root, compare_path,
1173                                         eb->fs_root, path, pool));
1174       break;
1175
1176     case svn_node_action_delete:
1177       if (eb->path_tracker)
1178         {
1179           SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
1180                     apr_psprintf(pool, _("Deleting invalid path '%s' in r%ld"),
1181                                  path, eb->current_rev));
1182           tracker_path_delete(eb->path_tracker, path);
1183         }
1184
1185       svn_repos__dumpfile_header_push(
1186         headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1187
1188       /* we can leave this routine quietly now, don't need to dump
1189          any content. */
1190       must_dump_text = FALSE;
1191       must_dump_props = FALSE;
1192       break;
1193
1194     case svn_node_action_replace:
1195       if (eb->path_tracker)
1196         SVN_ERR_W(node_must_exist(eb, path, eb->current_rev,
1197                                   svn_node_unknown, pool),
1198                   apr_psprintf(pool,
1199                                _("Replacing non-existent path '%s' in r%ld"),
1200                                path, eb->current_rev));
1201
1202       if (! is_copy)
1203         {
1204           if (eb->path_tracker)
1205             tracker_path_replace(eb->path_tracker, path);
1206
1207           /* a simple delete+add, implied by a single 'replace' action. */
1208           svn_repos__dumpfile_header_push(
1209             headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace");
1210
1211           /* definitely need to dump all content for a replace. */
1212           if (kind == svn_node_file)
1213             must_dump_text = TRUE;
1214           must_dump_props = TRUE;
1215           break;
1216         }
1217       else
1218         {
1219           /* more complex:  delete original, then add-with-history.  */
1220           /* ### Why not write a 'replace' record? Don't know. */
1221
1222           if (eb->path_tracker)
1223             {
1224               tracker_path_delete(eb->path_tracker, path);
1225             }
1226
1227           /* ### Unusually, we end this 'delete' node record with only a single
1228                  blank line after the header block -- no extra blank line. */
1229           SVN_ERR(dump_node_delete(eb->stream, path, pool));
1230
1231           /* The remaining action is a non-replacing add-with-history */
1232           /* action = svn_node_action_add; */
1233         }
1234       /* FALL THROUGH to 'add' */
1235
1236     case svn_node_action_add:
1237       if (eb->path_tracker)
1238         SVN_ERR_W(node_must_not_exist(eb, path, eb->current_rev, pool),
1239                   apr_psprintf(pool,
1240                                _("Adding already existing path '%s' in r%ld"),
1241                                path, eb->current_rev));
1242
1243       svn_repos__dumpfile_header_push(
1244         headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
1245
1246       if (! is_copy)
1247         {
1248           if (eb->path_tracker)
1249             tracker_path_add(eb->path_tracker, path);
1250
1251           /* Dump all contents for a simple 'add'. */
1252           if (kind == svn_node_file)
1253             must_dump_text = TRUE;
1254           must_dump_props = TRUE;
1255         }
1256       else
1257         {
1258           if (eb->path_tracker)
1259             {
1260               SVN_ERR_W(node_must_exist(eb, compare_path, compare_rev,
1261                                         kind, pool),
1262                         apr_psprintf(pool,
1263                                      _("Copying from invalid path to "
1264                                        "'%s' in r%ld"),
1265                                      path, eb->current_rev));
1266               tracker_path_copy(eb->path_tracker, path, compare_path,
1267                                 compare_rev);
1268             }
1269
1270           if (!eb->verify && cmp_rev < eb->oldest_dumped_rev
1271               && eb->notify_func)
1272             {
1273               notify_warning(pool, eb->notify_func, eb->notify_baton,
1274                              svn_repos_notify_warning_found_old_reference,
1275                              _("Referencing data in revision %ld,"
1276                                " which is older than the oldest"
1277                                " dumped revision (r%ld).  Loading this dump"
1278                                " into an empty repository"
1279                                " will fail."),
1280                              cmp_rev, eb->oldest_dumped_rev);
1281               if (eb->found_old_reference)
1282                 *eb->found_old_reference = TRUE;
1283             }
1284
1285           svn_repos__dumpfile_header_pushf(
1286             headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", cmp_rev);
1287           svn_repos__dumpfile_header_push(
1288             headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, cmp_path);
1289
1290           SVN_ERR(svn_fs_revision_root(&compare_root,
1291                                        svn_fs_root_fs(eb->fs_root),
1292                                        compare_rev, pool));
1293
1294           /* Need to decide if the copied node had any extra textual or
1295              property mods as well.  */
1296           SVN_ERR(svn_fs_props_changed(&must_dump_props,
1297                                        compare_root, compare_path,
1298                                        eb->fs_root, path, pool));
1299           if (kind == svn_node_file)
1300             {
1301               svn_checksum_t *checksum;
1302               const char *hex_digest;
1303               SVN_ERR(svn_fs_contents_changed(&must_dump_text,
1304                                               compare_root, compare_path,
1305                                               eb->fs_root, path, pool));
1306
1307               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1308                                            compare_root, compare_path,
1309                                            FALSE, pool));
1310               hex_digest = svn_checksum_to_cstring(checksum, pool);
1311               if (hex_digest)
1312                 svn_repos__dumpfile_header_push(
1313                   headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5, hex_digest);
1314
1315               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1316                                            compare_root, compare_path,
1317                                            FALSE, pool));
1318               hex_digest = svn_checksum_to_cstring(checksum, pool);
1319               if (hex_digest)
1320                 svn_repos__dumpfile_header_push(
1321                   headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1, hex_digest);
1322             }
1323         }
1324       break;
1325     }
1326
1327   if ((! must_dump_text) && (! must_dump_props))
1328     {
1329       /* If we're not supposed to dump text or props, so be it, we can
1330          just go home.  However, if either one needs to be dumped,
1331          then our dumpstream format demands that at a *minimum*, we
1332          see a lone "PROPS-END" as a divider between text and props
1333          content within the content-block. */
1334       SVN_ERR(svn_repos__dump_headers(eb->stream, headers, pool));
1335       len = 1;
1336       return svn_stream_write(eb->stream, "\n", &len); /* ### needed? */
1337     }
1338
1339   /*** Start prepping content to dump... ***/
1340
1341   /* If we are supposed to dump properties, write out a property
1342      length header and generate a stringbuf that contains those
1343      property values here. */
1344   if (must_dump_props)
1345     {
1346       apr_hash_t *prophash, *oldhash = NULL;
1347       svn_stream_t *propstream;
1348
1349       SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
1350
1351       /* If this is a partial dump, then issue a warning if we dump mergeinfo
1352          properties that refer to revisions older than the first revision
1353          dumped. */
1354       if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1)
1355         {
1356           svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
1357                                                       SVN_PROP_MERGEINFO);
1358           if (mergeinfo_str)
1359             {
1360               /* An error in verifying the mergeinfo must not prevent dumping
1361                  the data. Ignore any such error. */
1362               svn_error_clear(verify_mergeinfo_revisions(
1363                                 eb->found_old_mergeinfo,
1364                                 mergeinfo_str->data, eb->oldest_dumped_rev,
1365                                 eb->notify_func, eb->notify_baton,
1366                                 pool));
1367             }
1368         }
1369
1370       /* If we're checking UCS normalization, also parse any changed
1371          mergeinfo and warn about denormalized paths and name
1372          collisions there. */
1373       if (eb->verify && eb->check_normalization && eb->notify_func)
1374         {
1375           /* N.B.: This hash lookup happens only once; the conditions
1376              for verifying historic mergeinfo references and checking
1377              UCS normalization are mutually exclusive. */
1378           svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
1379                                                       SVN_PROP_MERGEINFO);
1380           if (mergeinfo_str)
1381             {
1382               svn_string_t *oldinfo_str = NULL;
1383               if (compare_root)
1384                 {
1385                   SVN_ERR(svn_fs_node_proplist(&oldhash,
1386                                                compare_root, compare_path,
1387                                                pool));
1388                   oldinfo_str = svn_hash_gets(oldhash, SVN_PROP_MERGEINFO);
1389                 }
1390               SVN_ERR(check_mergeinfo_normalization(
1391                           path, mergeinfo_str->data,
1392                           (oldinfo_str ? oldinfo_str->data : NULL),
1393                           eb->notify_func, eb->notify_baton, pool));
1394             }
1395         }
1396
1397       if (eb->use_deltas && compare_root)
1398         {
1399           /* Fetch the old property hash to diff against and output a header
1400              saying that our property contents are a delta. */
1401           if (!oldhash)         /* May have been set for normalization check */
1402             SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
1403                                          pool));
1404           svn_repos__dumpfile_header_push(
1405             headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true");
1406         }
1407       else
1408         oldhash = apr_hash_make(pool);
1409       propstring = svn_stringbuf_create_ensure(0, pool);
1410       propstream = svn_stream_from_stringbuf(propstring, pool);
1411       SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream,
1412                                          "PROPS-END", pool));
1413       SVN_ERR(svn_stream_close(propstream));
1414     }
1415
1416   /* If we are supposed to dump text, write out a text length header
1417      here, and an MD5 checksum (if available). */
1418   if (must_dump_text && (kind == svn_node_file))
1419     {
1420       svn_checksum_t *checksum;
1421       const char *hex_digest;
1422
1423       if (eb->use_deltas)
1424         {
1425           /* Compute the text delta now and write it into a temporary
1426              file, so that we can find its length.  Output a header
1427              saying our text contents are a delta. */
1428           SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
1429                               compare_path, eb->fs_root, path, pool));
1430           svn_repos__dumpfile_header_push(
1431             headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true");
1432
1433           if (compare_root)
1434             {
1435               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1436                                            compare_root, compare_path,
1437                                            FALSE, pool));
1438               hex_digest = svn_checksum_to_cstring(checksum, pool);
1439               if (hex_digest)
1440                 svn_repos__dumpfile_header_push(
1441                   headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, hex_digest);
1442
1443               SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1444                                            compare_root, compare_path,
1445                                            FALSE, pool));
1446               hex_digest = svn_checksum_to_cstring(checksum, pool);
1447               if (hex_digest)
1448                 svn_repos__dumpfile_header_push(
1449                   headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1, hex_digest);
1450             }
1451         }
1452       else
1453         {
1454           /* Just fetch the length of the file. */
1455           SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
1456         }
1457
1458       SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1459                                    eb->fs_root, path, FALSE, pool));
1460       hex_digest = svn_checksum_to_cstring(checksum, pool);
1461       if (hex_digest)
1462         svn_repos__dumpfile_header_push(
1463           headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, hex_digest);
1464
1465       SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1466                                    eb->fs_root, path, FALSE, pool));
1467       hex_digest = svn_checksum_to_cstring(checksum, pool);
1468       if (hex_digest)
1469         svn_repos__dumpfile_header_push(
1470           headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1, hex_digest);
1471     }
1472
1473   /* 'Content-length:' is the last header before we dump the content,
1474      and is the sum of the text and prop contents lengths.  We write
1475      this only for the benefit of non-Subversion RFC-822 parsers. */
1476   SVN_ERR(svn_repos__dump_node_record(eb->stream, headers,
1477                                       must_dump_props ? propstring : NULL,
1478                                       must_dump_text,
1479                                       must_dump_text ? textlen : 0,
1480                                       TRUE /*content_length_always*/,
1481                                       pool));
1482
1483   /* Dump text content */
1484   if (must_dump_text && (kind == svn_node_file))
1485     {
1486       svn_stream_t *contents;
1487
1488       if (delta_file)
1489         {
1490           /* Make sure to close the underlying file when the stream is
1491              closed. */
1492           contents = svn_stream_from_aprfile2(delta_file, FALSE, pool);
1493         }
1494       else
1495         SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
1496
1497       SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool),
1498                                NULL, NULL, pool));
1499     }
1500
1501   len = 2;
1502   return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
1503 }
1504
1505
1506 static svn_error_t *
1507 open_root(void *edit_baton,
1508           svn_revnum_t base_revision,
1509           apr_pool_t *pool,
1510           void **root_baton)
1511 {
1512   *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
1513                                edit_baton, NULL, pool);
1514   return SVN_NO_ERROR;
1515 }
1516
1517
1518 static svn_error_t *
1519 delete_entry(const char *path,
1520              svn_revnum_t revision,
1521              void *parent_baton,
1522              apr_pool_t *pool)
1523 {
1524   struct dir_baton *pb = parent_baton;
1525   const char *mypath = apr_pstrdup(pb->pool, path);
1526
1527   /* remember this path needs to be deleted. */
1528   svn_hash_sets(pb->deleted_entries, mypath, pb);
1529
1530   return SVN_NO_ERROR;
1531 }
1532
1533
1534 static svn_error_t *
1535 add_directory(const char *path,
1536               void *parent_baton,
1537               const char *copyfrom_path,
1538               svn_revnum_t copyfrom_rev,
1539               apr_pool_t *pool,
1540               void **child_baton)
1541 {
1542   struct dir_baton *pb = parent_baton;
1543   struct edit_baton *eb = pb->edit_baton;
1544   void *was_deleted;
1545   svn_boolean_t is_copy = FALSE;
1546   struct dir_baton *new_db
1547     = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, pool);
1548
1549   /* This might be a replacement -- is the path already deleted? */
1550   was_deleted = svn_hash_gets(pb->deleted_entries, path);
1551
1552   /* Detect an add-with-history. */
1553   is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1554
1555   /* Dump the node. */
1556   SVN_ERR(dump_node(eb, path,
1557                     svn_node_dir,
1558                     was_deleted ? svn_node_action_replace : svn_node_action_add,
1559                     is_copy,
1560                     is_copy ? copyfrom_path : NULL,
1561                     is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1562                     pool));
1563
1564   if (was_deleted)
1565     /* Delete the path, it's now been dumped. */
1566     svn_hash_sets(pb->deleted_entries, path, NULL);
1567
1568   /* Check for normalized name clashes, but only if this is actually a
1569      new name in the parent, not a replacement. */
1570   if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
1571     {
1572       pb->check_name_collision = TRUE;
1573     }
1574
1575   new_db->written_out = TRUE;
1576
1577   *child_baton = new_db;
1578   return SVN_NO_ERROR;
1579 }
1580
1581
1582 static svn_error_t *
1583 open_directory(const char *path,
1584                void *parent_baton,
1585                svn_revnum_t base_revision,
1586                apr_pool_t *pool,
1587                void **child_baton)
1588 {
1589   struct dir_baton *pb = parent_baton;
1590   struct edit_baton *eb = pb->edit_baton;
1591   struct dir_baton *new_db;
1592   const char *cmp_path = NULL;
1593   svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
1594
1595   /* If the parent directory has explicit comparison path and rev,
1596      record the same for this one. */
1597   if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
1598     {
1599       cmp_path = svn_relpath_join(pb->cmp_path,
1600                                   svn_relpath_basename(path, pool), pool);
1601       cmp_rev = pb->cmp_rev;
1602     }
1603
1604   new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, pool);
1605   *child_baton = new_db;
1606   return SVN_NO_ERROR;
1607 }
1608
1609
1610 static svn_error_t *
1611 close_directory(void *dir_baton,
1612                 apr_pool_t *pool)
1613 {
1614   struct dir_baton *db = dir_baton;
1615   struct edit_baton *eb = db->edit_baton;
1616   apr_pool_t *subpool = svn_pool_create(pool);
1617   int i;
1618   apr_array_header_t *sorted_entries;
1619
1620   /* Sort entries lexically instead of as paths. Even though the entries
1621    * are full paths they're all in the same directory (see comment in struct
1622    * dir_baton definition). So we really want to sort by basename, in which
1623    * case the lexical sort function is more efficient. */
1624   sorted_entries = svn_sort__hash(db->deleted_entries,
1625                                   svn_sort_compare_items_lexically, pool);
1626   for (i = 0; i < sorted_entries->nelts; i++)
1627     {
1628       const char *path = APR_ARRAY_IDX(sorted_entries, i,
1629                                        svn_sort__item_t).key;
1630
1631       svn_pool_clear(subpool);
1632
1633       /* By sending 'svn_node_unknown', the Node-kind: header simply won't
1634          be written out.  No big deal at all, really.  The loader
1635          shouldn't care.  */
1636       SVN_ERR(dump_node(eb, path,
1637                         svn_node_unknown, svn_node_action_delete,
1638                         FALSE, NULL, SVN_INVALID_REVNUM, subpool));
1639     }
1640
1641   svn_pool_destroy(subpool);
1642   return SVN_NO_ERROR;
1643 }
1644
1645
1646 static svn_error_t *
1647 add_file(const char *path,
1648          void *parent_baton,
1649          const char *copyfrom_path,
1650          svn_revnum_t copyfrom_rev,
1651          apr_pool_t *pool,
1652          void **file_baton)
1653 {
1654   struct dir_baton *pb = parent_baton;
1655   struct edit_baton *eb = pb->edit_baton;
1656   void *was_deleted;
1657   svn_boolean_t is_copy = FALSE;
1658
1659   /* This might be a replacement -- is the path already deleted? */
1660   was_deleted = svn_hash_gets(pb->deleted_entries, path);
1661
1662   /* Detect add-with-history. */
1663   is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1664
1665   /* Dump the node. */
1666   SVN_ERR(dump_node(eb, path,
1667                     svn_node_file,
1668                     was_deleted ? svn_node_action_replace : svn_node_action_add,
1669                     is_copy,
1670                     is_copy ? copyfrom_path : NULL,
1671                     is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1672                     pool));
1673
1674   if (was_deleted)
1675     /* delete the path, it's now been dumped. */
1676     svn_hash_sets(pb->deleted_entries, path, NULL);
1677
1678   /* Check for normalized name clashes, but only if this is actually a
1679      new name in the parent, not a replacement. */
1680   if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
1681     {
1682       pb->check_name_collision = TRUE;
1683     }
1684
1685   *file_baton = NULL;  /* muhahahaha */
1686   return SVN_NO_ERROR;
1687 }
1688
1689
1690 static svn_error_t *
1691 open_file(const char *path,
1692           void *parent_baton,
1693           svn_revnum_t ancestor_revision,
1694           apr_pool_t *pool,
1695           void **file_baton)
1696 {
1697   struct dir_baton *pb = parent_baton;
1698   struct edit_baton *eb = pb->edit_baton;
1699   const char *cmp_path = NULL;
1700   svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
1701
1702   /* If the parent directory has explicit comparison path and rev,
1703      record the same for this one. */
1704   if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
1705     {
1706       cmp_path = svn_relpath_join(pb->cmp_path,
1707                                   svn_relpath_basename(path, pool), pool);
1708       cmp_rev = pb->cmp_rev;
1709     }
1710
1711   SVN_ERR(dump_node(eb, path,
1712                     svn_node_file, svn_node_action_change,
1713                     FALSE, cmp_path, cmp_rev, pool));
1714
1715   *file_baton = NULL;  /* muhahahaha again */
1716   return SVN_NO_ERROR;
1717 }
1718
1719
1720 static svn_error_t *
1721 change_dir_prop(void *parent_baton,
1722                 const char *name,
1723                 const svn_string_t *value,
1724                 apr_pool_t *pool)
1725 {
1726   struct dir_baton *db = parent_baton;
1727   struct edit_baton *eb = db->edit_baton;
1728
1729   /* This function is what distinguishes between a directory that is
1730      opened to merely get somewhere, vs. one that is opened because it
1731      *actually* changed by itself.
1732
1733      Instead of recording the prop changes here, we just use this method
1734      to trigger writing the node; dump_node() finds all the changes. */
1735   if (! db->written_out)
1736     {
1737       SVN_ERR(dump_node(eb, db->path,
1738                         svn_node_dir, svn_node_action_change,
1739                         /* ### We pass is_copy=FALSE; this might be wrong
1740                            but the parameter isn't used when action=change. */
1741                         FALSE, db->cmp_path, db->cmp_rev, pool));
1742       db->written_out = TRUE;
1743     }
1744   return SVN_NO_ERROR;
1745 }
1746
1747 static svn_error_t *
1748 fetch_props_func(apr_hash_t **props,
1749                  void *baton,
1750                  const char *path,
1751                  svn_revnum_t base_revision,
1752                  apr_pool_t *result_pool,
1753                  apr_pool_t *scratch_pool)
1754 {
1755   struct edit_baton *eb = baton;
1756   svn_error_t *err;
1757   svn_fs_root_t *fs_root;
1758
1759   if (!SVN_IS_VALID_REVNUM(base_revision))
1760     base_revision = eb->current_rev - 1;
1761
1762   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1763
1764   err = svn_fs_node_proplist(props, fs_root, path, result_pool);
1765   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1766     {
1767       svn_error_clear(err);
1768       *props = apr_hash_make(result_pool);
1769       return SVN_NO_ERROR;
1770     }
1771   else if (err)
1772     return svn_error_trace(err);
1773
1774   return SVN_NO_ERROR;
1775 }
1776
1777 static svn_error_t *
1778 fetch_kind_func(svn_node_kind_t *kind,
1779                 void *baton,
1780                 const char *path,
1781                 svn_revnum_t base_revision,
1782                 apr_pool_t *scratch_pool)
1783 {
1784   struct edit_baton *eb = baton;
1785   svn_fs_root_t *fs_root;
1786
1787   if (!SVN_IS_VALID_REVNUM(base_revision))
1788     base_revision = eb->current_rev - 1;
1789
1790   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1791
1792   SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
1793
1794   return SVN_NO_ERROR;
1795 }
1796
1797 static svn_error_t *
1798 fetch_base_func(const char **filename,
1799                 void *baton,
1800                 const char *path,
1801                 svn_revnum_t base_revision,
1802                 apr_pool_t *result_pool,
1803                 apr_pool_t *scratch_pool)
1804 {
1805   struct edit_baton *eb = baton;
1806   svn_stream_t *contents;
1807   svn_stream_t *file_stream;
1808   const char *tmp_filename;
1809   svn_error_t *err;
1810   svn_fs_root_t *fs_root;
1811
1812   if (!SVN_IS_VALID_REVNUM(base_revision))
1813     base_revision = eb->current_rev - 1;
1814
1815   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1816
1817   err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
1818   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1819     {
1820       svn_error_clear(err);
1821       *filename = NULL;
1822       return SVN_NO_ERROR;
1823     }
1824   else if (err)
1825     return svn_error_trace(err);
1826   SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
1827                                  svn_io_file_del_on_pool_cleanup,
1828                                  scratch_pool, scratch_pool));
1829   SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
1830
1831   *filename = apr_pstrdup(result_pool, tmp_filename);
1832
1833   return SVN_NO_ERROR;
1834 }
1835
1836
1837 static svn_error_t *
1838 get_dump_editor(const svn_delta_editor_t **editor,
1839                 void **edit_baton,
1840                 svn_fs_t *fs,
1841                 svn_revnum_t to_rev,
1842                 const char *root_path,
1843                 svn_stream_t *stream,
1844                 svn_boolean_t *found_old_reference,
1845                 svn_boolean_t *found_old_mergeinfo,
1846                 svn_error_t *(*custom_close_directory)(void *dir_baton,
1847                                   apr_pool_t *scratch_pool),
1848                 svn_repos_notify_func_t notify_func,
1849                 void *notify_baton,
1850                 svn_revnum_t oldest_dumped_rev,
1851                 svn_boolean_t use_deltas,
1852                 svn_boolean_t verify,
1853                 svn_boolean_t check_normalization,
1854                 apr_pool_t *pool)
1855 {
1856   /* Allocate an edit baton to be stored in every directory baton.
1857      Set it up for the directory baton we create here, which is the
1858      root baton. */
1859   struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1860   svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
1861   svn_delta_shim_callbacks_t *shim_callbacks =
1862                                 svn_delta_shim_callbacks_default(pool);
1863
1864   /* Set up the edit baton. */
1865   eb->stream = stream;
1866   eb->notify_func = notify_func;
1867   eb->notify_baton = notify_baton;
1868   eb->oldest_dumped_rev = oldest_dumped_rev;
1869   eb->path = apr_pstrdup(pool, root_path);
1870   SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
1871   eb->fs = fs;
1872   eb->current_rev = to_rev;
1873   eb->use_deltas = use_deltas;
1874   eb->verify = verify;
1875   eb->check_normalization = check_normalization;
1876   eb->found_old_reference = found_old_reference;
1877   eb->found_old_mergeinfo = found_old_mergeinfo;
1878
1879   /* In non-verification mode, we will allow anything to be dumped because
1880      it might be an incremental dump with possible manual intervention.
1881      Also, this might be the last resort when it comes to data recovery.
1882
1883      Else, make sure that all paths exists at their respective revisions.
1884   */
1885   eb->path_tracker = verify ? tracker_create(to_rev, pool) : NULL;
1886
1887   /* Set up the editor. */
1888   dump_editor->open_root = open_root;
1889   dump_editor->delete_entry = delete_entry;
1890   dump_editor->add_directory = add_directory;
1891   dump_editor->open_directory = open_directory;
1892   if (custom_close_directory)
1893     dump_editor->close_directory = custom_close_directory;
1894   else
1895     dump_editor->close_directory = close_directory;
1896   dump_editor->change_dir_prop = change_dir_prop;
1897   dump_editor->add_file = add_file;
1898   dump_editor->open_file = open_file;
1899
1900   *edit_baton = eb;
1901   *editor = dump_editor;
1902
1903   shim_callbacks->fetch_kind_func = fetch_kind_func;
1904   shim_callbacks->fetch_props_func = fetch_props_func;
1905   shim_callbacks->fetch_base_func = fetch_base_func;
1906   shim_callbacks->fetch_baton = eb;
1907
1908   SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1909                                    NULL, NULL, shim_callbacks, pool, pool));
1910
1911   return SVN_NO_ERROR;
1912 }
1913
1914 /*----------------------------------------------------------------------*/
1915 \f
1916 /** The main dumping routine, svn_repos_dump_fs. **/
1917
1918
1919 /* Helper for svn_repos_dump_fs.
1920
1921    Write a revision record of REV in FS to writable STREAM, using POOL.
1922  */
1923 static svn_error_t *
1924 write_revision_record(svn_stream_t *stream,
1925                       svn_fs_t *fs,
1926                       svn_revnum_t rev,
1927                       apr_pool_t *pool)
1928 {
1929   apr_hash_t *props;
1930   apr_time_t timetemp;
1931   svn_string_t *datevalue;
1932
1933   SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
1934
1935   /* Run revision date properties through the time conversion to
1936      canonicalize them. */
1937   /* ### Remove this when it is no longer needed for sure. */
1938   datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
1939   if (datevalue)
1940     {
1941       SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
1942       datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
1943                                     pool);
1944       svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
1945     }
1946
1947   SVN_ERR(svn_repos__dump_revision_record(stream, rev, NULL, props,
1948                                           TRUE /*props_section_always*/,
1949                                           pool));
1950   return SVN_NO_ERROR;
1951 }
1952
1953
1954
1955 /* The main dumper. */
1956 svn_error_t *
1957 svn_repos_dump_fs3(svn_repos_t *repos,
1958                    svn_stream_t *stream,
1959                    svn_revnum_t start_rev,
1960                    svn_revnum_t end_rev,
1961                    svn_boolean_t incremental,
1962                    svn_boolean_t use_deltas,
1963                    svn_repos_notify_func_t notify_func,
1964                    void *notify_baton,
1965                    svn_cancel_func_t cancel_func,
1966                    void *cancel_baton,
1967                    apr_pool_t *pool)
1968 {
1969   const svn_delta_editor_t *dump_editor;
1970   void *dump_edit_baton = NULL;
1971   svn_revnum_t rev;
1972   svn_fs_t *fs = svn_repos_fs(repos);
1973   apr_pool_t *subpool = svn_pool_create(pool);
1974   svn_revnum_t youngest;
1975   const char *uuid;
1976   int version;
1977   svn_boolean_t found_old_reference = FALSE;
1978   svn_boolean_t found_old_mergeinfo = FALSE;
1979   svn_repos_notify_t *notify;
1980
1981   /* Determine the current youngest revision of the filesystem. */
1982   SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1983
1984   /* Use default vals if necessary. */
1985   if (! SVN_IS_VALID_REVNUM(start_rev))
1986     start_rev = 0;
1987   if (! SVN_IS_VALID_REVNUM(end_rev))
1988     end_rev = youngest;
1989   if (! stream)
1990     stream = svn_stream_empty(pool);
1991
1992   /* Validate the revisions. */
1993   if (start_rev > end_rev)
1994     return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1995                              _("Start revision %ld"
1996                                " is greater than end revision %ld"),
1997                              start_rev, end_rev);
1998   if (end_rev > youngest)
1999     return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2000                              _("End revision %ld is invalid "
2001                                "(youngest revision is %ld)"),
2002                              end_rev, youngest);
2003
2004   /* Write out the UUID. */
2005   SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
2006
2007   /* If we're not using deltas, use the previous version, for
2008      compatibility with svn 1.0.x. */
2009   version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
2010   if (!use_deltas)
2011     version--;
2012
2013   /* Write out "general" metadata for the dumpfile.  In this case, a
2014      magic header followed by a dumpfile format version. */
2015   SVN_ERR(svn_stream_printf(stream, pool,
2016                             SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
2017                             version));
2018   SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
2019                             ": %s\n\n", uuid));
2020
2021   /* Create a notify object that we can reuse in the loop. */
2022   if (notify_func)
2023     notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
2024                                      pool);
2025
2026   /* Main loop:  we're going to dump revision REV.  */
2027   for (rev = start_rev; rev <= end_rev; rev++)
2028     {
2029       svn_fs_root_t *to_root;
2030       svn_boolean_t use_deltas_for_rev;
2031
2032       svn_pool_clear(subpool);
2033
2034       /* Check for cancellation. */
2035       if (cancel_func)
2036         SVN_ERR(cancel_func(cancel_baton));
2037
2038       /* Write the revision record. */
2039       SVN_ERR(write_revision_record(stream, fs, rev, subpool));
2040
2041       /* When dumping revision 0, we just write out the revision record.
2042          The parser might want to use its properties. */
2043       if (rev == 0)
2044         goto loop_end;
2045
2046       /* Fetch the editor which dumps nodes to a file.  Regardless of
2047          what we've been told, don't use deltas for the first rev of a
2048          non-incremental dump. */
2049       use_deltas_for_rev = use_deltas && (incremental || rev != start_rev);
2050       SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, rev,
2051                               "", stream, &found_old_reference,
2052                               &found_old_mergeinfo, NULL,
2053                               notify_func, notify_baton,
2054                               start_rev, use_deltas_for_rev, FALSE, FALSE,
2055                               subpool));
2056
2057       /* Drive the editor in one way or another. */
2058       SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, subpool));
2059
2060       /* If this is the first revision of a non-incremental dump,
2061          we're in for a full tree dump.  Otherwise, we want to simply
2062          replay the revision.  */
2063       if ((rev == start_rev) && (! incremental))
2064         {
2065           /* Compare against revision 0, so everything appears to be added. */
2066           svn_fs_root_t *from_root;
2067           SVN_ERR(svn_fs_revision_root(&from_root, fs, 0, subpool));
2068           SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
2069                                        to_root, "",
2070                                        dump_editor, dump_edit_baton,
2071                                        NULL,
2072                                        NULL,
2073                                        FALSE, /* don't send text-deltas */
2074                                        svn_depth_infinity,
2075                                        FALSE, /* don't send entry props */
2076                                        FALSE, /* don't ignore ancestry */
2077                                        subpool));
2078         }
2079       else
2080         {
2081           /* The normal case: compare consecutive revs. */
2082           SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2083                                     dump_editor, dump_edit_baton,
2084                                     NULL, NULL, subpool));
2085
2086           /* While our editor close_edit implementation is a no-op, we still
2087              do this for completeness. */
2088           SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool));
2089         }
2090
2091     loop_end:
2092       if (notify_func)
2093         {
2094           notify->revision = rev;
2095           notify_func(notify_baton, notify, subpool);
2096         }
2097     }
2098
2099   if (notify_func)
2100     {
2101       /* Did we issue any warnings about references to revisions older than
2102          the oldest dumped revision?  If so, then issue a final generic
2103          warning, since the inline warnings already issued might easily be
2104          missed. */
2105
2106       notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool);
2107       notify_func(notify_baton, notify, subpool);
2108
2109       if (found_old_reference)
2110         {
2111           notify_warning(subpool, notify_func, notify_baton,
2112                          svn_repos_notify_warning_found_old_reference,
2113                          _("The range of revisions dumped "
2114                            "contained references to "
2115                            "copy sources outside that "
2116                            "range."));
2117         }
2118
2119       /* Ditto if we issued any warnings about old revisions referenced
2120          in dumped mergeinfo. */
2121       if (found_old_mergeinfo)
2122         {
2123           notify_warning(subpool, notify_func, notify_baton,
2124                          svn_repos_notify_warning_found_old_mergeinfo,
2125                          _("The range of revisions dumped "
2126                            "contained mergeinfo "
2127                            "which reference revisions outside "
2128                            "that range."));
2129         }
2130     }
2131
2132   svn_pool_destroy(subpool);
2133
2134   return SVN_NO_ERROR;
2135 }
2136
2137
2138 /*----------------------------------------------------------------------*/
2139 \f
2140 /* verify, based on dump */
2141
2142
2143 /* Creating a new revision that changes /A/B/E/bravo means creating new
2144    directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
2145    each entry not changed in the new revision a link back to the entry in a
2146    previous revision.  svn_repos_replay()ing a revision does not verify that
2147    those links are correct.
2148
2149    For paths actually changed in the revision we verify, we get directory
2150    contents or file length twice: once in the dump editor, and once here.
2151    We could create a new verify baton, store in it the changed paths, and
2152    skip those here, but that means building an entire wrapper editor and
2153    managing two levels of batons.  The impact from checking these entries
2154    twice should be minimal, while the code to avoid it is not.
2155 */
2156
2157 static svn_error_t *
2158 verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
2159                        void *val, apr_pool_t *pool)
2160 {
2161   struct dir_baton *db = baton;
2162   svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
2163   char *path;
2164   svn_boolean_t right_kind;
2165
2166   path = svn_relpath_join(db->path, (const char *)key, pool);
2167
2168   /* since we can't access the directory entries directly by their ID,
2169      we need to navigate from the FS_ROOT to them (relatively expensive
2170      because we may start at a never rev than the last change to node).
2171      We check that the node kind stored in the noderev matches the dir
2172      entry.  This also ensures that all entries point to valid noderevs.
2173    */
2174   switch (dirent->kind) {
2175   case svn_node_dir:
2176     SVN_ERR(svn_fs_is_dir(&right_kind, db->edit_baton->fs_root, path, pool));
2177     if (!right_kind)
2178       return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2179                                _("Node '%s' is not a directory."),
2180                                path);
2181
2182     break;
2183   case svn_node_file:
2184     SVN_ERR(svn_fs_is_file(&right_kind, db->edit_baton->fs_root, path, pool));
2185     if (!right_kind)
2186       return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2187                                _("Node '%s' is not a file."),
2188                                path);
2189     break;
2190   default:
2191     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2192                              _("Unexpected node kind %d for '%s'"),
2193                              dirent->kind, path);
2194   }
2195
2196   return SVN_NO_ERROR;
2197 }
2198
2199 /* Baton used by the check_name_collision hash iterator. */
2200 struct check_name_collision_baton
2201 {
2202   struct dir_baton *dir_baton;
2203   apr_hash_t *normalized;
2204   svn_membuf_t buffer;
2205 };
2206
2207 /* Scan the directory and report all entry names that differ only in
2208    Unicode character representation. */
2209 static svn_error_t *
2210 check_name_collision(void *baton, const void *key, apr_ssize_t klen,
2211                      void *val, apr_pool_t *iterpool)
2212 {
2213   struct check_name_collision_baton *const cb = baton;
2214   const char *name;
2215   const char *found;
2216
2217   SVN_ERR(svn_utf__normalize(&name, key, klen, &cb->buffer));
2218
2219   found = svn_hash_gets(cb->normalized, name);
2220   if (!found)
2221     svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2222                   normalized_unique);
2223   else if (found == normalized_collision)
2224     /* Skip already reported collision */;
2225   else
2226     {
2227       struct dir_baton *const db = cb->dir_baton;
2228       struct edit_baton *const eb = db->edit_baton;
2229       const char* normpath;
2230
2231       svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2232                     normalized_collision);
2233
2234       SVN_ERR(svn_utf__normalize(
2235                   &normpath, svn_relpath_join(db->path, name, iterpool),
2236                   SVN_UTF__UNKNOWN_LENGTH, &cb->buffer));
2237       notify_warning(iterpool, eb->notify_func, eb->notify_baton,
2238                      svn_repos_notify_warning_name_collision,
2239                      _("Duplicate representation of path '%s'"), normpath);
2240     }
2241   return SVN_NO_ERROR;
2242 }
2243
2244
2245 static svn_error_t *
2246 verify_close_directory(void *dir_baton, apr_pool_t *pool)
2247 {
2248   struct dir_baton *db = dir_baton;
2249   apr_hash_t *dirents;
2250   SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root,
2251                              db->path, pool));
2252   SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
2253                             dir_baton, pool));
2254
2255   if (db->check_name_collision)
2256     {
2257       struct check_name_collision_baton check_baton;
2258       check_baton.dir_baton = db;
2259       check_baton.normalized = apr_hash_make(pool);
2260       svn_membuf__create(&check_baton.buffer, 0, pool);
2261       SVN_ERR(svn_iter_apr_hash(NULL, dirents, check_name_collision,
2262                                 &check_baton, pool));
2263     }
2264
2265   return close_directory(dir_baton, pool);
2266 }
2267
2268 /* Verify revision REV in file system FS. */
2269 static svn_error_t *
2270 verify_one_revision(svn_fs_t *fs,
2271                     svn_revnum_t rev,
2272                     svn_repos_notify_func_t notify_func,
2273                     void *notify_baton,
2274                     svn_revnum_t start_rev,
2275                     svn_boolean_t check_normalization,
2276                     svn_cancel_func_t cancel_func,
2277                     void *cancel_baton,
2278                     apr_pool_t *scratch_pool)
2279 {
2280   const svn_delta_editor_t *dump_editor;
2281   void *dump_edit_baton;
2282   svn_fs_root_t *to_root;
2283   apr_hash_t *props;
2284   const svn_delta_editor_t *cancel_editor;
2285   void *cancel_edit_baton;
2286
2287   /* Get cancellable dump editor, but with our close_directory handler.*/
2288   SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
2289                           fs, rev, "",
2290                           svn_stream_empty(scratch_pool),
2291                           NULL, NULL,
2292                           verify_close_directory,
2293                           notify_func, notify_baton,
2294                           start_rev,
2295                           FALSE, TRUE, /* use_deltas, verify */
2296                           check_normalization,
2297                           scratch_pool));
2298   SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2299                                             dump_editor, dump_edit_baton,
2300                                             &cancel_editor,
2301                                             &cancel_edit_baton,
2302                                             scratch_pool));
2303   SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, scratch_pool));
2304   SVN_ERR(svn_fs_verify_root(to_root, scratch_pool));
2305   SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2306                             cancel_editor, cancel_edit_baton,
2307                             NULL, NULL, scratch_pool));
2308
2309   /* While our editor close_edit implementation is a no-op, we still
2310      do this for completeness. */
2311   SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, scratch_pool));
2312
2313   SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, scratch_pool));
2314
2315   return SVN_NO_ERROR;
2316 }
2317
2318 /* Baton type used for forwarding notifications from FS API to REPOS API. */
2319 struct verify_fs_notify_func_baton_t
2320 {
2321    /* notification function to call (must not be NULL) */
2322    svn_repos_notify_func_t notify_func;
2323
2324    /* baton to use for it */
2325    void *notify_baton;
2326
2327    /* type of notification to send (we will simply plug in the revision) */
2328    svn_repos_notify_t *notify;
2329 };
2330
2331 /* Forward the notification to BATON. */
2332 static void
2333 verify_fs_notify_func(svn_revnum_t revision,
2334                        void *baton,
2335                        apr_pool_t *pool)
2336 {
2337   struct verify_fs_notify_func_baton_t *notify_baton = baton;
2338
2339   notify_baton->notify->revision = revision;
2340   notify_baton->notify_func(notify_baton->notify_baton,
2341                             notify_baton->notify, pool);
2342 }
2343
2344 static svn_error_t *
2345 report_error(svn_revnum_t revision,
2346              svn_error_t *verify_err,
2347              svn_repos_verify_callback_t verify_callback,
2348              void *verify_baton,
2349              apr_pool_t *pool)
2350 {
2351   if (verify_callback)
2352     {
2353       svn_error_t *cb_err;
2354
2355       /* The caller provided us with a callback, so make him responsible
2356          for what's going to happen with the error. */
2357       cb_err = verify_callback(verify_baton, revision, verify_err, pool);
2358       svn_error_clear(verify_err);
2359       SVN_ERR(cb_err);
2360
2361       return SVN_NO_ERROR;
2362     }
2363   else
2364     {
2365       /* No callback -- no second guessing.  Just return the error. */
2366       return svn_error_trace(verify_err);
2367     }
2368 }
2369
2370 svn_error_t *
2371 svn_repos_verify_fs3(svn_repos_t *repos,
2372                      svn_revnum_t start_rev,
2373                      svn_revnum_t end_rev,
2374                      svn_boolean_t check_normalization,
2375                      svn_boolean_t metadata_only,
2376                      svn_repos_notify_func_t notify_func,
2377                      void *notify_baton,
2378                      svn_repos_verify_callback_t verify_callback,
2379                      void *verify_baton,
2380                      svn_cancel_func_t cancel_func,
2381                      void *cancel_baton,
2382                      apr_pool_t *pool)
2383 {
2384   svn_fs_t *fs = svn_repos_fs(repos);
2385   svn_revnum_t youngest;
2386   svn_revnum_t rev;
2387   apr_pool_t *iterpool = svn_pool_create(pool);
2388   svn_repos_notify_t *notify;
2389   svn_fs_progress_notify_func_t verify_notify = NULL;
2390   struct verify_fs_notify_func_baton_t *verify_notify_baton = NULL;
2391   svn_error_t *err;
2392
2393   /* Determine the current youngest revision of the filesystem. */
2394   SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2395
2396   /* Use default vals if necessary. */
2397   if (! SVN_IS_VALID_REVNUM(start_rev))
2398     start_rev = 0;
2399   if (! SVN_IS_VALID_REVNUM(end_rev))
2400     end_rev = youngest;
2401
2402   /* Validate the revisions. */
2403   if (start_rev > end_rev)
2404     return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2405                              _("Start revision %ld"
2406                                " is greater than end revision %ld"),
2407                              start_rev, end_rev);
2408   if (end_rev > youngest)
2409     return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2410                              _("End revision %ld is invalid "
2411                                "(youngest revision is %ld)"),
2412                              end_rev, youngest);
2413
2414   /* Create a notify object that we can reuse within the loop and a
2415      forwarding structure for notifications from inside svn_fs_verify(). */
2416   if (notify_func)
2417     {
2418       notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, pool);
2419
2420       verify_notify = verify_fs_notify_func;
2421       verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
2422       verify_notify_baton->notify_func = notify_func;
2423       verify_notify_baton->notify_baton = notify_baton;
2424       verify_notify_baton->notify
2425         = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool);
2426     }
2427
2428   /* Verify global metadata and backend-specific data first. */
2429   err = svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
2430                       start_rev, end_rev,
2431                       verify_notify, verify_notify_baton,
2432                       cancel_func, cancel_baton, pool);
2433
2434   if (err && err->apr_err == SVN_ERR_CANCELLED)
2435     {
2436       return svn_error_trace(err);
2437     }
2438   else if (err)
2439     {
2440       SVN_ERR(report_error(SVN_INVALID_REVNUM, err, verify_callback,
2441                            verify_baton, iterpool));
2442     }
2443
2444   if (!metadata_only)
2445     for (rev = start_rev; rev <= end_rev; rev++)
2446       {
2447         svn_pool_clear(iterpool);
2448
2449         /* Wrapper function to catch the possible errors. */
2450         err = verify_one_revision(fs, rev, notify_func, notify_baton,
2451                                   start_rev, check_normalization,
2452                                   cancel_func, cancel_baton,
2453                                   iterpool);
2454
2455         if (err && err->apr_err == SVN_ERR_CANCELLED)
2456           {
2457             return svn_error_trace(err);
2458           }
2459         else if (err)
2460           {
2461             SVN_ERR(report_error(rev, err, verify_callback, verify_baton,
2462                                  iterpool));
2463           }
2464         else if (notify_func)
2465           {
2466             /* Tell the caller that we're done with this revision. */
2467             notify->revision = rev;
2468             notify_func(notify_baton, notify, iterpool);
2469           }
2470       }
2471
2472   /* We're done. */
2473   if (notify_func)
2474     {
2475       notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool);
2476       notify_func(notify_baton, notify, iterpool);
2477     }
2478
2479   svn_pool_destroy(iterpool);
2480
2481   return SVN_NO_ERROR;
2482 }