1 /* dump.c --- writing filesystem contents into a portable 'dumpfile' format.
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
20 * ====================================================================
26 #include "svn_private_config.h"
27 #include "svn_pools.h"
28 #include "svn_error.h"
32 #include "svn_repos.h"
33 #include "svn_string.h"
34 #include "svn_dirent_uri.h"
37 #include "svn_checksum.h"
38 #include "svn_props.h"
39 #include "svn_sorts.h"
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"
48 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
50 /*----------------------------------------------------------------------*/
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.
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.
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.
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
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.
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.
80 typedef struct path_tracker_entry_t
82 /* path in the current tree */
83 svn_stringbuf_t *path;
85 /* copy-from path (must be empty if COPYFROM_REV is SVN_INVALID_REVNUM) */
86 svn_stringbuf_t *copyfrom_path;
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;
92 /* if FALSE, PATH has been deleted */
94 } path_tracker_entry_t;
96 /* Tracks all tree modifications above the current path.
98 typedef struct path_tracker_t
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.
104 apr_array_header_t *stack;
106 /* Number of relevant entries in STACK. May be 0 */
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.
113 svn_revnum_t revision;
115 /* Allocate container entries here. */
119 /* Return a new path tracker object for REVISION, allocated in POOL.
121 static path_tracker_t *
122 tracker_create(svn_revnum_t revision,
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;
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.
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.
142 tracker_trim(path_tracker_t *tracker,
144 svn_boolean_t allow_exact_match)
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]
151 for (; tracker->depth; --tracker->depth)
153 path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack,
155 path_tracker_entry_t);
157 = svn_dirent_skip_ancestor(parent->path->data, path);
159 /* always keep parents. Keep exact matches when allowed. */
160 if (rel_path && (allow_exact_match || *rel_path != '\0'))
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.
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.
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
178 tracker_lookup(const char **orig_path,
179 svn_revnum_t *orig_rev,
180 path_tracker_t *tracker,
184 tracker_trim(tracker, path, TRUE);
185 if (tracker->depth == 0)
187 /* no tree changes -> paths are the same as in the previous rev. */
189 *orig_rev = tracker->revision - 1;
193 path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack,
195 path_tracker_entry_t);
199 = svn_dirent_skip_ancestor(parent->path->data, path);
201 if (parent->copyfrom_rev != SVN_INVALID_REVNUM)
203 /* parent is a copy with history. Translate path. */
204 *orig_path = svn_dirent_join(parent->copyfrom_path->data,
206 *orig_rev = parent->copyfrom_rev;
208 else if (*rel_path == '\0')
210 /* added in this revision with no history */
212 *orig_rev = tracker->revision;
216 /* parent got added but not this path */
218 *orig_rev = SVN_INVALID_REVNUM;
223 /* (maybe parent) path has been deleted */
225 *orig_rev = SVN_INVALID_REVNUM;
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
234 Only the PATH member of the result is being updated. All other members
235 will have undefined values.
237 static path_tracker_entry_t *
238 tracker_add_entry(path_tracker_t *tracker,
241 path_tracker_entry_t *entry;
242 tracker_trim(tracker, path, FALSE);
244 if (tracker->depth == tracker->stack->nelts)
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);
252 entry = &APR_ARRAY_IDX(tracker->stack, tracker->depth,
253 path_tracker_entry_t);
256 svn_stringbuf_set(entry->path, path);
262 /* Update the TRACKER with a copy from COPYFROM_PATH@COPYFROM_REV to
263 PATH in the tracked revision.
266 tracker_path_copy(path_tracker_t *tracker,
268 const char *copyfrom_path,
269 svn_revnum_t copyfrom_rev)
271 path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
273 svn_stringbuf_set(entry->copyfrom_path, copyfrom_path);
274 entry->copyfrom_rev = copyfrom_rev;
275 entry->exists = TRUE;
278 /* Update the TRACKER with a plain addition of PATH (without history).
281 tracker_path_add(path_tracker_t *tracker,
284 path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
286 svn_stringbuf_setempty(entry->copyfrom_path);
287 entry->copyfrom_rev = SVN_INVALID_REVNUM;
288 entry->exists = TRUE;
291 /* Update the TRACKER with a replacement of PATH with a plain addition
295 tracker_path_replace(path_tracker_t *tracker,
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);
303 /* Update the TRACKER with a deletion of PATH.
306 tracker_path_delete(path_tracker_t *tracker,
309 path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
311 svn_stringbuf_setempty(entry->copyfrom_path);
312 entry->copyfrom_rev = SVN_INVALID_REVNUM;
313 entry->exists = FALSE;
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
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)
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;
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,
339 temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool);
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));
348 /* Get the length of the temporary file and rewind it. */
349 SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool));
352 return svn_io_file_seek(*tempfile, APR_SET, &offset, pool);
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.
360 __attribute__((format(printf, 5, 6)))
362 notify_warning(apr_pool_t *scratch_pool,
363 svn_repos_notify_func_t notify_func,
365 svn_repos_notify_warning_t warning,
366 const char *warning_fmt,
370 svn_repos_notify_t *notify;
372 if (notify_func == NULL)
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);
381 notify_func(notify_baton, notify, scratch_pool);
385 /*----------------------------------------------------------------------*/
387 /* Write to STREAM the header in HEADERS named KEY, if present.
390 write_header(svn_stream_t *stream,
393 apr_pool_t *scratch_pool)
395 const char *val = svn_hash_gets(headers, key);
399 SVN_ERR(svn_stream_printf(stream, scratch_pool,
400 "%s: %s\n", key, val));
405 /* Write headers, in arbitrary order.
406 * ### TODO: use a stable order
407 * ### Modifies HEADERS.
410 write_revision_headers(svn_stream_t *stream,
412 apr_pool_t *scratch_pool)
415 apr_hash_index_t *hi;
417 static const char *revision_headers_order[] =
419 SVN_REPOS_DUMPFILE_REVISION_NUMBER, /* must be first */
423 /* Write some headers in a given order */
424 for (h = revision_headers_order; *h; h++)
426 SVN_ERR(write_header(stream, headers, *h, scratch_pool));
427 svn_hash_sets(headers, *h, NULL);
430 /* Write any and all remaining headers except Content-length.
431 * ### TODO: use a stable order
433 for (hi = apr_hash_first(scratch_pool, headers); hi; hi = apr_hash_next(hi))
435 const char *key = apr_hash_this_key(hi);
437 if (strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH) != 0)
438 SVN_ERR(write_header(stream, headers, key, scratch_pool));
441 /* Content-length must be last */
442 SVN_ERR(write_header(stream, headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
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.
451 typedef struct svn_repos__dumpfile_header_entry_t {
452 const char *key, *val;
453 } svn_repos__dumpfile_header_entry_t;
455 svn_repos__dumpfile_headers_t *
456 svn_repos__dumpfile_headers_create(apr_pool_t *pool)
458 svn_repos__dumpfile_headers_t *headers
459 = apr_array_make(pool, 5, sizeof(svn_repos__dumpfile_header_entry_t));
465 svn_repos__dumpfile_header_push(svn_repos__dumpfile_headers_t *headers,
469 svn_repos__dumpfile_header_entry_t *h
470 = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t);
472 h->key = apr_pstrdup(headers->pool, key);
473 h->val = apr_pstrdup(headers->pool, val);
477 svn_repos__dumpfile_header_pushf(svn_repos__dumpfile_headers_t *headers,
483 svn_repos__dumpfile_header_entry_t *h
484 = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t);
486 h->key = apr_pstrdup(headers->pool, key);
487 va_start(ap, val_fmt);
488 h->val = apr_pvsprintf(headers->pool, val_fmt, ap);
493 svn_repos__dump_headers(svn_stream_t *stream,
494 svn_repos__dumpfile_headers_t *headers,
495 apr_pool_t *scratch_pool)
499 for (i = 0; i < headers->nelts; i++)
501 svn_repos__dumpfile_header_entry_t *h
502 = &APR_ARRAY_IDX(headers, i, svn_repos__dumpfile_header_entry_t);
504 SVN_ERR(svn_stream_printf(stream, scratch_pool,
505 "%s: %s\n", h->key, h->val));
509 SVN_ERR(svn_stream_puts(stream, "\n"));
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)
522 svn_stringbuf_t *propstring = NULL;
526 headers = apr_hash_copy(scratch_pool, extra_headers);
528 headers = apr_hash_make(scratch_pool);
530 /* ### someday write a revision-content-checksum */
532 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER,
533 apr_psprintf(scratch_pool, "%ld", revision));
535 if (apr_hash_count(revprops) || props_section_always)
537 svn_stream_t *propstream;
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));
544 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
545 apr_psprintf(scratch_pool,
546 "%" APR_SIZE_T_FMT, propstring->len));
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));
558 SVN_ERR(write_revision_headers(dump_stream, headers, scratch_pool));
561 SVN_ERR(svn_stream_puts(dump_stream, "\n"));
566 SVN_ERR(svn_stream_write(dump_stream, propstring->data, &propstring->len));
569 /* put an end to revision */
570 SVN_ERR(svn_stream_puts(dump_stream, "\n"));
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)
584 svn_filesize_t content_length = 0;
586 /* add content-length headers */
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;
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;
601 if (content_length_always || props_str || has_text)
603 svn_repos__dumpfile_header_pushf(
604 headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
605 "%" SVN_FILESIZE_T_FMT, content_length);
608 /* write the headers */
609 SVN_ERR(svn_repos__dump_headers(dump_stream, headers, scratch_pool));
611 /* write the props */
614 SVN_ERR(svn_stream_write(dump_stream, props_str->data, &props_str->len));
619 /*----------------------------------------------------------------------*/
621 /** An editor which dumps node-data in 'dumpfile format' to a file. **/
623 /* Look, mom! No file batons! */
627 /* The relpath which implicitly prepends all full paths coming into
628 this editor. This will almost always be "". */
631 /* The stream to dump to. */
632 svn_stream_t *stream;
634 /* Send feedback here, if non-NULL */
635 svn_repos_notify_func_t notify_func;
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;
642 /* The fs, so we can grab historic information if needed. */
645 /* True if dumped nodes should output deltas instead of full text. */
646 svn_boolean_t use_deltas;
648 /* True if this "dump" is in fact a verify. */
649 svn_boolean_t verify;
651 /* True if checking UCS normalization during a verify. */
652 svn_boolean_t check_normalization;
654 /* The first revision dumped in this dumpstream. */
655 svn_revnum_t oldest_dumped_rev;
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;
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;
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;
672 struct edit_baton *edit_baton;
674 /* has this directory been written to the output stream? */
675 svn_boolean_t written_out;
677 /* the repository relpath associated with this directory */
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;
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;
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;
699 /* pool to be used for deleting the hash items */
704 /* Make a directory baton to represent the directory was path
705 (relative to EDIT_BATON's path) is PATH.
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.
712 PB is the directory baton of this directory's parent,
713 or NULL if this is the top-level directory of the edit.
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,
721 struct dir_baton *pb,
724 struct edit_baton *eb = edit_baton;
725 struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
726 const char *full_path;
728 /* A path relative to nothing? I don't think so. */
729 SVN_ERR_ASSERT_NO_RETURN(!path || pb);
731 /* Construct the full path of this node. */
733 full_path = svn_relpath_join(eb->path, path, pool);
735 full_path = apr_pstrdup(pool, eb->path);
737 /* Remove leading slashes from copyfrom paths. */
739 cmp_path = svn_relpath_canonicalize(cmp_path, pool);
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;
754 fetch_kind_func(svn_node_kind_t *kind,
757 svn_revnum_t base_revision,
758 apr_pool_t *scratch_pool);
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,
766 Use SCRATCH_POOL for temporary allocations.
767 No-op if EB's path tracker has not been initialized.
770 node_must_exist(struct edit_baton *eb,
772 svn_revnum_t revision,
773 svn_node_kind_t expected_kind,
774 apr_pool_t *scratch_pool)
776 svn_node_kind_t kind = svn_node_none;
778 /* in case the caller is trying something stupid ... */
779 if (eb->path_tracker == NULL)
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);
787 /* determine the node type (default: no such node) */
789 SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
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."),
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);
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.
810 Use SCRATCH_POOL for temporary allocations.
811 No-op if EB's path tracker has not been initialized.
814 node_must_not_exist(struct edit_baton *eb,
816 svn_revnum_t revision,
817 apr_pool_t *scratch_pool)
819 svn_node_kind_t kind = svn_node_none;
821 /* in case the caller is trying something stupid ... */
822 if (eb->path_tracker == NULL)
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);
830 /* determine the node type (default: no such node) */
832 SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
835 if (kind != svn_node_none)
836 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
837 _("Path '%s' exists in r%ld."),
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.
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,
855 svn_mergeinfo_t mergeinfo, old_mergeinfo;
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,
863 if (apr_hash_count(old_mergeinfo))
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 "
873 if (found_old_mergeinfo)
874 *found_old_mergeinfo = TRUE;
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";
886 /* Baton for extract_mergeinfo_paths */
887 struct extract_mergeinfo_paths_baton
890 svn_boolean_t normalize;
894 /* Hash iterator that uniquifies all keys into a single hash table,
895 optionally normalizing them first. */
897 extract_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
898 void *val, apr_pool_t *iterpool)
900 struct extract_mergeinfo_paths_baton *const xb = baton;
904 SVN_ERR(svn_utf__normalize(&normkey, key, klen, &xb->buffer));
905 svn_hash_sets(xb->result,
906 apr_pstrdup(xb->buffer.pool, normkey),
910 apr_hash_set(xb->result,
911 apr_pmemdup(xb->buffer.pool, key, klen + 1), klen,
916 /* Baton for filter_mergeinfo_paths */
917 struct filter_mergeinfo_paths_baton
922 /* Compare two sets of denormalized paths from mergeinfo entries,
923 removing duplicates. */
925 filter_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
926 void *val, apr_pool_t *iterpool)
928 struct filter_mergeinfo_paths_baton *const fb = baton;
930 if (apr_hash_get(fb->paths, key, klen))
931 apr_hash_set(fb->paths, key, klen, NULL);
936 /* Baton used by the check_mergeinfo_normalization hash iterator. */
937 struct verify_mergeinfo_normalization_baton
940 apr_hash_t *normalized_paths;
942 svn_repos_notify_func_t notify_func;
946 /* Hash iterator that verifies normalization and collision of paths in
947 an svn:mergeinfo property. */
949 verify_mergeinfo_normalization(void *baton, const void *key, apr_ssize_t klen,
950 void *val, apr_pool_t *iterpool)
952 struct verify_mergeinfo_normalization_baton *const vb = baton;
954 const char *const path = key;
955 const char *normpath;
958 SVN_ERR(svn_utf__normalize(&normpath, path, klen, &vb->buffer));
959 found = svn_hash_gets(vb->normalized_paths, normpath);
961 svn_hash_sets(vb->normalized_paths,
962 apr_pstrdup(vb->buffer.pool, normpath),
964 else if (found == normalized_collision)
965 /* Skip already reported collision */;
968 /* Report path collision in mergeinfo */
969 svn_hash_sets(vb->normalized_paths,
970 apr_pstrdup(vb->buffer.pool, normpath),
971 normalized_collision);
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);
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. */
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,
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;
1002 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, new_mergeinfo, pool));
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;
1014 struct filter_mergeinfo_paths_baton filter_baton;
1015 svn_mergeinfo_t oldinfo;
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;
1025 filter_baton.paths = added_paths;
1026 SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
1027 filter_mergeinfo_paths,
1028 &filter_baton, pool));
1031 normalized_paths = apr_hash_make(pool);
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));
1042 return SVN_NO_ERROR;
1046 /* A special case of dump_node(), for a delete record.
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.
1053 * Also it doesn't do a path-tracker check.
1055 static svn_error_t *
1056 dump_node_delete(svn_stream_t *stream,
1057 const char *node_relpath,
1060 svn_repos__dumpfile_headers_t *headers
1061 = svn_repos__dumpfile_headers_create(pool);
1063 /* Node-path: ... */
1064 svn_repos__dumpfile_header_push(
1065 headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
1067 /* Node-action: delete */
1068 svn_repos__dumpfile_header_push(
1069 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1071 SVN_ERR(svn_repos__dump_headers(stream, headers, pool));
1072 return SVN_NO_ERROR;
1075 /* This helper is the main "meat" of the editor -- it does all the
1076 work of writing a node record.
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.
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.
1087 static svn_error_t *
1088 dump_node(struct edit_baton *eb,
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,
1097 svn_stringbuf_t *propstring;
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;
1108 /* Maybe validate the path. */
1109 if (eb->verify || eb->notify_func)
1111 svn_error_t *err = svn_fs__path_valid(path, pool);
1115 if (eb->notify_func)
1117 char errbuf[512]; /* ### svn_strerror() magic number */
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"),
1123 svn_err_best_message(err, errbuf, sizeof(errbuf)));
1126 /* Return the error in addition to notifying about it. */
1128 return svn_error_trace(err);
1130 svn_error_clear(err);
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");
1144 /* Remove leading slashes from copyfrom paths. */
1146 cmp_path = svn_relpath_canonicalize(cmp_path, pool);
1148 /* Validate the comparison path/rev. */
1149 if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
1151 compare_path = cmp_path;
1152 compare_rev = cmp_rev;
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));
1163 svn_repos__dumpfile_header_push(
1164 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change");
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));
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));
1180 case svn_node_action_delete:
1181 if (eb->path_tracker)
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);
1189 svn_repos__dumpfile_header_push(
1190 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1192 /* we can leave this routine quietly now, don't need to dump
1194 must_dump_text = FALSE;
1195 must_dump_props = FALSE;
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),
1203 _("Replacing non-existent path '%s' in r%ld"),
1204 path, eb->current_rev));
1208 if (eb->path_tracker)
1209 tracker_path_replace(eb->path_tracker, path);
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");
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;
1223 /* more complex: delete original, then add-with-history. */
1224 /* ### Why not write a 'replace' record? Don't know. */
1226 if (eb->path_tracker)
1228 tracker_path_delete(eb->path_tracker, path);
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));
1235 /* The remaining action is a non-replacing add-with-history */
1236 /* action = svn_node_action_add; */
1238 /* FALL THROUGH to 'add' */
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),
1244 _("Adding already existing path '%s' in r%ld"),
1245 path, eb->current_rev));
1247 svn_repos__dumpfile_header_push(
1248 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
1252 if (eb->path_tracker)
1253 tracker_path_add(eb->path_tracker, path);
1255 /* Dump all contents for a simple 'add'. */
1256 if (kind == svn_node_file)
1257 must_dump_text = TRUE;
1258 must_dump_props = TRUE;
1262 if (eb->path_tracker)
1264 SVN_ERR_W(node_must_exist(eb, compare_path, compare_rev,
1267 _("Copying from invalid path to "
1269 path, eb->current_rev));
1270 tracker_path_copy(eb->path_tracker, path, compare_path,
1274 if (!eb->verify && cmp_rev < eb->oldest_dumped_rev
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"
1284 cmp_rev, eb->oldest_dumped_rev);
1285 if (eb->found_old_reference)
1286 *eb->found_old_reference = TRUE;
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);
1294 SVN_ERR(svn_fs_revision_root(&compare_root,
1295 svn_fs_root_fs(eb->fs_root),
1296 compare_rev, pool));
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)
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));
1311 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1312 compare_root, compare_path,
1314 hex_digest = svn_checksum_to_cstring(checksum, pool);
1316 svn_repos__dumpfile_header_push(
1317 headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5, hex_digest);
1319 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1320 compare_root, compare_path,
1322 hex_digest = svn_checksum_to_cstring(checksum, pool);
1324 svn_repos__dumpfile_header_push(
1325 headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1, hex_digest);
1331 if ((! must_dump_text) && (! must_dump_props))
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));
1340 return svn_stream_write(eb->stream, "\n", &len); /* ### needed? */
1343 /*** Start prepping content to dump... ***/
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)
1350 apr_hash_t *prophash, *oldhash = NULL;
1351 svn_stream_t *propstream;
1353 SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
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
1358 if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1)
1360 svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
1361 SVN_PROP_MERGEINFO);
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,
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)
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);
1386 svn_string_t *oldinfo_str = NULL;
1389 SVN_ERR(svn_fs_node_proplist(&oldhash,
1390 compare_root, compare_path,
1392 oldinfo_str = svn_hash_gets(oldhash, SVN_PROP_MERGEINFO);
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));
1401 if (eb->use_deltas && compare_root)
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,
1408 svn_repos__dumpfile_header_push(
1409 headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true");
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));
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))
1424 svn_checksum_t *checksum;
1425 const char *hex_digest;
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");
1439 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1440 compare_root, compare_path,
1442 hex_digest = svn_checksum_to_cstring(checksum, pool);
1444 svn_repos__dumpfile_header_push(
1445 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, hex_digest);
1447 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1448 compare_root, compare_path,
1450 hex_digest = svn_checksum_to_cstring(checksum, pool);
1452 svn_repos__dumpfile_header_push(
1453 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1, hex_digest);
1458 /* Just fetch the length of the file. */
1459 SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
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);
1466 svn_repos__dumpfile_header_push(
1467 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, hex_digest);
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);
1473 svn_repos__dumpfile_header_push(
1474 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1, hex_digest);
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,
1483 must_dump_text ? textlen : 0,
1484 TRUE /*content_length_always*/,
1487 /* Dump text content */
1488 if (must_dump_text && (kind == svn_node_file))
1490 svn_stream_t *contents;
1494 /* Make sure to close the underlying file when the stream is
1496 contents = svn_stream_from_aprfile2(delta_file, FALSE, pool);
1499 SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
1501 SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool),
1506 return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
1510 static svn_error_t *
1511 open_root(void *edit_baton,
1512 svn_revnum_t base_revision,
1516 *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
1517 edit_baton, NULL, pool);
1518 return SVN_NO_ERROR;
1522 static svn_error_t *
1523 delete_entry(const char *path,
1524 svn_revnum_t revision,
1528 struct dir_baton *pb = parent_baton;
1529 const char *mypath = apr_pstrdup(pb->pool, path);
1531 /* remember this path needs to be deleted. */
1532 svn_hash_sets(pb->deleted_entries, mypath, pb);
1534 return SVN_NO_ERROR;
1538 static svn_error_t *
1539 add_directory(const char *path,
1541 const char *copyfrom_path,
1542 svn_revnum_t copyfrom_rev,
1546 struct dir_baton *pb = parent_baton;
1547 struct edit_baton *eb = pb->edit_baton;
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);
1553 /* This might be a replacement -- is the path already deleted? */
1554 was_deleted = svn_hash_gets(pb->deleted_entries, path);
1556 /* Detect an add-with-history. */
1557 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1559 /* Dump the node. */
1560 SVN_ERR(dump_node(eb, path,
1562 was_deleted ? svn_node_action_replace : svn_node_action_add,
1564 is_copy ? copyfrom_path : NULL,
1565 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1569 /* Delete the path, it's now been dumped. */
1570 svn_hash_sets(pb->deleted_entries, path, NULL);
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)
1576 pb->check_name_collision = TRUE;
1579 new_db->written_out = TRUE;
1581 *child_baton = new_db;
1582 return SVN_NO_ERROR;
1586 static svn_error_t *
1587 open_directory(const char *path,
1589 svn_revnum_t base_revision,
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;
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))
1603 cmp_path = svn_relpath_join(pb->cmp_path,
1604 svn_relpath_basename(path, pool), pool);
1605 cmp_rev = pb->cmp_rev;
1608 new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, pool);
1609 *child_baton = new_db;
1610 return SVN_NO_ERROR;
1614 static svn_error_t *
1615 close_directory(void *dir_baton,
1618 struct dir_baton *db = dir_baton;
1619 struct edit_baton *eb = db->edit_baton;
1620 apr_pool_t *subpool = svn_pool_create(pool);
1622 apr_array_header_t *sorted_entries;
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++)
1632 const char *path = APR_ARRAY_IDX(sorted_entries, i,
1633 svn_sort__item_t).key;
1635 svn_pool_clear(subpool);
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
1640 SVN_ERR(dump_node(eb, path,
1641 svn_node_unknown, svn_node_action_delete,
1642 FALSE, NULL, SVN_INVALID_REVNUM, subpool));
1645 svn_pool_destroy(subpool);
1646 return SVN_NO_ERROR;
1650 static svn_error_t *
1651 add_file(const char *path,
1653 const char *copyfrom_path,
1654 svn_revnum_t copyfrom_rev,
1658 struct dir_baton *pb = parent_baton;
1659 struct edit_baton *eb = pb->edit_baton;
1661 svn_boolean_t is_copy = FALSE;
1663 /* This might be a replacement -- is the path already deleted? */
1664 was_deleted = svn_hash_gets(pb->deleted_entries, path);
1666 /* Detect add-with-history. */
1667 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1669 /* Dump the node. */
1670 SVN_ERR(dump_node(eb, path,
1672 was_deleted ? svn_node_action_replace : svn_node_action_add,
1674 is_copy ? copyfrom_path : NULL,
1675 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1679 /* delete the path, it's now been dumped. */
1680 svn_hash_sets(pb->deleted_entries, path, NULL);
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)
1686 pb->check_name_collision = TRUE;
1689 *file_baton = NULL; /* muhahahaha */
1690 return SVN_NO_ERROR;
1694 static svn_error_t *
1695 open_file(const char *path,
1697 svn_revnum_t ancestor_revision,
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;
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))
1710 cmp_path = svn_relpath_join(pb->cmp_path,
1711 svn_relpath_basename(path, pool), pool);
1712 cmp_rev = pb->cmp_rev;
1715 SVN_ERR(dump_node(eb, path,
1716 svn_node_file, svn_node_action_change,
1717 FALSE, cmp_path, cmp_rev, pool));
1719 *file_baton = NULL; /* muhahahaha again */
1720 return SVN_NO_ERROR;
1724 static svn_error_t *
1725 change_dir_prop(void *parent_baton,
1727 const svn_string_t *value,
1730 struct dir_baton *db = parent_baton;
1731 struct edit_baton *eb = db->edit_baton;
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.
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)
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;
1748 return SVN_NO_ERROR;
1751 static svn_error_t *
1752 fetch_props_func(apr_hash_t **props,
1755 svn_revnum_t base_revision,
1756 apr_pool_t *result_pool,
1757 apr_pool_t *scratch_pool)
1759 struct edit_baton *eb = baton;
1761 svn_fs_root_t *fs_root;
1763 if (!SVN_IS_VALID_REVNUM(base_revision))
1764 base_revision = eb->current_rev - 1;
1766 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1768 err = svn_fs_node_proplist(props, fs_root, path, result_pool);
1769 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1771 svn_error_clear(err);
1772 *props = apr_hash_make(result_pool);
1773 return SVN_NO_ERROR;
1776 return svn_error_trace(err);
1778 return SVN_NO_ERROR;
1781 static svn_error_t *
1782 fetch_kind_func(svn_node_kind_t *kind,
1785 svn_revnum_t base_revision,
1786 apr_pool_t *scratch_pool)
1788 struct edit_baton *eb = baton;
1789 svn_fs_root_t *fs_root;
1791 if (!SVN_IS_VALID_REVNUM(base_revision))
1792 base_revision = eb->current_rev - 1;
1794 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1796 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
1798 return SVN_NO_ERROR;
1801 static svn_error_t *
1802 fetch_base_func(const char **filename,
1805 svn_revnum_t base_revision,
1806 apr_pool_t *result_pool,
1807 apr_pool_t *scratch_pool)
1809 struct edit_baton *eb = baton;
1810 svn_stream_t *contents;
1811 svn_stream_t *file_stream;
1812 const char *tmp_filename;
1814 svn_fs_root_t *fs_root;
1816 if (!SVN_IS_VALID_REVNUM(base_revision))
1817 base_revision = eb->current_rev - 1;
1819 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1821 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
1822 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1824 svn_error_clear(err);
1826 return SVN_NO_ERROR;
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));
1835 *filename = apr_pstrdup(result_pool, tmp_filename);
1837 return SVN_NO_ERROR;
1841 static svn_error_t *
1842 get_dump_editor(const svn_delta_editor_t **editor,
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,
1854 svn_revnum_t oldest_dumped_rev,
1855 svn_boolean_t use_deltas,
1856 svn_boolean_t verify,
1857 svn_boolean_t check_normalization,
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
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);
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));
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;
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.
1887 Else, make sure that all paths exists at their respective revisions.
1889 eb->path_tracker = verify ? tracker_create(to_rev, pool) : NULL;
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;
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;
1905 *editor = dump_editor;
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;
1912 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1913 NULL, NULL, shim_callbacks, pool, pool));
1915 return SVN_NO_ERROR;
1918 /*----------------------------------------------------------------------*/
1920 /** The main dumping routine, svn_repos_dump_fs. **/
1923 /* Helper for svn_repos_dump_fs.
1925 Write a revision record of REV in FS to writable STREAM, using POOL.
1927 static svn_error_t *
1928 write_revision_record(svn_stream_t *stream,
1934 apr_time_t timetemp;
1935 svn_string_t *datevalue;
1937 SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
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);
1945 SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
1946 datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
1948 svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
1951 SVN_ERR(svn_repos__dump_revision_record(stream, rev, NULL, props,
1952 TRUE /*props_section_always*/,
1954 return SVN_NO_ERROR;
1959 /* The main dumper. */
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,
1969 svn_cancel_func_t cancel_func,
1973 const svn_delta_editor_t *dump_editor;
1974 void *dump_edit_baton = NULL;
1976 svn_fs_t *fs = svn_repos_fs(repos);
1977 apr_pool_t *subpool = svn_pool_create(pool);
1978 svn_revnum_t youngest;
1981 svn_boolean_t found_old_reference = FALSE;
1982 svn_boolean_t found_old_mergeinfo = FALSE;
1983 svn_repos_notify_t *notify;
1985 /* Determine the current youngest revision of the filesystem. */
1986 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1988 /* Use default vals if necessary. */
1989 if (! SVN_IS_VALID_REVNUM(start_rev))
1991 if (! SVN_IS_VALID_REVNUM(end_rev))
1994 stream = svn_stream_empty(pool);
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)"),
2008 /* Write out the UUID. */
2009 SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
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;
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",
2022 SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
2025 /* Create a notify object that we can reuse in the loop. */
2027 notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
2030 /* Main loop: we're going to dump revision REV. */
2031 for (rev = start_rev; rev <= end_rev; rev++)
2033 svn_fs_root_t *to_root;
2034 svn_boolean_t use_deltas_for_rev;
2036 svn_pool_clear(subpool);
2038 /* Check for cancellation. */
2040 SVN_ERR(cancel_func(cancel_baton));
2042 /* Write the revision record. */
2043 SVN_ERR(write_revision_record(stream, fs, rev, subpool));
2045 /* When dumping revision 0, we just write out the revision record.
2046 The parser might want to use its properties. */
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,
2061 /* Drive the editor in one way or another. */
2062 SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, subpool));
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))
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, "", "",
2074 dump_editor, dump_edit_baton,
2077 FALSE, /* don't send text-deltas */
2079 FALSE, /* don't send entry props */
2080 FALSE, /* don't ignore ancestry */
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));
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));
2098 notify->revision = rev;
2099 notify_func(notify_baton, notify, subpool);
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
2110 notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool);
2111 notify_func(notify_baton, notify, subpool);
2113 if (found_old_reference)
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 "
2123 /* Ditto if we issued any warnings about old revisions referenced
2124 in dumped mergeinfo. */
2125 if (found_old_mergeinfo)
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 "
2136 svn_pool_destroy(subpool);
2138 return SVN_NO_ERROR;
2142 /*----------------------------------------------------------------------*/
2144 /* verify, based on dump */
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.
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.
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)
2165 struct dir_baton *db = baton;
2166 svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
2168 svn_boolean_t right_kind;
2170 path = svn_relpath_join(db->path, (const char *)key, pool);
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.
2178 switch (dirent->kind) {
2180 SVN_ERR(svn_fs_is_dir(&right_kind, db->edit_baton->fs_root, path, pool));
2182 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2183 _("Node '%s' is not a directory."),
2188 SVN_ERR(svn_fs_is_file(&right_kind, db->edit_baton->fs_root, path, pool));
2190 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2191 _("Node '%s' is not a file."),
2195 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2196 _("Unexpected node kind %d for '%s'"),
2197 dirent->kind, path);
2200 return SVN_NO_ERROR;
2203 /* Baton used by the check_name_collision hash iterator. */
2204 struct check_name_collision_baton
2206 struct dir_baton *dir_baton;
2207 apr_hash_t *normalized;
2208 svn_membuf_t buffer;
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)
2217 struct check_name_collision_baton *const cb = baton;
2221 SVN_ERR(svn_utf__normalize(&name, key, klen, &cb->buffer));
2223 found = svn_hash_gets(cb->normalized, name);
2225 svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2227 else if (found == normalized_collision)
2228 /* Skip already reported collision */;
2231 struct dir_baton *const db = cb->dir_baton;
2232 struct edit_baton *const eb = db->edit_baton;
2233 const char* normpath;
2235 svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2236 normalized_collision);
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);
2245 return SVN_NO_ERROR;
2249 static svn_error_t *
2250 verify_close_directory(void *dir_baton, apr_pool_t *pool)
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,
2256 SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
2259 if (db->check_name_collision)
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));
2269 return close_directory(dir_baton, pool);
2272 /* Verify revision REV in file system FS. */
2273 static svn_error_t *
2274 verify_one_revision(svn_fs_t *fs,
2276 svn_repos_notify_func_t notify_func,
2278 svn_revnum_t start_rev,
2279 svn_boolean_t check_normalization,
2280 svn_cancel_func_t cancel_func,
2282 apr_pool_t *scratch_pool)
2284 const svn_delta_editor_t *dump_editor;
2285 void *dump_edit_baton;
2286 svn_fs_root_t *to_root;
2288 const svn_delta_editor_t *cancel_editor;
2289 void *cancel_edit_baton;
2291 /* Get cancellable dump editor, but with our close_directory handler.*/
2292 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
2294 svn_stream_empty(scratch_pool),
2296 verify_close_directory,
2297 notify_func, notify_baton,
2299 FALSE, TRUE, /* use_deltas, verify */
2300 check_normalization,
2302 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2303 dump_editor, dump_edit_baton,
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));
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));
2317 SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, scratch_pool));
2319 return SVN_NO_ERROR;
2322 /* Baton type used for forwarding notifications from FS API to REPOS API. */
2323 struct verify_fs_notify_func_baton_t
2325 /* notification function to call (must not be NULL) */
2326 svn_repos_notify_func_t notify_func;
2328 /* baton to use for it */
2331 /* type of notification to send (we will simply plug in the revision) */
2332 svn_repos_notify_t *notify;
2335 /* Forward the notification to BATON. */
2337 verify_fs_notify_func(svn_revnum_t revision,
2341 struct verify_fs_notify_func_baton_t *notify_baton = baton;
2343 notify_baton->notify->revision = revision;
2344 notify_baton->notify_func(notify_baton->notify_baton,
2345 notify_baton->notify, pool);
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,
2355 if (verify_callback)
2357 svn_error_t *cb_err;
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);
2365 return SVN_NO_ERROR;
2369 /* No callback -- no second guessing. Just return the error. */
2370 return svn_error_trace(verify_err);
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,
2382 svn_repos_verify_callback_t verify_callback,
2384 svn_cancel_func_t cancel_func,
2388 svn_fs_t *fs = svn_repos_fs(repos);
2389 svn_revnum_t youngest;
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;
2397 /* Determine the current youngest revision of the filesystem. */
2398 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2400 /* Use default vals if necessary. */
2401 if (! SVN_IS_VALID_REVNUM(start_rev))
2403 if (! SVN_IS_VALID_REVNUM(end_rev))
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)"),
2418 /* Create a notify object that we can reuse within the loop and a
2419 forwarding structure for notifications from inside svn_fs_verify(). */
2422 notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, pool);
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);
2432 /* Verify global metadata and backend-specific data first. */
2433 err = svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
2435 verify_notify, verify_notify_baton,
2436 cancel_func, cancel_baton, pool);
2438 if (err && err->apr_err == SVN_ERR_CANCELLED)
2440 return svn_error_trace(err);
2444 SVN_ERR(report_error(SVN_INVALID_REVNUM, err, verify_callback,
2445 verify_baton, iterpool));
2449 for (rev = start_rev; rev <= end_rev; rev++)
2451 svn_pool_clear(iterpool);
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,
2459 if (err && err->apr_err == SVN_ERR_CANCELLED)
2461 return svn_error_trace(err);
2465 SVN_ERR(report_error(rev, err, verify_callback, verify_baton,
2468 else if (notify_func)
2470 /* Tell the caller that we're done with this revision. */
2471 notify->revision = rev;
2472 notify_func(notify_baton, notify, iterpool);
2479 notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool);
2480 notify_func(notify_baton, notify, iterpool);
2483 svn_pool_destroy(iterpool);
2485 return SVN_NO_ERROR;