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;
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_get_offset(&offset, *tempfile, 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 REPOS to writable STREAM, using POOL.
1926 Dump revision properties as well if INCLUDE_REVPROPS has been set.
1927 AUTHZ_FUNC and AUTHZ_BATON are passed directly to the repos layer.
1929 static svn_error_t *
1930 write_revision_record(svn_stream_t *stream,
1933 svn_boolean_t include_revprops,
1934 svn_repos_authz_func_t authz_func,
1939 apr_time_t timetemp;
1940 svn_string_t *datevalue;
1942 if (include_revprops)
1944 SVN_ERR(svn_repos_fs_revision_proplist(&props, repos, rev,
1945 authz_func, authz_baton, pool));
1947 /* Run revision date properties through the time conversion to
1948 canonicalize them. */
1949 /* ### Remove this when it is no longer needed for sure. */
1950 datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
1953 SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
1954 datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
1956 svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
1961 /* Although we won't use it, we still need this container for the
1963 props = apr_hash_make(pool);
1966 SVN_ERR(svn_repos__dump_revision_record(stream, rev, NULL, props,
1969 return SVN_NO_ERROR;
1972 /* Baton for dump_filter_authz_func(). */
1973 typedef struct dump_filter_baton_t
1975 svn_repos_dump_filter_func_t filter_func;
1977 } dump_filter_baton_t;
1979 /* Implements svn_repos_authz_func_t. */
1980 static svn_error_t *
1981 dump_filter_authz_func(svn_boolean_t *allowed,
1982 svn_fs_root_t *root,
1987 dump_filter_baton_t *b = baton;
1989 return svn_error_trace(b->filter_func(allowed, root, path, b->filter_baton,
1995 /* The main dumper. */
1997 svn_repos_dump_fs4(svn_repos_t *repos,
1998 svn_stream_t *stream,
1999 svn_revnum_t start_rev,
2000 svn_revnum_t end_rev,
2001 svn_boolean_t incremental,
2002 svn_boolean_t use_deltas,
2003 svn_boolean_t include_revprops,
2004 svn_boolean_t include_changes,
2005 svn_repos_notify_func_t notify_func,
2007 svn_repos_dump_filter_func_t filter_func,
2009 svn_cancel_func_t cancel_func,
2013 const svn_delta_editor_t *dump_editor;
2014 void *dump_edit_baton = NULL;
2016 svn_fs_t *fs = svn_repos_fs(repos);
2017 apr_pool_t *iterpool = svn_pool_create(pool);
2018 svn_revnum_t youngest;
2021 svn_boolean_t found_old_reference = FALSE;
2022 svn_boolean_t found_old_mergeinfo = FALSE;
2023 svn_repos_notify_t *notify;
2024 svn_repos_authz_func_t authz_func;
2025 dump_filter_baton_t authz_baton = {0};
2027 /* Make sure we catch up on the latest revprop changes. This is the only
2028 * time we will refresh the revprop data in this query. */
2029 SVN_ERR(svn_fs_refresh_revision_props(fs, pool));
2031 /* Determine the current youngest revision of the filesystem. */
2032 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2034 /* Use default vals if necessary. */
2035 if (! SVN_IS_VALID_REVNUM(start_rev))
2037 if (! SVN_IS_VALID_REVNUM(end_rev))
2040 stream = svn_stream_empty(pool);
2042 /* Validate the revisions. */
2043 if (start_rev > end_rev)
2044 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2045 _("Start revision %ld"
2046 " is greater than end revision %ld"),
2047 start_rev, end_rev);
2048 if (end_rev > youngest)
2049 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2050 _("End revision %ld is invalid "
2051 "(youngest revision is %ld)"),
2054 /* We use read authz callback to implement dump filtering. If there is no
2055 * read access for some node, it will be excluded from dump as well as
2056 * references to it (e.g. copy source). */
2059 authz_func = dump_filter_authz_func;
2060 authz_baton.filter_func = filter_func;
2061 authz_baton.filter_baton = filter_baton;
2068 /* Write out the UUID. */
2069 SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
2071 /* If we're not using deltas, use the previous version, for
2072 compatibility with svn 1.0.x. */
2073 version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
2077 /* Write out "general" metadata for the dumpfile. In this case, a
2078 magic header followed by a dumpfile format version. */
2079 SVN_ERR(svn_stream_printf(stream, pool,
2080 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
2082 SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
2085 /* Create a notify object that we can reuse in the loop. */
2087 notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
2090 /* Main loop: we're going to dump revision REV. */
2091 for (rev = start_rev; rev <= end_rev; rev++)
2093 svn_fs_root_t *to_root;
2094 svn_boolean_t use_deltas_for_rev;
2096 svn_pool_clear(iterpool);
2098 /* Check for cancellation. */
2100 SVN_ERR(cancel_func(cancel_baton));
2102 /* Write the revision record. */
2103 SVN_ERR(write_revision_record(stream, repos, rev, include_revprops,
2104 authz_func, &authz_baton, iterpool));
2106 /* When dumping revision 0, we just write out the revision record.
2107 The parser might want to use its properties.
2108 If we don't want revision changes at all, skip in any case. */
2109 if (rev == 0 || !include_changes)
2112 /* Fetch the editor which dumps nodes to a file. Regardless of
2113 what we've been told, don't use deltas for the first rev of a
2114 non-incremental dump. */
2115 use_deltas_for_rev = use_deltas && (incremental || rev != start_rev);
2116 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, rev,
2117 "", stream, &found_old_reference,
2118 &found_old_mergeinfo, NULL,
2119 notify_func, notify_baton,
2120 start_rev, use_deltas_for_rev, FALSE, FALSE,
2123 /* Drive the editor in one way or another. */
2124 SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool));
2126 /* If this is the first revision of a non-incremental dump,
2127 we're in for a full tree dump. Otherwise, we want to simply
2128 replay the revision. */
2129 if ((rev == start_rev) && (! incremental))
2131 /* Compare against revision 0, so everything appears to be added. */
2132 svn_fs_root_t *from_root;
2133 SVN_ERR(svn_fs_revision_root(&from_root, fs, 0, iterpool));
2134 SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
2136 dump_editor, dump_edit_baton,
2137 authz_func, &authz_baton,
2138 FALSE, /* don't send text-deltas */
2140 FALSE, /* don't send entry props */
2141 FALSE, /* don't ignore ancestry */
2146 /* The normal case: compare consecutive revs. */
2147 SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2148 dump_editor, dump_edit_baton,
2149 authz_func, &authz_baton, iterpool));
2151 /* While our editor close_edit implementation is a no-op, we still
2152 do this for completeness. */
2153 SVN_ERR(dump_editor->close_edit(dump_edit_baton, iterpool));
2159 notify->revision = rev;
2160 notify_func(notify_baton, notify, iterpool);
2166 /* Did we issue any warnings about references to revisions older than
2167 the oldest dumped revision? If so, then issue a final generic
2168 warning, since the inline warnings already issued might easily be
2171 notify = svn_repos_notify_create(svn_repos_notify_dump_end, iterpool);
2172 notify_func(notify_baton, notify, iterpool);
2174 if (found_old_reference)
2176 notify_warning(iterpool, notify_func, notify_baton,
2177 svn_repos_notify_warning_found_old_reference,
2178 _("The range of revisions dumped "
2179 "contained references to "
2180 "copy sources outside that "
2184 /* Ditto if we issued any warnings about old revisions referenced
2185 in dumped mergeinfo. */
2186 if (found_old_mergeinfo)
2188 notify_warning(iterpool, notify_func, notify_baton,
2189 svn_repos_notify_warning_found_old_mergeinfo,
2190 _("The range of revisions dumped "
2191 "contained mergeinfo "
2192 "which reference revisions outside "
2197 svn_pool_destroy(iterpool);
2199 return SVN_NO_ERROR;
2203 /*----------------------------------------------------------------------*/
2205 /* verify, based on dump */
2208 /* Creating a new revision that changes /A/B/E/bravo means creating new
2209 directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
2210 each entry not changed in the new revision a link back to the entry in a
2211 previous revision. svn_repos_replay()ing a revision does not verify that
2212 those links are correct.
2214 For paths actually changed in the revision we verify, we get directory
2215 contents or file length twice: once in the dump editor, and once here.
2216 We could create a new verify baton, store in it the changed paths, and
2217 skip those here, but that means building an entire wrapper editor and
2218 managing two levels of batons. The impact from checking these entries
2219 twice should be minimal, while the code to avoid it is not.
2222 static svn_error_t *
2223 verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
2224 void *val, apr_pool_t *pool)
2226 struct dir_baton *db = baton;
2227 svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
2229 svn_boolean_t right_kind;
2231 path = svn_relpath_join(db->path, (const char *)key, pool);
2233 /* since we can't access the directory entries directly by their ID,
2234 we need to navigate from the FS_ROOT to them (relatively expensive
2235 because we may start at a never rev than the last change to node).
2236 We check that the node kind stored in the noderev matches the dir
2237 entry. This also ensures that all entries point to valid noderevs.
2239 switch (dirent->kind) {
2241 SVN_ERR(svn_fs_is_dir(&right_kind, db->edit_baton->fs_root, path, pool));
2243 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2244 _("Node '%s' is not a directory."),
2249 SVN_ERR(svn_fs_is_file(&right_kind, db->edit_baton->fs_root, path, pool));
2251 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2252 _("Node '%s' is not a file."),
2256 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2257 _("Unexpected node kind %d for '%s'"),
2258 dirent->kind, path);
2261 return SVN_NO_ERROR;
2264 /* Baton used by the check_name_collision hash iterator. */
2265 struct check_name_collision_baton
2267 struct dir_baton *dir_baton;
2268 apr_hash_t *normalized;
2269 svn_membuf_t buffer;
2272 /* Scan the directory and report all entry names that differ only in
2273 Unicode character representation. */
2274 static svn_error_t *
2275 check_name_collision(void *baton, const void *key, apr_ssize_t klen,
2276 void *val, apr_pool_t *iterpool)
2278 struct check_name_collision_baton *const cb = baton;
2282 SVN_ERR(svn_utf__normalize(&name, key, klen, &cb->buffer));
2284 found = svn_hash_gets(cb->normalized, name);
2286 svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2288 else if (found == normalized_collision)
2289 /* Skip already reported collision */;
2292 struct dir_baton *const db = cb->dir_baton;
2293 struct edit_baton *const eb = db->edit_baton;
2294 const char* normpath;
2296 svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2297 normalized_collision);
2299 SVN_ERR(svn_utf__normalize(
2300 &normpath, svn_relpath_join(db->path, name, iterpool),
2301 SVN_UTF__UNKNOWN_LENGTH, &cb->buffer));
2302 notify_warning(iterpool, eb->notify_func, eb->notify_baton,
2303 svn_repos_notify_warning_name_collision,
2304 _("Duplicate representation of path '%s'"), normpath);
2306 return SVN_NO_ERROR;
2310 static svn_error_t *
2311 verify_close_directory(void *dir_baton, apr_pool_t *pool)
2313 struct dir_baton *db = dir_baton;
2314 apr_hash_t *dirents;
2315 SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root,
2317 SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
2320 if (db->check_name_collision)
2322 struct check_name_collision_baton check_baton;
2323 check_baton.dir_baton = db;
2324 check_baton.normalized = apr_hash_make(pool);
2325 svn_membuf__create(&check_baton.buffer, 0, pool);
2326 SVN_ERR(svn_iter_apr_hash(NULL, dirents, check_name_collision,
2327 &check_baton, pool));
2330 return close_directory(dir_baton, pool);
2333 /* Verify revision REV in file system FS. */
2334 static svn_error_t *
2335 verify_one_revision(svn_fs_t *fs,
2337 svn_repos_notify_func_t notify_func,
2339 svn_revnum_t start_rev,
2340 svn_boolean_t check_normalization,
2341 svn_cancel_func_t cancel_func,
2343 apr_pool_t *scratch_pool)
2345 const svn_delta_editor_t *dump_editor;
2346 void *dump_edit_baton;
2347 svn_fs_root_t *to_root;
2349 const svn_delta_editor_t *cancel_editor;
2350 void *cancel_edit_baton;
2352 /* Get cancellable dump editor, but with our close_directory handler.*/
2353 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
2355 svn_stream_empty(scratch_pool),
2357 verify_close_directory,
2358 notify_func, notify_baton,
2360 FALSE, TRUE, /* use_deltas, verify */
2361 check_normalization,
2363 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2364 dump_editor, dump_edit_baton,
2368 SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, scratch_pool));
2369 SVN_ERR(svn_fs_verify_root(to_root, scratch_pool));
2370 SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2371 cancel_editor, cancel_edit_baton,
2372 NULL, NULL, scratch_pool));
2374 /* While our editor close_edit implementation is a no-op, we still
2375 do this for completeness. */
2376 SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, scratch_pool));
2378 SVN_ERR(svn_fs_revision_proplist2(&props, fs, rev, FALSE, scratch_pool,
2381 return SVN_NO_ERROR;
2384 /* Baton type used for forwarding notifications from FS API to REPOS API. */
2385 struct verify_fs_notify_func_baton_t
2387 /* notification function to call (must not be NULL) */
2388 svn_repos_notify_func_t notify_func;
2390 /* baton to use for it */
2393 /* type of notification to send (we will simply plug in the revision) */
2394 svn_repos_notify_t *notify;
2397 /* Forward the notification to BATON. */
2399 verify_fs_notify_func(svn_revnum_t revision,
2403 struct verify_fs_notify_func_baton_t *notify_baton = baton;
2405 notify_baton->notify->revision = revision;
2406 notify_baton->notify_func(notify_baton->notify_baton,
2407 notify_baton->notify, pool);
2410 static svn_error_t *
2411 report_error(svn_revnum_t revision,
2412 svn_error_t *verify_err,
2413 svn_repos_verify_callback_t verify_callback,
2417 if (verify_callback)
2419 svn_error_t *cb_err;
2421 /* The caller provided us with a callback, so make him responsible
2422 for what's going to happen with the error. */
2423 cb_err = verify_callback(verify_baton, revision, verify_err, pool);
2424 svn_error_clear(verify_err);
2427 return SVN_NO_ERROR;
2431 /* No callback -- no second guessing. Just return the error. */
2432 return svn_error_trace(verify_err);
2437 svn_repos_verify_fs3(svn_repos_t *repos,
2438 svn_revnum_t start_rev,
2439 svn_revnum_t end_rev,
2440 svn_boolean_t check_normalization,
2441 svn_boolean_t metadata_only,
2442 svn_repos_notify_func_t notify_func,
2444 svn_repos_verify_callback_t verify_callback,
2446 svn_cancel_func_t cancel_func,
2450 svn_fs_t *fs = svn_repos_fs(repos);
2451 svn_revnum_t youngest;
2453 apr_pool_t *iterpool = svn_pool_create(pool);
2454 svn_repos_notify_t *notify;
2455 svn_fs_progress_notify_func_t verify_notify = NULL;
2456 struct verify_fs_notify_func_baton_t *verify_notify_baton = NULL;
2459 /* Make sure we catch up on the latest revprop changes. This is the only
2460 * time we will refresh the revprop data in this query. */
2461 SVN_ERR(svn_fs_refresh_revision_props(fs, pool));
2463 /* Determine the current youngest revision of the filesystem. */
2464 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2466 /* Use default vals if necessary. */
2467 if (! SVN_IS_VALID_REVNUM(start_rev))
2469 if (! SVN_IS_VALID_REVNUM(end_rev))
2472 /* Validate the revisions. */
2473 if (start_rev > end_rev)
2474 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2475 _("Start revision %ld"
2476 " is greater than end revision %ld"),
2477 start_rev, end_rev);
2478 if (end_rev > youngest)
2479 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2480 _("End revision %ld is invalid "
2481 "(youngest revision is %ld)"),
2484 /* Create a notify object that we can reuse within the loop and a
2485 forwarding structure for notifications from inside svn_fs_verify(). */
2488 notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, pool);
2490 verify_notify = verify_fs_notify_func;
2491 verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
2492 verify_notify_baton->notify_func = notify_func;
2493 verify_notify_baton->notify_baton = notify_baton;
2494 verify_notify_baton->notify
2495 = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool);
2498 /* Verify global metadata and backend-specific data first. */
2499 err = svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
2501 verify_notify, verify_notify_baton,
2502 cancel_func, cancel_baton, pool);
2504 if (err && err->apr_err == SVN_ERR_CANCELLED)
2506 return svn_error_trace(err);
2510 SVN_ERR(report_error(SVN_INVALID_REVNUM, err, verify_callback,
2511 verify_baton, iterpool));
2515 for (rev = start_rev; rev <= end_rev; rev++)
2517 svn_pool_clear(iterpool);
2519 /* Wrapper function to catch the possible errors. */
2520 err = verify_one_revision(fs, rev, notify_func, notify_baton,
2521 start_rev, check_normalization,
2522 cancel_func, cancel_baton,
2525 if (err && err->apr_err == SVN_ERR_CANCELLED)
2527 return svn_error_trace(err);
2531 SVN_ERR(report_error(rev, err, verify_callback, verify_baton,
2534 else if (notify_func)
2536 /* Tell the caller that we're done with this revision. */
2537 notify->revision = rev;
2538 notify_func(notify_baton, notify, iterpool);
2545 notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool);
2546 notify_func(notify_baton, notify, iterpool);
2549 svn_pool_destroy(iterpool);
2551 return SVN_NO_ERROR;