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