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));
549 /* Write out a regular Content-length header for the benefit of
550 non-Subversion RFC-822 parsers. */
551 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
552 apr_psprintf(scratch_pool,
553 "%" APR_SIZE_T_FMT, propstring->len));
554 SVN_ERR(write_revision_headers(dump_stream, headers, scratch_pool));
557 SVN_ERR(svn_stream_puts(dump_stream, "\n"));
562 SVN_ERR(svn_stream_write(dump_stream, propstring->data, &propstring->len));
565 /* put an end to revision */
566 SVN_ERR(svn_stream_puts(dump_stream, "\n"));
572 svn_repos__dump_node_record(svn_stream_t *dump_stream,
573 svn_repos__dumpfile_headers_t *headers,
574 svn_stringbuf_t *props_str,
575 svn_boolean_t has_text,
576 svn_filesize_t text_content_length,
577 svn_boolean_t content_length_always,
578 apr_pool_t *scratch_pool)
580 svn_filesize_t content_length = 0;
582 /* add content-length headers */
585 svn_repos__dumpfile_header_pushf(
586 headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
587 "%" APR_SIZE_T_FMT, props_str->len);
588 content_length += props_str->len;
592 svn_repos__dumpfile_header_pushf(
593 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH,
594 "%" SVN_FILESIZE_T_FMT, text_content_length);
595 content_length += text_content_length;
597 if (content_length_always || props_str || has_text)
599 svn_repos__dumpfile_header_pushf(
600 headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
601 "%" SVN_FILESIZE_T_FMT, content_length);
604 /* write the headers */
605 SVN_ERR(svn_repos__dump_headers(dump_stream, headers, scratch_pool));
607 /* write the props */
610 SVN_ERR(svn_stream_write(dump_stream, props_str->data, &props_str->len));
615 /*----------------------------------------------------------------------*/
617 /** An editor which dumps node-data in 'dumpfile format' to a file. **/
619 /* Look, mom! No file batons! */
623 /* The relpath which implicitly prepends all full paths coming into
624 this editor. This will almost always be "". */
627 /* The stream to dump to. */
628 svn_stream_t *stream;
630 /* Send feedback here, if non-NULL */
631 svn_repos_notify_func_t notify_func;
634 /* The fs revision root, so we can read the contents of paths. */
635 svn_fs_root_t *fs_root;
636 svn_revnum_t current_rev;
638 /* The fs, so we can grab historic information if needed. */
641 /* True if dumped nodes should output deltas instead of full text. */
642 svn_boolean_t use_deltas;
644 /* True if this "dump" is in fact a verify. */
645 svn_boolean_t verify;
647 /* True if checking UCS normalization during a verify. */
648 svn_boolean_t check_normalization;
650 /* The first revision dumped in this dumpstream. */
651 svn_revnum_t oldest_dumped_rev;
653 /* If not NULL, set to true if any references to revisions older than
654 OLDEST_DUMPED_REV were found in the dumpstream. */
655 svn_boolean_t *found_old_reference;
657 /* If not NULL, set to true if any mergeinfo was dumped which contains
658 revisions older than OLDEST_DUMPED_REV. */
659 svn_boolean_t *found_old_mergeinfo;
661 /* Structure allows us to verify the paths currently being dumped.
662 If NULL, validity checks are being skipped. */
663 path_tracker_t *path_tracker;
668 struct edit_baton *edit_baton;
670 /* has this directory been written to the output stream? */
671 svn_boolean_t written_out;
673 /* the repository relpath associated with this directory */
676 /* The comparison repository relpath and revision of this directory.
677 If both of these are valid, use them as a source against which to
678 compare the directory instead of the default comparison source of
679 PATH in the previous revision. */
680 const char *cmp_path;
681 svn_revnum_t cmp_rev;
683 /* hash of paths that need to be deleted, though some -might- be
684 replaced. maps const char * paths to this dir_baton. (they're
685 full paths, because that's what the editor driver gives us. but
686 really, they're all within this directory.) */
687 apr_hash_t *deleted_entries;
689 /* A flag indicating that new entries have been added to this
690 directory in this revision. Used to optimize detection of UCS
691 representation collisions; we will only check for that in
692 revisions where new names appear in the directory. */
693 svn_boolean_t check_name_collision;
695 /* pool to be used for deleting the hash items */
700 /* Make a directory baton to represent the directory was path
701 (relative to EDIT_BATON's path) is PATH.
703 CMP_PATH/CMP_REV are the path/revision against which this directory
704 should be compared for changes. If either is omitted (NULL for the
705 path, SVN_INVALID_REVNUM for the rev), just compare this directory
706 PATH against itself in the previous revision.
708 PB is the directory baton of this directory's parent,
709 or NULL if this is the top-level directory of the edit.
711 Perform all allocations in POOL. */
712 static struct dir_baton *
713 make_dir_baton(const char *path,
714 const char *cmp_path,
715 svn_revnum_t cmp_rev,
717 struct dir_baton *pb,
720 struct edit_baton *eb = edit_baton;
721 struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
722 const char *full_path;
724 /* A path relative to nothing? I don't think so. */
725 SVN_ERR_ASSERT_NO_RETURN(!path || pb);
727 /* Construct the full path of this node. */
729 full_path = svn_relpath_join(eb->path, path, pool);
731 full_path = apr_pstrdup(pool, eb->path);
733 /* Remove leading slashes from copyfrom paths. */
735 cmp_path = svn_relpath_canonicalize(cmp_path, pool);
737 new_db->edit_baton = eb;
738 new_db->path = full_path;
739 new_db->cmp_path = cmp_path;
740 new_db->cmp_rev = cmp_rev;
741 new_db->written_out = FALSE;
742 new_db->deleted_entries = apr_hash_make(pool);
743 new_db->check_name_collision = FALSE;
750 fetch_kind_func(svn_node_kind_t *kind,
753 svn_revnum_t base_revision,
754 apr_pool_t *scratch_pool);
756 /* Return an error when PATH in REVISION does not exist or is of a
757 different kind than EXPECTED_KIND. If the latter is svn_node_unknown,
758 skip that check. Use EB for context information. If REVISION is the
759 current revision, use EB's path tracker to follow renames, deletions,
762 Use SCRATCH_POOL for temporary allocations.
763 No-op if EB's path tracker has not been initialized.
766 node_must_exist(struct edit_baton *eb,
768 svn_revnum_t revision,
769 svn_node_kind_t expected_kind,
770 apr_pool_t *scratch_pool)
772 svn_node_kind_t kind = svn_node_none;
774 /* in case the caller is trying something stupid ... */
775 if (eb->path_tracker == NULL)
778 /* paths pertaining to the revision currently being processed must
779 be translated / checked using our path tracker. */
780 if (revision == eb->path_tracker->revision)
781 tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
783 /* determine the node type (default: no such node) */
785 SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
788 if (kind == svn_node_none)
789 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
790 _("Path '%s' not found in r%ld."),
793 if (expected_kind != kind && expected_kind != svn_node_unknown)
794 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
795 _("Unexpected node kind %d for '%s' at r%ld. "
796 "Expected kind was %d."),
797 kind, path, revision, expected_kind);
802 /* Return an error when PATH exists in REVISION. Use EB for context
803 information. If REVISION is the current revision, use EB's path
804 tracker to follow renames, deletions, etc.
806 Use SCRATCH_POOL for temporary allocations.
807 No-op if EB's path tracker has not been initialized.
810 node_must_not_exist(struct edit_baton *eb,
812 svn_revnum_t revision,
813 apr_pool_t *scratch_pool)
815 svn_node_kind_t kind = svn_node_none;
817 /* in case the caller is trying something stupid ... */
818 if (eb->path_tracker == NULL)
821 /* paths pertaining to the revision currently being processed must
822 be translated / checked using our path tracker. */
823 if (revision == eb->path_tracker->revision)
824 tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
826 /* determine the node type (default: no such node) */
828 SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
831 if (kind != svn_node_none)
832 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
833 _("Path '%s' exists in r%ld."),
839 /* If the mergeinfo in MERGEINFO_STR refers to any revisions older than
840 * OLDEST_DUMPED_REV, issue a warning and set *FOUND_OLD_MERGEINFO to TRUE,
841 * otherwise leave *FOUND_OLD_MERGEINFO unchanged.
844 verify_mergeinfo_revisions(svn_boolean_t *found_old_mergeinfo,
845 const char *mergeinfo_str,
846 svn_revnum_t oldest_dumped_rev,
847 svn_repos_notify_func_t notify_func,
851 svn_mergeinfo_t mergeinfo, old_mergeinfo;
853 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str, pool));
854 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
855 &old_mergeinfo, mergeinfo,
856 oldest_dumped_rev - 1, 0,
859 if (apr_hash_count(old_mergeinfo))
861 notify_warning(pool, notify_func, notify_baton,
862 svn_repos_notify_warning_found_old_mergeinfo,
863 _("Mergeinfo referencing revision(s) prior "
864 "to the oldest dumped revision (r%ld). "
865 "Loading this dump may result in invalid "
869 if (found_old_mergeinfo)
870 *found_old_mergeinfo = TRUE;
876 /* Unique string pointers used by verify_mergeinfo_normalization()
877 and check_name_collision() */
878 static const char normalized_unique[] = "normalized_unique";
879 static const char normalized_collision[] = "normalized_collision";
882 /* Baton for extract_mergeinfo_paths */
883 struct extract_mergeinfo_paths_baton
886 svn_boolean_t normalize;
890 /* Hash iterator that uniquifies all keys into a single hash table,
891 optionally normalizing them first. */
893 extract_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
894 void *val, apr_pool_t *iterpool)
896 struct extract_mergeinfo_paths_baton *const xb = baton;
900 SVN_ERR(svn_utf__normalize(&normkey, key, klen, &xb->buffer));
901 svn_hash_sets(xb->result,
902 apr_pstrdup(xb->buffer.pool, normkey),
906 apr_hash_set(xb->result,
907 apr_pmemdup(xb->buffer.pool, key, klen + 1), klen,
912 /* Baton for filter_mergeinfo_paths */
913 struct filter_mergeinfo_paths_baton
918 /* Compare two sets of denormalized paths from mergeinfo entries,
919 removing duplicates. */
921 filter_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
922 void *val, apr_pool_t *iterpool)
924 struct filter_mergeinfo_paths_baton *const fb = baton;
926 if (apr_hash_get(fb->paths, key, klen))
927 apr_hash_set(fb->paths, key, klen, NULL);
932 /* Baton used by the check_mergeinfo_normalization hash iterator. */
933 struct verify_mergeinfo_normalization_baton
936 apr_hash_t *normalized_paths;
938 svn_repos_notify_func_t notify_func;
942 /* Hash iterator that verifies normalization and collision of paths in
943 an svn:mergeinfo property. */
945 verify_mergeinfo_normalization(void *baton, const void *key, apr_ssize_t klen,
946 void *val, apr_pool_t *iterpool)
948 struct verify_mergeinfo_normalization_baton *const vb = baton;
950 const char *const path = key;
951 const char *normpath;
954 SVN_ERR(svn_utf__normalize(&normpath, path, klen, &vb->buffer));
955 found = svn_hash_gets(vb->normalized_paths, normpath);
957 svn_hash_sets(vb->normalized_paths,
958 apr_pstrdup(vb->buffer.pool, normpath),
960 else if (found == normalized_collision)
961 /* Skip already reported collision */;
964 /* Report path collision in mergeinfo */
965 svn_hash_sets(vb->normalized_paths,
966 apr_pstrdup(vb->buffer.pool, normpath),
967 normalized_collision);
969 notify_warning(iterpool, vb->notify_func, vb->notify_baton,
970 svn_repos_notify_warning_mergeinfo_collision,
971 _("Duplicate representation of path '%s'"
972 " in %s property of '%s'"),
973 normpath, SVN_PROP_MERGEINFO, vb->path);
978 /* Check UCS normalization of mergeinfo for PATH. NEW_MERGEINFO is the
979 svn:mergeinfo property value being set; OLD_MERGEINFO is the
980 previous property value, which may be NULL. Only the paths that
981 were added in are checked, including collision checks. This
982 minimizes the number of notifications we generate for a given
983 mergeinfo property. */
985 check_mergeinfo_normalization(const char *path,
986 const char *new_mergeinfo,
987 const char *old_mergeinfo,
988 svn_repos_notify_func_t notify_func,
992 svn_mergeinfo_t mergeinfo;
993 apr_hash_t *normalized_paths;
994 apr_hash_t *added_paths;
995 struct extract_mergeinfo_paths_baton extract_baton;
996 struct verify_mergeinfo_normalization_baton verify_baton;
998 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, new_mergeinfo, pool));
1000 extract_baton.result = apr_hash_make(pool);
1001 extract_baton.normalize = FALSE;
1002 svn_membuf__create(&extract_baton.buffer, 0, pool);
1003 SVN_ERR(svn_iter_apr_hash(NULL, mergeinfo,
1004 extract_mergeinfo_paths,
1005 &extract_baton, pool));
1006 added_paths = extract_baton.result;
1010 struct filter_mergeinfo_paths_baton filter_baton;
1011 svn_mergeinfo_t oldinfo;
1013 extract_baton.result = apr_hash_make(pool);
1014 extract_baton.normalize = TRUE;
1015 SVN_ERR(svn_mergeinfo_parse(&oldinfo, old_mergeinfo, pool));
1016 SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
1017 extract_mergeinfo_paths,
1018 &extract_baton, pool));
1019 normalized_paths = extract_baton.result;
1021 filter_baton.paths = added_paths;
1022 SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
1023 filter_mergeinfo_paths,
1024 &filter_baton, pool));
1027 normalized_paths = apr_hash_make(pool);
1029 verify_baton.path = path;
1030 verify_baton.normalized_paths = normalized_paths;
1031 verify_baton.buffer = extract_baton.buffer;
1032 verify_baton.notify_func = notify_func;
1033 verify_baton.notify_baton = notify_baton;
1034 SVN_ERR(svn_iter_apr_hash(NULL, added_paths,
1035 verify_mergeinfo_normalization,
1036 &verify_baton, pool));
1038 return SVN_NO_ERROR;
1042 /* A special case of dump_node(), for a delete record.
1044 * The only thing special about this version is it only writes one blank
1045 * line, not two, after the headers. Why? Historical precedent for the
1046 * case where a delete record is used as part of a (delete + add-with-history)
1047 * in implementing a replacement.
1049 * Also it doesn't do a path-tracker check.
1051 static svn_error_t *
1052 dump_node_delete(svn_stream_t *stream,
1053 const char *node_relpath,
1056 svn_repos__dumpfile_headers_t *headers
1057 = svn_repos__dumpfile_headers_create(pool);
1059 /* Node-path: ... */
1060 svn_repos__dumpfile_header_push(
1061 headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
1063 /* Node-action: delete */
1064 svn_repos__dumpfile_header_push(
1065 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1067 SVN_ERR(svn_repos__dump_headers(stream, headers, pool));
1068 return SVN_NO_ERROR;
1071 /* This helper is the main "meat" of the editor -- it does all the
1072 work of writing a node record.
1074 Write out a node record for PATH of type KIND under EB->FS_ROOT.
1075 ACTION describes what is happening to the node (see enum svn_node_action).
1076 Write record to writable EB->STREAM.
1078 If the node was itself copied, IS_COPY is TRUE and the
1079 path/revision of the copy source are in CMP_PATH/CMP_REV. If
1080 IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
1081 of a copied subtree.
1083 static svn_error_t *
1084 dump_node(struct edit_baton *eb,
1086 svn_node_kind_t kind,
1087 enum svn_node_action action,
1088 svn_boolean_t is_copy,
1089 const char *cmp_path,
1090 svn_revnum_t cmp_rev,
1093 svn_stringbuf_t *propstring;
1095 svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
1096 const char *compare_path = path;
1097 svn_revnum_t compare_rev = eb->current_rev - 1;
1098 svn_fs_root_t *compare_root = NULL;
1099 apr_file_t *delta_file = NULL;
1100 svn_repos__dumpfile_headers_t *headers
1101 = svn_repos__dumpfile_headers_create(pool);
1102 svn_filesize_t textlen;
1104 /* Maybe validate the path. */
1105 if (eb->verify || eb->notify_func)
1107 svn_error_t *err = svn_fs__path_valid(path, pool);
1111 if (eb->notify_func)
1113 char errbuf[512]; /* ### svn_strerror() magic number */
1115 notify_warning(pool, eb->notify_func, eb->notify_baton,
1116 svn_repos_notify_warning_invalid_fspath,
1117 _("E%06d: While validating fspath '%s': %s"),
1119 svn_err_best_message(err, errbuf, sizeof(errbuf)));
1122 /* Return the error in addition to notifying about it. */
1124 return svn_error_trace(err);
1126 svn_error_clear(err);
1130 /* Write out metadata headers for this file node. */
1131 svn_repos__dumpfile_header_push(
1132 headers, SVN_REPOS_DUMPFILE_NODE_PATH, path);
1133 if (kind == svn_node_file)
1134 svn_repos__dumpfile_header_push(
1135 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file");
1136 else if (kind == svn_node_dir)
1137 svn_repos__dumpfile_header_push(
1138 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir");
1140 /* Remove leading slashes from copyfrom paths. */
1142 cmp_path = svn_relpath_canonicalize(cmp_path, pool);
1144 /* Validate the comparison path/rev. */
1145 if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
1147 compare_path = cmp_path;
1148 compare_rev = cmp_rev;
1153 case svn_node_action_change:
1154 if (eb->path_tracker)
1155 SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
1156 apr_psprintf(pool, _("Change invalid path '%s' in r%ld"),
1157 path, eb->current_rev));
1159 svn_repos__dumpfile_header_push(
1160 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change");
1162 /* either the text or props changed, or possibly both. */
1163 SVN_ERR(svn_fs_revision_root(&compare_root,
1164 svn_fs_root_fs(eb->fs_root),
1165 compare_rev, pool));
1167 SVN_ERR(svn_fs_props_different(&must_dump_props,
1168 compare_root, compare_path,
1169 eb->fs_root, path, pool));
1170 if (kind == svn_node_file)
1171 SVN_ERR(svn_fs_contents_different(&must_dump_text,
1172 compare_root, compare_path,
1173 eb->fs_root, path, pool));
1176 case svn_node_action_delete:
1177 if (eb->path_tracker)
1179 SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
1180 apr_psprintf(pool, _("Deleting invalid path '%s' in r%ld"),
1181 path, eb->current_rev));
1182 tracker_path_delete(eb->path_tracker, path);
1185 svn_repos__dumpfile_header_push(
1186 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1188 /* we can leave this routine quietly now, don't need to dump
1190 must_dump_text = FALSE;
1191 must_dump_props = FALSE;
1194 case svn_node_action_replace:
1195 if (eb->path_tracker)
1196 SVN_ERR_W(node_must_exist(eb, path, eb->current_rev,
1197 svn_node_unknown, pool),
1199 _("Replacing non-existent path '%s' in r%ld"),
1200 path, eb->current_rev));
1204 if (eb->path_tracker)
1205 tracker_path_replace(eb->path_tracker, path);
1207 /* a simple delete+add, implied by a single 'replace' action. */
1208 svn_repos__dumpfile_header_push(
1209 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace");
1211 /* definitely need to dump all content for a replace. */
1212 if (kind == svn_node_file)
1213 must_dump_text = TRUE;
1214 must_dump_props = TRUE;
1219 /* more complex: delete original, then add-with-history. */
1220 /* ### Why not write a 'replace' record? Don't know. */
1222 if (eb->path_tracker)
1224 tracker_path_delete(eb->path_tracker, path);
1227 /* ### Unusually, we end this 'delete' node record with only a single
1228 blank line after the header block -- no extra blank line. */
1229 SVN_ERR(dump_node_delete(eb->stream, path, pool));
1231 /* The remaining action is a non-replacing add-with-history */
1232 /* action = svn_node_action_add; */
1234 /* FALL THROUGH to 'add' */
1236 case svn_node_action_add:
1237 if (eb->path_tracker)
1238 SVN_ERR_W(node_must_not_exist(eb, path, eb->current_rev, pool),
1240 _("Adding already existing path '%s' in r%ld"),
1241 path, eb->current_rev));
1243 svn_repos__dumpfile_header_push(
1244 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
1248 if (eb->path_tracker)
1249 tracker_path_add(eb->path_tracker, path);
1251 /* Dump all contents for a simple 'add'. */
1252 if (kind == svn_node_file)
1253 must_dump_text = TRUE;
1254 must_dump_props = TRUE;
1258 if (eb->path_tracker)
1260 SVN_ERR_W(node_must_exist(eb, compare_path, compare_rev,
1263 _("Copying from invalid path to "
1265 path, eb->current_rev));
1266 tracker_path_copy(eb->path_tracker, path, compare_path,
1270 if (!eb->verify && cmp_rev < eb->oldest_dumped_rev
1273 notify_warning(pool, eb->notify_func, eb->notify_baton,
1274 svn_repos_notify_warning_found_old_reference,
1275 _("Referencing data in revision %ld,"
1276 " which is older than the oldest"
1277 " dumped revision (r%ld). Loading this dump"
1278 " into an empty repository"
1280 cmp_rev, eb->oldest_dumped_rev);
1281 if (eb->found_old_reference)
1282 *eb->found_old_reference = TRUE;
1285 svn_repos__dumpfile_header_pushf(
1286 headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", cmp_rev);
1287 svn_repos__dumpfile_header_push(
1288 headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, cmp_path);
1290 SVN_ERR(svn_fs_revision_root(&compare_root,
1291 svn_fs_root_fs(eb->fs_root),
1292 compare_rev, pool));
1294 /* Need to decide if the copied node had any extra textual or
1295 property mods as well. */
1296 SVN_ERR(svn_fs_props_different(&must_dump_props,
1297 compare_root, compare_path,
1298 eb->fs_root, path, pool));
1299 if (kind == svn_node_file)
1301 svn_checksum_t *checksum;
1302 const char *hex_digest;
1303 SVN_ERR(svn_fs_contents_different(&must_dump_text,
1304 compare_root, compare_path,
1305 eb->fs_root, path, pool));
1307 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1308 compare_root, compare_path,
1310 hex_digest = svn_checksum_to_cstring(checksum, pool);
1312 svn_repos__dumpfile_header_push(
1313 headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5, hex_digest);
1315 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1316 compare_root, compare_path,
1318 hex_digest = svn_checksum_to_cstring(checksum, pool);
1320 svn_repos__dumpfile_header_push(
1321 headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1, hex_digest);
1327 if ((! must_dump_text) && (! must_dump_props))
1329 /* If we're not supposed to dump text or props, so be it, we can
1330 just go home. However, if either one needs to be dumped,
1331 then our dumpstream format demands that at a *minimum*, we
1332 see a lone "PROPS-END" as a divider between text and props
1333 content within the content-block. */
1334 SVN_ERR(svn_repos__dump_headers(eb->stream, headers, pool));
1336 return svn_stream_write(eb->stream, "\n", &len); /* ### needed? */
1339 /*** Start prepping content to dump... ***/
1341 /* If we are supposed to dump properties, write out a property
1342 length header and generate a stringbuf that contains those
1343 property values here. */
1344 if (must_dump_props)
1346 apr_hash_t *prophash, *oldhash = NULL;
1347 svn_stream_t *propstream;
1349 SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
1351 /* If this is a partial dump, then issue a warning if we dump mergeinfo
1352 properties that refer to revisions older than the first revision
1354 if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1)
1356 svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
1357 SVN_PROP_MERGEINFO);
1360 /* An error in verifying the mergeinfo must not prevent dumping
1361 the data. Ignore any such error. */
1362 svn_error_clear(verify_mergeinfo_revisions(
1363 eb->found_old_mergeinfo,
1364 mergeinfo_str->data, eb->oldest_dumped_rev,
1365 eb->notify_func, eb->notify_baton,
1370 /* If we're checking UCS normalization, also parse any changed
1371 mergeinfo and warn about denormalized paths and name
1372 collisions there. */
1373 if (eb->verify && eb->check_normalization && eb->notify_func)
1375 /* N.B.: This hash lookup happens only once; the conditions
1376 for verifying historic mergeinfo references and checking
1377 UCS normalization are mutually exclusive. */
1378 svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
1379 SVN_PROP_MERGEINFO);
1382 svn_string_t *oldinfo_str = NULL;
1385 SVN_ERR(svn_fs_node_proplist(&oldhash,
1386 compare_root, compare_path,
1388 oldinfo_str = svn_hash_gets(oldhash, SVN_PROP_MERGEINFO);
1390 SVN_ERR(check_mergeinfo_normalization(
1391 path, mergeinfo_str->data,
1392 (oldinfo_str ? oldinfo_str->data : NULL),
1393 eb->notify_func, eb->notify_baton, pool));
1397 if (eb->use_deltas && compare_root)
1399 /* Fetch the old property hash to diff against and output a header
1400 saying that our property contents are a delta. */
1401 if (!oldhash) /* May have been set for normalization check */
1402 SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
1404 svn_repos__dumpfile_header_push(
1405 headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true");
1408 oldhash = apr_hash_make(pool);
1409 propstring = svn_stringbuf_create_ensure(0, pool);
1410 propstream = svn_stream_from_stringbuf(propstring, pool);
1411 SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream,
1412 "PROPS-END", pool));
1413 SVN_ERR(svn_stream_close(propstream));
1416 /* If we are supposed to dump text, write out a text length header
1417 here, and an MD5 checksum (if available). */
1418 if (must_dump_text && (kind == svn_node_file))
1420 svn_checksum_t *checksum;
1421 const char *hex_digest;
1425 /* Compute the text delta now and write it into a temporary
1426 file, so that we can find its length. Output a header
1427 saying our text contents are a delta. */
1428 SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
1429 compare_path, eb->fs_root, path, pool));
1430 svn_repos__dumpfile_header_push(
1431 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true");
1435 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1436 compare_root, compare_path,
1438 hex_digest = svn_checksum_to_cstring(checksum, pool);
1440 svn_repos__dumpfile_header_push(
1441 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, hex_digest);
1443 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1444 compare_root, compare_path,
1446 hex_digest = svn_checksum_to_cstring(checksum, pool);
1448 svn_repos__dumpfile_header_push(
1449 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1, hex_digest);
1454 /* Just fetch the length of the file. */
1455 SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
1458 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1459 eb->fs_root, path, FALSE, pool));
1460 hex_digest = svn_checksum_to_cstring(checksum, pool);
1462 svn_repos__dumpfile_header_push(
1463 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, hex_digest);
1465 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1466 eb->fs_root, path, FALSE, pool));
1467 hex_digest = svn_checksum_to_cstring(checksum, pool);
1469 svn_repos__dumpfile_header_push(
1470 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1, hex_digest);
1473 /* 'Content-length:' is the last header before we dump the content,
1474 and is the sum of the text and prop contents lengths. We write
1475 this only for the benefit of non-Subversion RFC-822 parsers. */
1476 SVN_ERR(svn_repos__dump_node_record(eb->stream, headers,
1477 must_dump_props ? propstring : NULL,
1479 must_dump_text ? textlen : 0,
1480 TRUE /*content_length_always*/,
1483 /* Dump text content */
1484 if (must_dump_text && (kind == svn_node_file))
1486 svn_stream_t *contents;
1490 /* Make sure to close the underlying file when the stream is
1492 contents = svn_stream_from_aprfile2(delta_file, FALSE, pool);
1495 SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
1497 SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool),
1502 return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
1506 static svn_error_t *
1507 open_root(void *edit_baton,
1508 svn_revnum_t base_revision,
1512 *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
1513 edit_baton, NULL, pool);
1514 return SVN_NO_ERROR;
1518 static svn_error_t *
1519 delete_entry(const char *path,
1520 svn_revnum_t revision,
1524 struct dir_baton *pb = parent_baton;
1525 const char *mypath = apr_pstrdup(pb->pool, path);
1527 /* remember this path needs to be deleted. */
1528 svn_hash_sets(pb->deleted_entries, mypath, pb);
1530 return SVN_NO_ERROR;
1534 static svn_error_t *
1535 add_directory(const char *path,
1537 const char *copyfrom_path,
1538 svn_revnum_t copyfrom_rev,
1542 struct dir_baton *pb = parent_baton;
1543 struct edit_baton *eb = pb->edit_baton;
1545 svn_boolean_t is_copy = FALSE;
1546 struct dir_baton *new_db
1547 = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, pool);
1549 /* This might be a replacement -- is the path already deleted? */
1550 was_deleted = svn_hash_gets(pb->deleted_entries, path);
1552 /* Detect an add-with-history. */
1553 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1555 /* Dump the node. */
1556 SVN_ERR(dump_node(eb, path,
1558 was_deleted ? svn_node_action_replace : svn_node_action_add,
1560 is_copy ? copyfrom_path : NULL,
1561 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1565 /* Delete the path, it's now been dumped. */
1566 svn_hash_sets(pb->deleted_entries, path, NULL);
1568 /* Check for normalized name clashes, but only if this is actually a
1569 new name in the parent, not a replacement. */
1570 if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
1572 pb->check_name_collision = TRUE;
1575 new_db->written_out = TRUE;
1577 *child_baton = new_db;
1578 return SVN_NO_ERROR;
1582 static svn_error_t *
1583 open_directory(const char *path,
1585 svn_revnum_t base_revision,
1589 struct dir_baton *pb = parent_baton;
1590 struct edit_baton *eb = pb->edit_baton;
1591 struct dir_baton *new_db;
1592 const char *cmp_path = NULL;
1593 svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
1595 /* If the parent directory has explicit comparison path and rev,
1596 record the same for this one. */
1597 if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
1599 cmp_path = svn_relpath_join(pb->cmp_path,
1600 svn_relpath_basename(path, pool), pool);
1601 cmp_rev = pb->cmp_rev;
1604 new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, pool);
1605 *child_baton = new_db;
1606 return SVN_NO_ERROR;
1610 static svn_error_t *
1611 close_directory(void *dir_baton,
1614 struct dir_baton *db = dir_baton;
1615 struct edit_baton *eb = db->edit_baton;
1616 apr_pool_t *subpool = svn_pool_create(pool);
1618 apr_array_header_t *sorted_entries;
1620 /* Sort entries lexically instead of as paths. Even though the entries
1621 * are full paths they're all in the same directory (see comment in struct
1622 * dir_baton definition). So we really want to sort by basename, in which
1623 * case the lexical sort function is more efficient. */
1624 sorted_entries = svn_sort__hash(db->deleted_entries,
1625 svn_sort_compare_items_lexically, pool);
1626 for (i = 0; i < sorted_entries->nelts; i++)
1628 const char *path = APR_ARRAY_IDX(sorted_entries, i,
1629 svn_sort__item_t).key;
1631 svn_pool_clear(subpool);
1633 /* By sending 'svn_node_unknown', the Node-kind: header simply won't
1634 be written out. No big deal at all, really. The loader
1636 SVN_ERR(dump_node(eb, path,
1637 svn_node_unknown, svn_node_action_delete,
1638 FALSE, NULL, SVN_INVALID_REVNUM, subpool));
1641 svn_pool_destroy(subpool);
1642 return SVN_NO_ERROR;
1646 static svn_error_t *
1647 add_file(const char *path,
1649 const char *copyfrom_path,
1650 svn_revnum_t copyfrom_rev,
1654 struct dir_baton *pb = parent_baton;
1655 struct edit_baton *eb = pb->edit_baton;
1657 svn_boolean_t is_copy = FALSE;
1659 /* This might be a replacement -- is the path already deleted? */
1660 was_deleted = svn_hash_gets(pb->deleted_entries, path);
1662 /* Detect add-with-history. */
1663 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1665 /* Dump the node. */
1666 SVN_ERR(dump_node(eb, path,
1668 was_deleted ? svn_node_action_replace : svn_node_action_add,
1670 is_copy ? copyfrom_path : NULL,
1671 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1675 /* delete the path, it's now been dumped. */
1676 svn_hash_sets(pb->deleted_entries, path, NULL);
1678 /* Check for normalized name clashes, but only if this is actually a
1679 new name in the parent, not a replacement. */
1680 if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
1682 pb->check_name_collision = TRUE;
1685 *file_baton = NULL; /* muhahahaha */
1686 return SVN_NO_ERROR;
1690 static svn_error_t *
1691 open_file(const char *path,
1693 svn_revnum_t ancestor_revision,
1697 struct dir_baton *pb = parent_baton;
1698 struct edit_baton *eb = pb->edit_baton;
1699 const char *cmp_path = NULL;
1700 svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
1702 /* If the parent directory has explicit comparison path and rev,
1703 record the same for this one. */
1704 if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
1706 cmp_path = svn_relpath_join(pb->cmp_path,
1707 svn_relpath_basename(path, pool), pool);
1708 cmp_rev = pb->cmp_rev;
1711 SVN_ERR(dump_node(eb, path,
1712 svn_node_file, svn_node_action_change,
1713 FALSE, cmp_path, cmp_rev, pool));
1715 *file_baton = NULL; /* muhahahaha again */
1716 return SVN_NO_ERROR;
1720 static svn_error_t *
1721 change_dir_prop(void *parent_baton,
1723 const svn_string_t *value,
1726 struct dir_baton *db = parent_baton;
1727 struct edit_baton *eb = db->edit_baton;
1729 /* This function is what distinguishes between a directory that is
1730 opened to merely get somewhere, vs. one that is opened because it
1731 *actually* changed by itself.
1733 Instead of recording the prop changes here, we just use this method
1734 to trigger writing the node; dump_node() finds all the changes. */
1735 if (! db->written_out)
1737 SVN_ERR(dump_node(eb, db->path,
1738 svn_node_dir, svn_node_action_change,
1739 /* ### We pass is_copy=FALSE; this might be wrong
1740 but the parameter isn't used when action=change. */
1741 FALSE, db->cmp_path, db->cmp_rev, pool));
1742 db->written_out = TRUE;
1744 return SVN_NO_ERROR;
1747 static svn_error_t *
1748 fetch_props_func(apr_hash_t **props,
1751 svn_revnum_t base_revision,
1752 apr_pool_t *result_pool,
1753 apr_pool_t *scratch_pool)
1755 struct edit_baton *eb = baton;
1757 svn_fs_root_t *fs_root;
1759 if (!SVN_IS_VALID_REVNUM(base_revision))
1760 base_revision = eb->current_rev - 1;
1762 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1764 err = svn_fs_node_proplist(props, fs_root, path, result_pool);
1765 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1767 svn_error_clear(err);
1768 *props = apr_hash_make(result_pool);
1769 return SVN_NO_ERROR;
1772 return svn_error_trace(err);
1774 return SVN_NO_ERROR;
1777 static svn_error_t *
1778 fetch_kind_func(svn_node_kind_t *kind,
1781 svn_revnum_t base_revision,
1782 apr_pool_t *scratch_pool)
1784 struct edit_baton *eb = baton;
1785 svn_fs_root_t *fs_root;
1787 if (!SVN_IS_VALID_REVNUM(base_revision))
1788 base_revision = eb->current_rev - 1;
1790 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1792 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
1794 return SVN_NO_ERROR;
1797 static svn_error_t *
1798 fetch_base_func(const char **filename,
1801 svn_revnum_t base_revision,
1802 apr_pool_t *result_pool,
1803 apr_pool_t *scratch_pool)
1805 struct edit_baton *eb = baton;
1806 svn_stream_t *contents;
1807 svn_stream_t *file_stream;
1808 const char *tmp_filename;
1810 svn_fs_root_t *fs_root;
1812 if (!SVN_IS_VALID_REVNUM(base_revision))
1813 base_revision = eb->current_rev - 1;
1815 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1817 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
1818 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1820 svn_error_clear(err);
1822 return SVN_NO_ERROR;
1825 return svn_error_trace(err);
1826 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
1827 svn_io_file_del_on_pool_cleanup,
1828 scratch_pool, scratch_pool));
1829 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
1831 *filename = apr_pstrdup(result_pool, tmp_filename);
1833 return SVN_NO_ERROR;
1837 static svn_error_t *
1838 get_dump_editor(const svn_delta_editor_t **editor,
1841 svn_revnum_t to_rev,
1842 const char *root_path,
1843 svn_stream_t *stream,
1844 svn_boolean_t *found_old_reference,
1845 svn_boolean_t *found_old_mergeinfo,
1846 svn_error_t *(*custom_close_directory)(void *dir_baton,
1847 apr_pool_t *scratch_pool),
1848 svn_repos_notify_func_t notify_func,
1850 svn_revnum_t oldest_dumped_rev,
1851 svn_boolean_t use_deltas,
1852 svn_boolean_t verify,
1853 svn_boolean_t check_normalization,
1856 /* Allocate an edit baton to be stored in every directory baton.
1857 Set it up for the directory baton we create here, which is the
1859 struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1860 svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
1861 svn_delta_shim_callbacks_t *shim_callbacks =
1862 svn_delta_shim_callbacks_default(pool);
1864 /* Set up the edit baton. */
1865 eb->stream = stream;
1866 eb->notify_func = notify_func;
1867 eb->notify_baton = notify_baton;
1868 eb->oldest_dumped_rev = oldest_dumped_rev;
1869 eb->path = apr_pstrdup(pool, root_path);
1870 SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
1872 eb->current_rev = to_rev;
1873 eb->use_deltas = use_deltas;
1874 eb->verify = verify;
1875 eb->check_normalization = check_normalization;
1876 eb->found_old_reference = found_old_reference;
1877 eb->found_old_mergeinfo = found_old_mergeinfo;
1879 /* In non-verification mode, we will allow anything to be dumped because
1880 it might be an incremental dump with possible manual intervention.
1881 Also, this might be the last resort when it comes to data recovery.
1883 Else, make sure that all paths exists at their respective revisions.
1885 eb->path_tracker = verify ? tracker_create(to_rev, pool) : NULL;
1887 /* Set up the editor. */
1888 dump_editor->open_root = open_root;
1889 dump_editor->delete_entry = delete_entry;
1890 dump_editor->add_directory = add_directory;
1891 dump_editor->open_directory = open_directory;
1892 if (custom_close_directory)
1893 dump_editor->close_directory = custom_close_directory;
1895 dump_editor->close_directory = close_directory;
1896 dump_editor->change_dir_prop = change_dir_prop;
1897 dump_editor->add_file = add_file;
1898 dump_editor->open_file = open_file;
1901 *editor = dump_editor;
1903 shim_callbacks->fetch_kind_func = fetch_kind_func;
1904 shim_callbacks->fetch_props_func = fetch_props_func;
1905 shim_callbacks->fetch_base_func = fetch_base_func;
1906 shim_callbacks->fetch_baton = eb;
1908 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1909 NULL, NULL, shim_callbacks, pool, pool));
1911 return SVN_NO_ERROR;
1914 /*----------------------------------------------------------------------*/
1916 /** The main dumping routine, svn_repos_dump_fs. **/
1919 /* Helper for svn_repos_dump_fs.
1921 Write a revision record of REV in FS to writable STREAM, using POOL.
1923 static svn_error_t *
1924 write_revision_record(svn_stream_t *stream,
1930 apr_time_t timetemp;
1931 svn_string_t *datevalue;
1933 SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
1935 /* Run revision date properties through the time conversion to
1936 canonicalize them. */
1937 /* ### Remove this when it is no longer needed for sure. */
1938 datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
1941 SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
1942 datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
1944 svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
1947 SVN_ERR(svn_repos__dump_revision_record(stream, rev, NULL, props,
1948 TRUE /*props_section_always*/,
1950 return SVN_NO_ERROR;
1955 /* The main dumper. */
1957 svn_repos_dump_fs3(svn_repos_t *repos,
1958 svn_stream_t *stream,
1959 svn_revnum_t start_rev,
1960 svn_revnum_t end_rev,
1961 svn_boolean_t incremental,
1962 svn_boolean_t use_deltas,
1963 svn_repos_notify_func_t notify_func,
1965 svn_cancel_func_t cancel_func,
1969 const svn_delta_editor_t *dump_editor;
1970 void *dump_edit_baton = NULL;
1972 svn_fs_t *fs = svn_repos_fs(repos);
1973 apr_pool_t *subpool = svn_pool_create(pool);
1974 svn_revnum_t youngest;
1977 svn_boolean_t found_old_reference = FALSE;
1978 svn_boolean_t found_old_mergeinfo = FALSE;
1979 svn_repos_notify_t *notify;
1981 /* Determine the current youngest revision of the filesystem. */
1982 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1984 /* Use default vals if necessary. */
1985 if (! SVN_IS_VALID_REVNUM(start_rev))
1987 if (! SVN_IS_VALID_REVNUM(end_rev))
1990 stream = svn_stream_empty(pool);
1992 /* Validate the revisions. */
1993 if (start_rev > end_rev)
1994 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1995 _("Start revision %ld"
1996 " is greater than end revision %ld"),
1997 start_rev, end_rev);
1998 if (end_rev > youngest)
1999 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2000 _("End revision %ld is invalid "
2001 "(youngest revision is %ld)"),
2004 /* Write out the UUID. */
2005 SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
2007 /* If we're not using deltas, use the previous version, for
2008 compatibility with svn 1.0.x. */
2009 version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
2013 /* Write out "general" metadata for the dumpfile. In this case, a
2014 magic header followed by a dumpfile format version. */
2015 SVN_ERR(svn_stream_printf(stream, pool,
2016 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
2018 SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
2021 /* Create a notify object that we can reuse in the loop. */
2023 notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
2026 /* Main loop: we're going to dump revision REV. */
2027 for (rev = start_rev; rev <= end_rev; rev++)
2029 svn_fs_root_t *to_root;
2030 svn_boolean_t use_deltas_for_rev;
2032 svn_pool_clear(subpool);
2034 /* Check for cancellation. */
2036 SVN_ERR(cancel_func(cancel_baton));
2038 /* Write the revision record. */
2039 SVN_ERR(write_revision_record(stream, fs, rev, subpool));
2041 /* When dumping revision 0, we just write out the revision record.
2042 The parser might want to use its properties. */
2046 /* Fetch the editor which dumps nodes to a file. Regardless of
2047 what we've been told, don't use deltas for the first rev of a
2048 non-incremental dump. */
2049 use_deltas_for_rev = use_deltas && (incremental || rev != start_rev);
2050 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, rev,
2051 "", stream, &found_old_reference,
2052 &found_old_mergeinfo, NULL,
2053 notify_func, notify_baton,
2054 start_rev, use_deltas_for_rev, FALSE, FALSE,
2057 /* Drive the editor in one way or another. */
2058 SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, subpool));
2060 /* If this is the first revision of a non-incremental dump,
2061 we're in for a full tree dump. Otherwise, we want to simply
2062 replay the revision. */
2063 if ((rev == start_rev) && (! incremental))
2065 /* Compare against revision 0, so everything appears to be added. */
2066 svn_fs_root_t *from_root;
2067 SVN_ERR(svn_fs_revision_root(&from_root, fs, 0, subpool));
2068 SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
2070 dump_editor, dump_edit_baton,
2073 FALSE, /* don't send text-deltas */
2075 FALSE, /* don't send entry props */
2076 FALSE, /* don't ignore ancestry */
2081 /* The normal case: compare consecutive revs. */
2082 SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2083 dump_editor, dump_edit_baton,
2084 NULL, NULL, subpool));
2086 /* While our editor close_edit implementation is a no-op, we still
2087 do this for completeness. */
2088 SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool));
2094 notify->revision = rev;
2095 notify_func(notify_baton, notify, subpool);
2101 /* Did we issue any warnings about references to revisions older than
2102 the oldest dumped revision? If so, then issue a final generic
2103 warning, since the inline warnings already issued might easily be
2106 notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool);
2107 notify_func(notify_baton, notify, subpool);
2109 if (found_old_reference)
2111 notify_warning(subpool, notify_func, notify_baton,
2112 svn_repos_notify_warning_found_old_reference,
2113 _("The range of revisions dumped "
2114 "contained references to "
2115 "copy sources outside that "
2119 /* Ditto if we issued any warnings about old revisions referenced
2120 in dumped mergeinfo. */
2121 if (found_old_mergeinfo)
2123 notify_warning(subpool, notify_func, notify_baton,
2124 svn_repos_notify_warning_found_old_mergeinfo,
2125 _("The range of revisions dumped "
2126 "contained mergeinfo "
2127 "which reference revisions outside "
2132 svn_pool_destroy(subpool);
2134 return SVN_NO_ERROR;
2138 /*----------------------------------------------------------------------*/
2140 /* verify, based on dump */
2143 /* Creating a new revision that changes /A/B/E/bravo means creating new
2144 directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
2145 each entry not changed in the new revision a link back to the entry in a
2146 previous revision. svn_repos_replay()ing a revision does not verify that
2147 those links are correct.
2149 For paths actually changed in the revision we verify, we get directory
2150 contents or file length twice: once in the dump editor, and once here.
2151 We could create a new verify baton, store in it the changed paths, and
2152 skip those here, but that means building an entire wrapper editor and
2153 managing two levels of batons. The impact from checking these entries
2154 twice should be minimal, while the code to avoid it is not.
2157 static svn_error_t *
2158 verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
2159 void *val, apr_pool_t *pool)
2161 struct dir_baton *db = baton;
2162 svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
2164 svn_boolean_t right_kind;
2166 path = svn_relpath_join(db->path, (const char *)key, pool);
2168 /* since we can't access the directory entries directly by their ID,
2169 we need to navigate from the FS_ROOT to them (relatively expensive
2170 because we may start at a never rev than the last change to node).
2171 We check that the node kind stored in the noderev matches the dir
2172 entry. This also ensures that all entries point to valid noderevs.
2174 switch (dirent->kind) {
2176 SVN_ERR(svn_fs_is_dir(&right_kind, db->edit_baton->fs_root, path, pool));
2178 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2179 _("Node '%s' is not a directory."),
2184 SVN_ERR(svn_fs_is_file(&right_kind, db->edit_baton->fs_root, path, pool));
2186 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2187 _("Node '%s' is not a file."),
2191 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2192 _("Unexpected node kind %d for '%s'"),
2193 dirent->kind, path);
2196 return SVN_NO_ERROR;
2199 /* Baton used by the check_name_collision hash iterator. */
2200 struct check_name_collision_baton
2202 struct dir_baton *dir_baton;
2203 apr_hash_t *normalized;
2204 svn_membuf_t buffer;
2207 /* Scan the directory and report all entry names that differ only in
2208 Unicode character representation. */
2209 static svn_error_t *
2210 check_name_collision(void *baton, const void *key, apr_ssize_t klen,
2211 void *val, apr_pool_t *iterpool)
2213 struct check_name_collision_baton *const cb = baton;
2217 SVN_ERR(svn_utf__normalize(&name, key, klen, &cb->buffer));
2219 found = svn_hash_gets(cb->normalized, name);
2221 svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2223 else if (found == normalized_collision)
2224 /* Skip already reported collision */;
2227 struct dir_baton *const db = cb->dir_baton;
2228 struct edit_baton *const eb = db->edit_baton;
2229 const char* normpath;
2231 svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2232 normalized_collision);
2234 SVN_ERR(svn_utf__normalize(
2235 &normpath, svn_relpath_join(db->path, name, iterpool),
2236 SVN_UTF__UNKNOWN_LENGTH, &cb->buffer));
2237 notify_warning(iterpool, eb->notify_func, eb->notify_baton,
2238 svn_repos_notify_warning_name_collision,
2239 _("Duplicate representation of path '%s'"), normpath);
2241 return SVN_NO_ERROR;
2245 static svn_error_t *
2246 verify_close_directory(void *dir_baton, apr_pool_t *pool)
2248 struct dir_baton *db = dir_baton;
2249 apr_hash_t *dirents;
2250 SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root,
2252 SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
2255 if (db->check_name_collision)
2257 struct check_name_collision_baton check_baton;
2258 check_baton.dir_baton = db;
2259 check_baton.normalized = apr_hash_make(pool);
2260 svn_membuf__create(&check_baton.buffer, 0, pool);
2261 SVN_ERR(svn_iter_apr_hash(NULL, dirents, check_name_collision,
2262 &check_baton, pool));
2265 return close_directory(dir_baton, pool);
2268 /* Verify revision REV in file system FS. */
2269 static svn_error_t *
2270 verify_one_revision(svn_fs_t *fs,
2272 svn_repos_notify_func_t notify_func,
2274 svn_revnum_t start_rev,
2275 svn_boolean_t check_normalization,
2276 svn_cancel_func_t cancel_func,
2278 apr_pool_t *scratch_pool)
2280 const svn_delta_editor_t *dump_editor;
2281 void *dump_edit_baton;
2282 svn_fs_root_t *to_root;
2284 const svn_delta_editor_t *cancel_editor;
2285 void *cancel_edit_baton;
2287 /* Get cancellable dump editor, but with our close_directory handler.*/
2288 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
2290 svn_stream_empty(scratch_pool),
2292 verify_close_directory,
2293 notify_func, notify_baton,
2295 FALSE, TRUE, /* use_deltas, verify */
2296 check_normalization,
2298 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2299 dump_editor, dump_edit_baton,
2303 SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, scratch_pool));
2304 SVN_ERR(svn_fs_verify_root(to_root, scratch_pool));
2305 SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2306 cancel_editor, cancel_edit_baton,
2307 NULL, NULL, scratch_pool));
2309 /* While our editor close_edit implementation is a no-op, we still
2310 do this for completeness. */
2311 SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, scratch_pool));
2313 SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, scratch_pool));
2315 return SVN_NO_ERROR;
2318 /* Baton type used for forwarding notifications from FS API to REPOS API. */
2319 struct verify_fs_notify_func_baton_t
2321 /* notification function to call (must not be NULL) */
2322 svn_repos_notify_func_t notify_func;
2324 /* baton to use for it */
2327 /* type of notification to send (we will simply plug in the revision) */
2328 svn_repos_notify_t *notify;
2331 /* Forward the notification to BATON. */
2333 verify_fs_notify_func(svn_revnum_t revision,
2337 struct verify_fs_notify_func_baton_t *notify_baton = baton;
2339 notify_baton->notify->revision = revision;
2340 notify_baton->notify_func(notify_baton->notify_baton,
2341 notify_baton->notify, pool);
2344 static svn_error_t *
2345 report_error(svn_revnum_t revision,
2346 svn_error_t *verify_err,
2347 svn_repos_verify_callback_t verify_callback,
2351 if (verify_callback)
2353 svn_error_t *cb_err;
2355 /* The caller provided us with a callback, so make him responsible
2356 for what's going to happen with the error. */
2357 cb_err = verify_callback(verify_baton, revision, verify_err, pool);
2358 svn_error_clear(verify_err);
2361 return SVN_NO_ERROR;
2365 /* No callback -- no second guessing. Just return the error. */
2366 return svn_error_trace(verify_err);
2371 svn_repos_verify_fs3(svn_repos_t *repos,
2372 svn_revnum_t start_rev,
2373 svn_revnum_t end_rev,
2374 svn_boolean_t check_normalization,
2375 svn_boolean_t metadata_only,
2376 svn_repos_notify_func_t notify_func,
2378 svn_repos_verify_callback_t verify_callback,
2380 svn_cancel_func_t cancel_func,
2384 svn_fs_t *fs = svn_repos_fs(repos);
2385 svn_revnum_t youngest;
2387 apr_pool_t *iterpool = svn_pool_create(pool);
2388 svn_repos_notify_t *notify;
2389 svn_fs_progress_notify_func_t verify_notify = NULL;
2390 struct verify_fs_notify_func_baton_t *verify_notify_baton = NULL;
2393 /* Determine the current youngest revision of the filesystem. */
2394 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2396 /* Use default vals if necessary. */
2397 if (! SVN_IS_VALID_REVNUM(start_rev))
2399 if (! SVN_IS_VALID_REVNUM(end_rev))
2402 /* Validate the revisions. */
2403 if (start_rev > end_rev)
2404 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2405 _("Start revision %ld"
2406 " is greater than end revision %ld"),
2407 start_rev, end_rev);
2408 if (end_rev > youngest)
2409 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2410 _("End revision %ld is invalid "
2411 "(youngest revision is %ld)"),
2414 /* Create a notify object that we can reuse within the loop and a
2415 forwarding structure for notifications from inside svn_fs_verify(). */
2418 notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, pool);
2420 verify_notify = verify_fs_notify_func;
2421 verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
2422 verify_notify_baton->notify_func = notify_func;
2423 verify_notify_baton->notify_baton = notify_baton;
2424 verify_notify_baton->notify
2425 = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool);
2428 /* Verify global metadata and backend-specific data first. */
2429 err = svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
2431 verify_notify, verify_notify_baton,
2432 cancel_func, cancel_baton, pool);
2434 if (err && err->apr_err == SVN_ERR_CANCELLED)
2436 return svn_error_trace(err);
2440 SVN_ERR(report_error(SVN_INVALID_REVNUM, err, verify_callback,
2441 verify_baton, iterpool));
2445 for (rev = start_rev; rev <= end_rev; rev++)
2447 svn_pool_clear(iterpool);
2449 /* Wrapper function to catch the possible errors. */
2450 err = verify_one_revision(fs, rev, notify_func, notify_baton,
2451 start_rev, check_normalization,
2452 cancel_func, cancel_baton,
2455 if (err && err->apr_err == SVN_ERR_CANCELLED)
2457 return svn_error_trace(err);
2461 SVN_ERR(report_error(rev, err, verify_callback, verify_baton,
2464 else if (notify_func)
2466 /* Tell the caller that we're done with this revision. */
2467 notify->revision = rev;
2468 notify_func(notify_baton, notify, iterpool);
2475 notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool);
2476 notify_func(notify_baton, notify, iterpool);
2479 svn_pool_destroy(iterpool);
2481 return SVN_NO_ERROR;