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"
47 #include "private/svn_fspath.h"
49 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
51 /*----------------------------------------------------------------------*/
54 /* To be able to check whether a path exists in the current revision
55 (as changes come in), we need to track the relevant tree changes.
57 In particular, we remember deletions, additions and copies including
58 their copy-from info. Since the dump performs a pre-order tree walk,
59 we only need to store the data for the stack of parent folders.
61 The problem that we are trying to solve is that the dump receives
62 transforming operations whose validity depends on previous operations
63 in the same revision but cannot be checked against the final state
64 as stored in the repository as that is the state *after* we applied
65 the respective tree changes.
67 Note that the tracker functions don't perform any sanity or validity
68 checks. Those higher-level tests have to be done in the calling code.
69 However, there is no way to corrupt the data structure using the
73 /* Single entry in the path tracker. Not all levels along the path
74 hierarchy do need to have an instance of this struct but only those
75 that got changed by a tree modification.
77 Please note that the path info in this struct is stored in re-usable
78 stringbuf objects such that we don't need to allocate more memory than
79 the longest path we encounter.
81 typedef struct path_tracker_entry_t
83 /* path in the current tree */
84 svn_stringbuf_t *path;
86 /* copy-from path (must be empty if COPYFROM_REV is SVN_INVALID_REVNUM) */
87 svn_stringbuf_t *copyfrom_path;
89 /* copy-from revision (SVN_INVALID_REVNUM for additions / replacements
90 that don't copy history, i.e. with no sub-tree) */
91 svn_revnum_t copyfrom_rev;
93 /* if FALSE, PATH has been deleted */
95 } path_tracker_entry_t;
97 /* Tracks all tree modifications above the current path.
99 typedef struct path_tracker_t
101 /* Container for all relevant tree changes in depth order.
102 May contain more entries than DEPTH to allow for reusing memory.
103 Only entries 0 .. DEPTH-1 are valid.
105 apr_array_header_t *stack;
107 /* Number of relevant entries in STACK. May be 0 */
110 /* Revision that we current track. If DEPTH is 0, paths are exist in
111 REVISION exactly when they exist in REVISION-1. This applies only
112 to the current state of our tree walk.
114 svn_revnum_t revision;
116 /* Allocate container entries here. */
120 /* Return a new path tracker object for REVISION, allocated in POOL.
122 static path_tracker_t *
123 tracker_create(svn_revnum_t revision,
126 path_tracker_t *result = apr_pcalloc(pool, sizeof(*result));
127 result->stack = apr_array_make(pool, 16, sizeof(path_tracker_entry_t));
128 result->revision = revision;
134 /* Remove all entries from TRACKER that are not relevant to PATH anymore.
135 * If ALLOW_EXACT_MATCH is FALSE, keep only entries that pertain to
136 * parent folders but not to PATH itself.
138 * This internal function implicitly updates the tracker state during the
139 * tree by removing "past" entries. Other functions will add entries when
140 * we encounter a new tree change.
143 tracker_trim(path_tracker_t *tracker,
145 svn_boolean_t allow_exact_match)
147 /* remove everything that is unrelated to PATH.
148 Note that TRACKER->STACK is depth-ordered,
149 i.e. stack[N] is a (maybe indirect) parent of stack[N+1]
152 for (; tracker->depth; --tracker->depth)
154 path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack,
156 path_tracker_entry_t);
158 = svn_dirent_skip_ancestor(parent->path->data, path);
160 /* always keep parents. Keep exact matches when allowed. */
161 if (rel_path && (allow_exact_match || *rel_path != '\0'))
166 /* Using TRACKER, check what path at what revision in the repository must
167 be checked to decide that whether PATH exists. Return the info in
168 *ORIG_PATH and *ORIG_REV, respectively.
170 If the path is known to not exist, *ORIG_PATH will be NULL and *ORIG_REV
171 will be SVN_INVALID_REVNUM. If *ORIG_REV is SVN_INVALID_REVNUM, PATH
172 has just been added in the revision currently being tracked.
174 Use POOL for allocations. Note that *ORIG_PATH may be allocated in POOL,
175 a reference to internal data with the same lifetime as TRACKER or just
179 tracker_lookup(const char **orig_path,
180 svn_revnum_t *orig_rev,
181 path_tracker_t *tracker,
185 tracker_trim(tracker, path, TRUE);
186 if (tracker->depth == 0)
188 /* no tree changes -> paths are the same as in the previous rev. */
190 *orig_rev = tracker->revision - 1;
194 path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack,
196 path_tracker_entry_t);
200 = svn_dirent_skip_ancestor(parent->path->data, path);
202 if (parent->copyfrom_rev != SVN_INVALID_REVNUM)
204 /* parent is a copy with history. Translate path. */
205 *orig_path = svn_dirent_join(parent->copyfrom_path->data,
207 *orig_rev = parent->copyfrom_rev;
209 else if (*rel_path == '\0')
211 /* added in this revision with no history */
213 *orig_rev = tracker->revision;
217 /* parent got added but not this path */
219 *orig_rev = SVN_INVALID_REVNUM;
224 /* (maybe parent) path has been deleted */
226 *orig_rev = SVN_INVALID_REVNUM;
231 /* Return a reference to the stack entry in TRACKER for PATH. If no
232 suitable entry exists, add one. Implicitly updates the tracked tree
235 Only the PATH member of the result is being updated. All other members
236 will have undefined values.
238 static path_tracker_entry_t *
239 tracker_add_entry(path_tracker_t *tracker,
242 path_tracker_entry_t *entry;
243 tracker_trim(tracker, path, FALSE);
245 if (tracker->depth == tracker->stack->nelts)
247 entry = apr_array_push(tracker->stack);
248 entry->path = svn_stringbuf_create_empty(tracker->pool);
249 entry->copyfrom_path = svn_stringbuf_create_empty(tracker->pool);
253 entry = &APR_ARRAY_IDX(tracker->stack, tracker->depth,
254 path_tracker_entry_t);
257 svn_stringbuf_set(entry->path, path);
263 /* Update the TRACKER with a copy from COPYFROM_PATH@COPYFROM_REV to
264 PATH in the tracked revision.
267 tracker_path_copy(path_tracker_t *tracker,
269 const char *copyfrom_path,
270 svn_revnum_t copyfrom_rev)
272 path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
274 svn_stringbuf_set(entry->copyfrom_path, copyfrom_path);
275 entry->copyfrom_rev = copyfrom_rev;
276 entry->exists = TRUE;
279 /* Update the TRACKER with a plain addition of PATH (without history).
282 tracker_path_add(path_tracker_t *tracker,
285 path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
287 svn_stringbuf_setempty(entry->copyfrom_path);
288 entry->copyfrom_rev = SVN_INVALID_REVNUM;
289 entry->exists = TRUE;
292 /* Update the TRACKER with a replacement of PATH with a plain addition
296 tracker_path_replace(path_tracker_t *tracker,
299 /* this will implicitly purge all previous sub-tree info from STACK.
300 Thus, no need to tack the deletion explicitly. */
301 tracker_path_add(tracker, path);
304 /* Update the TRACKER with a deletion of PATH.
307 tracker_path_delete(path_tracker_t *tracker,
310 path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
312 svn_stringbuf_setempty(entry->copyfrom_path);
313 entry->copyfrom_rev = SVN_INVALID_REVNUM;
314 entry->exists = FALSE;
318 /* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and
319 store it into a new temporary file *TEMPFILE. OLDROOT may be NULL,
320 in which case the delta will be computed against an empty file, as
321 per the svn_fs_get_file_delta_stream docstring. Record the length
322 of the temporary file in *LEN, and rewind the file before
325 store_delta(apr_file_t **tempfile, svn_filesize_t *len,
326 svn_fs_root_t *oldroot, const char *oldpath,
327 svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool)
329 svn_stream_t *temp_stream;
331 svn_txdelta_stream_t *delta_stream;
332 svn_txdelta_window_handler_t wh;
335 /* Create a temporary file and open a stream to it. Note that we need
336 the file handle in order to rewind it. */
337 SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL,
338 svn_io_file_del_on_pool_cleanup,
340 temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool);
342 /* Compute the delta and send it to the temporary file. */
343 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath,
344 newroot, newpath, pool));
345 svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0,
346 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
347 SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool));
349 /* Get the length of the temporary file and rewind it. */
350 SVN_ERR(svn_io_file_get_offset(&offset, *tempfile, pool));
353 return svn_io_file_seek(*tempfile, APR_SET, &offset, pool);
357 /* Send a notification of type #svn_repos_notify_warning, subtype WARNING,
358 with message WARNING_FMT formatted with the remaining variable arguments.
359 Send it by calling NOTIFY_FUNC (if not null) with NOTIFY_BATON.
361 __attribute__((format(printf, 5, 6)))
363 notify_warning(apr_pool_t *scratch_pool,
364 svn_repos_notify_func_t notify_func,
366 svn_repos_notify_warning_t warning,
367 const char *warning_fmt,
371 svn_repos_notify_t *notify;
373 if (notify_func == NULL)
376 notify = svn_repos_notify_create(svn_repos_notify_warning, scratch_pool);
377 notify->warning = warning;
378 va_start(va, warning_fmt);
379 notify->warning_str = apr_pvsprintf(scratch_pool, warning_fmt, va);
382 notify_func(notify_baton, notify, scratch_pool);
386 /*----------------------------------------------------------------------*/
388 /* Write to STREAM the header in HEADERS named KEY, if present.
391 write_header(svn_stream_t *stream,
394 apr_pool_t *scratch_pool)
396 const char *val = svn_hash_gets(headers, key);
400 SVN_ERR(svn_stream_printf(stream, scratch_pool,
401 "%s: %s\n", key, val));
406 /* Write headers, in arbitrary order.
407 * ### TODO: use a stable order
408 * ### Modifies HEADERS.
411 write_revision_headers(svn_stream_t *stream,
413 apr_pool_t *scratch_pool)
416 apr_hash_index_t *hi;
418 static const char *revision_headers_order[] =
420 SVN_REPOS_DUMPFILE_REVISION_NUMBER, /* must be first */
424 /* Write some headers in a given order */
425 for (h = revision_headers_order; *h; h++)
427 SVN_ERR(write_header(stream, headers, *h, scratch_pool));
428 svn_hash_sets(headers, *h, NULL);
431 /* Write any and all remaining headers except Content-length.
432 * ### TODO: use a stable order
434 for (hi = apr_hash_first(scratch_pool, headers); hi; hi = apr_hash_next(hi))
436 const char *key = apr_hash_this_key(hi);
438 if (strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH) != 0)
439 SVN_ERR(write_header(stream, headers, key, scratch_pool));
442 /* Content-length must be last */
443 SVN_ERR(write_header(stream, headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
449 /* A header entry: the element type of the apr_array_header_t which is
450 * the real type of svn_repos__dumpfile_headers_t.
452 typedef struct svn_repos__dumpfile_header_entry_t {
453 const char *key, *val;
454 } svn_repos__dumpfile_header_entry_t;
456 svn_repos__dumpfile_headers_t *
457 svn_repos__dumpfile_headers_create(apr_pool_t *pool)
459 svn_repos__dumpfile_headers_t *headers
460 = apr_array_make(pool, 5, sizeof(svn_repos__dumpfile_header_entry_t));
466 svn_repos__dumpfile_header_push(svn_repos__dumpfile_headers_t *headers,
470 svn_repos__dumpfile_header_entry_t *h
471 = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t);
473 h->key = apr_pstrdup(headers->pool, key);
474 h->val = apr_pstrdup(headers->pool, val);
478 svn_repos__dumpfile_header_pushf(svn_repos__dumpfile_headers_t *headers,
484 svn_repos__dumpfile_header_entry_t *h
485 = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t);
487 h->key = apr_pstrdup(headers->pool, key);
488 va_start(ap, val_fmt);
489 h->val = apr_pvsprintf(headers->pool, val_fmt, ap);
494 svn_repos__dump_headers(svn_stream_t *stream,
495 svn_repos__dumpfile_headers_t *headers,
496 apr_pool_t *scratch_pool)
500 for (i = 0; i < headers->nelts; i++)
502 svn_repos__dumpfile_header_entry_t *h
503 = &APR_ARRAY_IDX(headers, i, svn_repos__dumpfile_header_entry_t);
505 SVN_ERR(svn_stream_printf(stream, scratch_pool,
506 "%s: %s\n", h->key, h->val));
510 SVN_ERR(svn_stream_puts(stream, "\n"));
516 svn_repos__dump_magic_header_record(svn_stream_t *dump_stream,
520 SVN_ERR(svn_stream_printf(dump_stream, pool,
521 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
527 svn_repos__dump_uuid_header_record(svn_stream_t *dump_stream,
533 SVN_ERR(svn_stream_printf(dump_stream, pool, SVN_REPOS_DUMPFILE_UUID
540 svn_repos__dump_revision_record(svn_stream_t *dump_stream,
541 svn_revnum_t revision,
542 apr_hash_t *extra_headers,
543 apr_hash_t *revprops,
544 svn_boolean_t props_section_always,
545 apr_pool_t *scratch_pool)
547 svn_stringbuf_t *propstring = NULL;
551 headers = apr_hash_copy(scratch_pool, extra_headers);
553 headers = apr_hash_make(scratch_pool);
555 /* ### someday write a revision-content-checksum */
557 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER,
558 apr_psprintf(scratch_pool, "%ld", revision));
560 if (apr_hash_count(revprops) || props_section_always)
562 svn_stream_t *propstream;
564 propstring = svn_stringbuf_create_empty(scratch_pool);
565 propstream = svn_stream_from_stringbuf(propstring, scratch_pool);
566 SVN_ERR(svn_hash_write2(revprops, propstream, "PROPS-END", scratch_pool));
567 SVN_ERR(svn_stream_close(propstream));
569 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
570 apr_psprintf(scratch_pool,
571 "%" APR_SIZE_T_FMT, propstring->len));
576 /* Write out a regular Content-length header for the benefit of
577 non-Subversion RFC-822 parsers. */
578 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
579 apr_psprintf(scratch_pool,
580 "%" APR_SIZE_T_FMT, propstring->len));
583 SVN_ERR(write_revision_headers(dump_stream, headers, scratch_pool));
586 SVN_ERR(svn_stream_puts(dump_stream, "\n"));
591 SVN_ERR(svn_stream_write(dump_stream, propstring->data, &propstring->len));
594 /* put an end to revision */
595 SVN_ERR(svn_stream_puts(dump_stream, "\n"));
601 svn_repos__dump_node_record(svn_stream_t *dump_stream,
602 svn_repos__dumpfile_headers_t *headers,
603 svn_stringbuf_t *props_str,
604 svn_boolean_t has_text,
605 svn_filesize_t text_content_length,
606 svn_boolean_t content_length_always,
607 apr_pool_t *scratch_pool)
609 svn_filesize_t content_length = 0;
611 /* add content-length headers */
614 svn_repos__dumpfile_header_pushf(
615 headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
616 "%" APR_SIZE_T_FMT, props_str->len);
617 content_length += props_str->len;
621 svn_repos__dumpfile_header_pushf(
622 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH,
623 "%" SVN_FILESIZE_T_FMT, text_content_length);
624 content_length += text_content_length;
626 if (content_length_always || props_str || has_text)
628 svn_repos__dumpfile_header_pushf(
629 headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
630 "%" SVN_FILESIZE_T_FMT, content_length);
633 /* write the headers */
634 SVN_ERR(svn_repos__dump_headers(dump_stream, headers, scratch_pool));
636 /* write the props */
639 SVN_ERR(svn_stream_write(dump_stream, props_str->data, &props_str->len));
644 /*----------------------------------------------------------------------*/
646 /** An editor which dumps node-data in 'dumpfile format' to a file. **/
648 /* Look, mom! No file batons! */
652 /* The relpath which implicitly prepends all full paths coming into
653 this editor. This will almost always be "". */
656 /* The stream to dump to. */
657 svn_stream_t *stream;
659 /* Send feedback here, if non-NULL */
660 svn_repos_notify_func_t notify_func;
663 /* The fs revision root, so we can read the contents of paths. */
664 svn_fs_root_t *fs_root;
665 svn_revnum_t current_rev;
667 /* The fs, so we can grab historic information if needed. */
670 /* True if dumped nodes should output deltas instead of full text. */
671 svn_boolean_t use_deltas;
673 /* True if this "dump" is in fact a verify. */
674 svn_boolean_t verify;
676 /* True if checking UCS normalization during a verify. */
677 svn_boolean_t check_normalization;
679 /* The first revision dumped in this dumpstream. */
680 svn_revnum_t oldest_dumped_rev;
682 /* If not NULL, set to true if any references to revisions older than
683 OLDEST_DUMPED_REV were found in the dumpstream. */
684 svn_boolean_t *found_old_reference;
686 /* If not NULL, set to true if any mergeinfo was dumped which contains
687 revisions older than OLDEST_DUMPED_REV. */
688 svn_boolean_t *found_old_mergeinfo;
690 /* Structure allows us to verify the paths currently being dumped.
691 If NULL, validity checks are being skipped. */
692 path_tracker_t *path_tracker;
697 struct edit_baton *edit_baton;
699 /* has this directory been written to the output stream? */
700 svn_boolean_t written_out;
702 /* the repository relpath associated with this directory */
705 /* The comparison repository relpath and revision of this directory.
706 If both of these are valid, use them as a source against which to
707 compare the directory instead of the default comparison source of
708 PATH in the previous revision. */
709 const char *cmp_path;
710 svn_revnum_t cmp_rev;
712 /* hash of paths that need to be deleted, though some -might- be
713 replaced. maps const char * paths to this dir_baton. (they're
714 full paths, because that's what the editor driver gives us. but
715 really, they're all within this directory.) */
716 apr_hash_t *deleted_entries;
718 /* A flag indicating that new entries have been added to this
719 directory in this revision. Used to optimize detection of UCS
720 representation collisions; we will only check for that in
721 revisions where new names appear in the directory. */
722 svn_boolean_t check_name_collision;
724 /* pool to be used for deleting the hash items */
729 /* Make a directory baton to represent the directory was path
730 (relative to EDIT_BATON's path) is PATH.
732 CMP_PATH/CMP_REV are the path/revision against which this directory
733 should be compared for changes. If either is omitted (NULL for the
734 path, SVN_INVALID_REVNUM for the rev), just compare this directory
735 PATH against itself in the previous revision.
737 PB is the directory baton of this directory's parent,
738 or NULL if this is the top-level directory of the edit.
740 Perform all allocations in POOL. */
741 static struct svn_error_t *
742 make_dir_baton(struct dir_baton **dbp,
744 const char *cmp_path,
745 svn_revnum_t cmp_rev,
747 struct dir_baton *pb,
750 struct edit_baton *eb = edit_baton;
751 struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
752 const char *full_path, *canonicalized_path;
754 /* A path relative to nothing? I don't think so. */
755 SVN_ERR_ASSERT(!path || pb);
757 /* Construct the full path of this node. */
759 full_path = svn_relpath_join(eb->path, path, pool);
761 full_path = apr_pstrdup(pool, eb->path);
763 /* Remove leading slashes from copyfrom paths. */
766 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL,
767 cmp_path, pool, pool));
768 cmp_path = canonicalized_path;
771 new_db->edit_baton = eb;
772 new_db->path = full_path;
773 new_db->cmp_path = cmp_path;
774 new_db->cmp_rev = cmp_rev;
775 new_db->written_out = FALSE;
776 new_db->deleted_entries = apr_hash_make(pool);
777 new_db->check_name_collision = FALSE;
785 fetch_kind_func(svn_node_kind_t *kind,
788 svn_revnum_t base_revision,
789 apr_pool_t *scratch_pool);
791 /* Return an error when PATH in REVISION does not exist or is of a
792 different kind than EXPECTED_KIND. If the latter is svn_node_unknown,
793 skip that check. Use EB for context information. If REVISION is the
794 current revision, use EB's path tracker to follow renames, deletions,
797 Use SCRATCH_POOL for temporary allocations.
798 No-op if EB's path tracker has not been initialized.
801 node_must_exist(struct edit_baton *eb,
803 svn_revnum_t revision,
804 svn_node_kind_t expected_kind,
805 apr_pool_t *scratch_pool)
807 svn_node_kind_t kind = svn_node_none;
809 /* in case the caller is trying something stupid ... */
810 if (eb->path_tracker == NULL)
813 /* paths pertaining to the revision currently being processed must
814 be translated / checked using our path tracker. */
815 if (revision == eb->path_tracker->revision)
816 tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
818 /* determine the node type (default: no such node) */
820 SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
823 if (kind == svn_node_none)
824 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
825 _("Path '%s' not found in r%ld."),
828 if (expected_kind != kind && expected_kind != svn_node_unknown)
829 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
830 _("Unexpected node kind %d for '%s' at r%ld. "
831 "Expected kind was %d."),
832 kind, path, revision, expected_kind);
837 /* Return an error when PATH exists in REVISION. Use EB for context
838 information. If REVISION is the current revision, use EB's path
839 tracker to follow renames, deletions, etc.
841 Use SCRATCH_POOL for temporary allocations.
842 No-op if EB's path tracker has not been initialized.
845 node_must_not_exist(struct edit_baton *eb,
847 svn_revnum_t revision,
848 apr_pool_t *scratch_pool)
850 svn_node_kind_t kind = svn_node_none;
852 /* in case the caller is trying something stupid ... */
853 if (eb->path_tracker == NULL)
856 /* paths pertaining to the revision currently being processed must
857 be translated / checked using our path tracker. */
858 if (revision == eb->path_tracker->revision)
859 tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
861 /* determine the node type (default: no such node) */
863 SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
866 if (kind != svn_node_none)
867 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
868 _("Path '%s' exists in r%ld."),
874 /* If the mergeinfo in MERGEINFO_STR refers to any revisions older than
875 * OLDEST_DUMPED_REV, issue a warning and set *FOUND_OLD_MERGEINFO to TRUE,
876 * otherwise leave *FOUND_OLD_MERGEINFO unchanged.
879 verify_mergeinfo_revisions(svn_boolean_t *found_old_mergeinfo,
880 const char *mergeinfo_str,
881 svn_revnum_t oldest_dumped_rev,
882 svn_repos_notify_func_t notify_func,
886 svn_mergeinfo_t mergeinfo, old_mergeinfo;
888 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str, pool));
889 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
890 &old_mergeinfo, mergeinfo,
891 oldest_dumped_rev - 1, 0,
894 if (apr_hash_count(old_mergeinfo))
896 notify_warning(pool, notify_func, notify_baton,
897 svn_repos_notify_warning_found_old_mergeinfo,
898 _("Mergeinfo referencing revision(s) prior "
899 "to the oldest dumped revision (r%ld). "
900 "Loading this dump may result in invalid "
904 if (found_old_mergeinfo)
905 *found_old_mergeinfo = TRUE;
911 /* Unique string pointers used by verify_mergeinfo_normalization()
912 and check_name_collision() */
913 static const char normalized_unique[] = "normalized_unique";
914 static const char normalized_collision[] = "normalized_collision";
917 /* Baton for extract_mergeinfo_paths */
918 struct extract_mergeinfo_paths_baton
921 svn_boolean_t normalize;
925 /* Hash iterator that uniquifies all keys into a single hash table,
926 optionally normalizing them first. */
928 extract_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
929 void *val, apr_pool_t *iterpool)
931 struct extract_mergeinfo_paths_baton *const xb = baton;
935 SVN_ERR(svn_utf__normalize(&normkey, key, klen, &xb->buffer));
936 svn_hash_sets(xb->result,
937 apr_pstrdup(xb->buffer.pool, normkey),
941 apr_hash_set(xb->result,
942 apr_pmemdup(xb->buffer.pool, key, klen + 1), klen,
947 /* Baton for filter_mergeinfo_paths */
948 struct filter_mergeinfo_paths_baton
953 /* Compare two sets of denormalized paths from mergeinfo entries,
954 removing duplicates. */
956 filter_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
957 void *val, apr_pool_t *iterpool)
959 struct filter_mergeinfo_paths_baton *const fb = baton;
961 if (apr_hash_get(fb->paths, key, klen))
962 apr_hash_set(fb->paths, key, klen, NULL);
967 /* Baton used by the check_mergeinfo_normalization hash iterator. */
968 struct verify_mergeinfo_normalization_baton
971 apr_hash_t *normalized_paths;
973 svn_repos_notify_func_t notify_func;
977 /* Hash iterator that verifies normalization and collision of paths in
978 an svn:mergeinfo property. */
980 verify_mergeinfo_normalization(void *baton, const void *key, apr_ssize_t klen,
981 void *val, apr_pool_t *iterpool)
983 struct verify_mergeinfo_normalization_baton *const vb = baton;
985 const char *const path = key;
986 const char *normpath;
989 SVN_ERR(svn_utf__normalize(&normpath, path, klen, &vb->buffer));
990 found = svn_hash_gets(vb->normalized_paths, normpath);
992 svn_hash_sets(vb->normalized_paths,
993 apr_pstrdup(vb->buffer.pool, normpath),
995 else if (found == normalized_collision)
996 /* Skip already reported collision */;
999 /* Report path collision in mergeinfo */
1000 svn_hash_sets(vb->normalized_paths,
1001 apr_pstrdup(vb->buffer.pool, normpath),
1002 normalized_collision);
1004 notify_warning(iterpool, vb->notify_func, vb->notify_baton,
1005 svn_repos_notify_warning_mergeinfo_collision,
1006 _("Duplicate representation of path '%s'"
1007 " in %s property of '%s'"),
1008 normpath, SVN_PROP_MERGEINFO, vb->path);
1010 return SVN_NO_ERROR;
1013 /* Check UCS normalization of mergeinfo for PATH. NEW_MERGEINFO is the
1014 svn:mergeinfo property value being set; OLD_MERGEINFO is the
1015 previous property value, which may be NULL. Only the paths that
1016 were added in are checked, including collision checks. This
1017 minimizes the number of notifications we generate for a given
1018 mergeinfo property. */
1019 static svn_error_t *
1020 check_mergeinfo_normalization(const char *path,
1021 const char *new_mergeinfo,
1022 const char *old_mergeinfo,
1023 svn_repos_notify_func_t notify_func,
1027 svn_mergeinfo_t mergeinfo;
1028 apr_hash_t *normalized_paths;
1029 apr_hash_t *added_paths;
1030 struct extract_mergeinfo_paths_baton extract_baton;
1031 struct verify_mergeinfo_normalization_baton verify_baton;
1033 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, new_mergeinfo, pool));
1035 extract_baton.result = apr_hash_make(pool);
1036 extract_baton.normalize = FALSE;
1037 svn_membuf__create(&extract_baton.buffer, 0, pool);
1038 SVN_ERR(svn_iter_apr_hash(NULL, mergeinfo,
1039 extract_mergeinfo_paths,
1040 &extract_baton, pool));
1041 added_paths = extract_baton.result;
1045 struct filter_mergeinfo_paths_baton filter_baton;
1046 svn_mergeinfo_t oldinfo;
1048 extract_baton.result = apr_hash_make(pool);
1049 extract_baton.normalize = TRUE;
1050 SVN_ERR(svn_mergeinfo_parse(&oldinfo, old_mergeinfo, pool));
1051 SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
1052 extract_mergeinfo_paths,
1053 &extract_baton, pool));
1054 normalized_paths = extract_baton.result;
1056 filter_baton.paths = added_paths;
1057 SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
1058 filter_mergeinfo_paths,
1059 &filter_baton, pool));
1062 normalized_paths = apr_hash_make(pool);
1064 verify_baton.path = path;
1065 verify_baton.normalized_paths = normalized_paths;
1066 verify_baton.buffer = extract_baton.buffer;
1067 verify_baton.notify_func = notify_func;
1068 verify_baton.notify_baton = notify_baton;
1069 SVN_ERR(svn_iter_apr_hash(NULL, added_paths,
1070 verify_mergeinfo_normalization,
1071 &verify_baton, pool));
1073 return SVN_NO_ERROR;
1077 /* A special case of dump_node(), for a delete record.
1079 * The only thing special about this version is it only writes one blank
1080 * line, not two, after the headers. Why? Historical precedent for the
1081 * case where a delete record is used as part of a (delete + add-with-history)
1082 * in implementing a replacement.
1084 * Also it doesn't do a path-tracker check.
1086 static svn_error_t *
1087 dump_node_delete(svn_stream_t *stream,
1088 const char *node_relpath,
1091 svn_repos__dumpfile_headers_t *headers
1092 = svn_repos__dumpfile_headers_create(pool);
1094 /* Node-path: ... */
1095 svn_repos__dumpfile_header_push(
1096 headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
1098 /* Node-action: delete */
1099 svn_repos__dumpfile_header_push(
1100 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1102 SVN_ERR(svn_repos__dump_headers(stream, headers, pool));
1103 return SVN_NO_ERROR;
1106 /* This helper is the main "meat" of the editor -- it does all the
1107 work of writing a node record.
1109 Write out a node record for PATH of type KIND under EB->FS_ROOT.
1110 ACTION describes what is happening to the node (see enum svn_node_action).
1111 Write record to writable EB->STREAM.
1113 If the node was itself copied, IS_COPY is TRUE and the
1114 path/revision of the copy source are in CMP_PATH/CMP_REV. If
1115 IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
1116 of a copied subtree.
1118 static svn_error_t *
1119 dump_node(struct edit_baton *eb,
1121 svn_node_kind_t kind,
1122 enum svn_node_action action,
1123 svn_boolean_t is_copy,
1124 const char *cmp_path,
1125 svn_revnum_t cmp_rev,
1128 svn_stringbuf_t *propstring;
1130 svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
1131 const char *compare_path = path;
1132 svn_revnum_t compare_rev = eb->current_rev - 1;
1133 svn_fs_root_t *compare_root = NULL;
1134 apr_file_t *delta_file = NULL;
1135 svn_repos__dumpfile_headers_t *headers
1136 = svn_repos__dumpfile_headers_create(pool);
1137 svn_filesize_t textlen;
1139 /* Maybe validate the path. */
1140 if (eb->verify || eb->notify_func)
1142 svn_error_t *err = svn_fs__path_valid(path, pool);
1146 if (eb->notify_func)
1148 char errbuf[512]; /* ### svn_strerror() magic number */
1150 notify_warning(pool, eb->notify_func, eb->notify_baton,
1151 svn_repos_notify_warning_invalid_fspath,
1152 _("E%06d: While validating fspath '%s': %s"),
1154 svn_err_best_message(err, errbuf, sizeof(errbuf)));
1157 /* Return the error in addition to notifying about it. */
1159 return svn_error_trace(err);
1161 svn_error_clear(err);
1165 /* Write out metadata headers for this file node. */
1166 svn_repos__dumpfile_header_push(
1167 headers, SVN_REPOS_DUMPFILE_NODE_PATH, path);
1168 if (kind == svn_node_file)
1169 svn_repos__dumpfile_header_push(
1170 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file");
1171 else if (kind == svn_node_dir)
1172 svn_repos__dumpfile_header_push(
1173 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir");
1175 /* Remove leading slashes from copyfrom paths. */
1178 const char *canonicalized_path;
1179 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL,
1180 cmp_path, pool, pool));
1181 cmp_path = canonicalized_path;
1184 /* Validate the comparison path/rev. */
1185 if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
1187 compare_path = cmp_path;
1188 compare_rev = cmp_rev;
1193 case svn_node_action_change:
1194 if (eb->path_tracker)
1195 SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
1196 apr_psprintf(pool, _("Change invalid path '%s' in r%ld"),
1197 path, eb->current_rev));
1199 svn_repos__dumpfile_header_push(
1200 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change");
1202 /* either the text or props changed, or possibly both. */
1203 SVN_ERR(svn_fs_revision_root(&compare_root,
1204 svn_fs_root_fs(eb->fs_root),
1205 compare_rev, pool));
1207 SVN_ERR(svn_fs_props_changed(&must_dump_props,
1208 compare_root, compare_path,
1209 eb->fs_root, path, pool));
1210 if (kind == svn_node_file)
1211 SVN_ERR(svn_fs_contents_changed(&must_dump_text,
1212 compare_root, compare_path,
1213 eb->fs_root, path, pool));
1216 case svn_node_action_delete:
1217 if (eb->path_tracker)
1219 SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
1220 apr_psprintf(pool, _("Deleting invalid path '%s' in r%ld"),
1221 path, eb->current_rev));
1222 tracker_path_delete(eb->path_tracker, path);
1225 svn_repos__dumpfile_header_push(
1226 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1228 /* we can leave this routine quietly now, don't need to dump
1230 must_dump_text = FALSE;
1231 must_dump_props = FALSE;
1234 case svn_node_action_replace:
1235 if (eb->path_tracker)
1236 SVN_ERR_W(node_must_exist(eb, path, eb->current_rev,
1237 svn_node_unknown, pool),
1239 _("Replacing non-existent path '%s' in r%ld"),
1240 path, eb->current_rev));
1244 if (eb->path_tracker)
1245 tracker_path_replace(eb->path_tracker, path);
1247 /* a simple delete+add, implied by a single 'replace' action. */
1248 svn_repos__dumpfile_header_push(
1249 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace");
1251 /* definitely need to dump all content for a replace. */
1252 if (kind == svn_node_file)
1253 must_dump_text = TRUE;
1254 must_dump_props = TRUE;
1259 /* more complex: delete original, then add-with-history. */
1260 /* ### Why not write a 'replace' record? Don't know. */
1262 if (eb->path_tracker)
1264 tracker_path_delete(eb->path_tracker, path);
1267 /* ### Unusually, we end this 'delete' node record with only a single
1268 blank line after the header block -- no extra blank line. */
1269 SVN_ERR(dump_node_delete(eb->stream, path, pool));
1271 /* The remaining action is a non-replacing add-with-history */
1272 /* action = svn_node_action_add; */
1274 /* FALL THROUGH to 'add' */
1276 case svn_node_action_add:
1277 if (eb->path_tracker)
1278 SVN_ERR_W(node_must_not_exist(eb, path, eb->current_rev, pool),
1280 _("Adding already existing path '%s' in r%ld"),
1281 path, eb->current_rev));
1283 svn_repos__dumpfile_header_push(
1284 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
1288 if (eb->path_tracker)
1289 tracker_path_add(eb->path_tracker, path);
1291 /* Dump all contents for a simple 'add'. */
1292 if (kind == svn_node_file)
1293 must_dump_text = TRUE;
1294 must_dump_props = TRUE;
1298 if (eb->path_tracker)
1300 SVN_ERR_W(node_must_exist(eb, compare_path, compare_rev,
1303 _("Copying from invalid path to "
1305 path, eb->current_rev));
1306 tracker_path_copy(eb->path_tracker, path, compare_path,
1310 if (!eb->verify && cmp_rev < eb->oldest_dumped_rev
1313 notify_warning(pool, eb->notify_func, eb->notify_baton,
1314 svn_repos_notify_warning_found_old_reference,
1315 _("Referencing data in revision %ld,"
1316 " which is older than the oldest"
1317 " dumped revision (r%ld). Loading this dump"
1318 " into an empty repository"
1320 cmp_rev, eb->oldest_dumped_rev);
1321 if (eb->found_old_reference)
1322 *eb->found_old_reference = TRUE;
1325 svn_repos__dumpfile_header_pushf(
1326 headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", cmp_rev);
1327 svn_repos__dumpfile_header_push(
1328 headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, cmp_path);
1330 SVN_ERR(svn_fs_revision_root(&compare_root,
1331 svn_fs_root_fs(eb->fs_root),
1332 compare_rev, pool));
1334 /* Need to decide if the copied node had any extra textual or
1335 property mods as well. */
1336 SVN_ERR(svn_fs_props_changed(&must_dump_props,
1337 compare_root, compare_path,
1338 eb->fs_root, path, pool));
1339 if (kind == svn_node_file)
1341 svn_checksum_t *checksum;
1342 const char *hex_digest;
1343 SVN_ERR(svn_fs_contents_changed(&must_dump_text,
1344 compare_root, compare_path,
1345 eb->fs_root, path, pool));
1347 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1348 compare_root, compare_path,
1350 hex_digest = svn_checksum_to_cstring(checksum, pool);
1352 svn_repos__dumpfile_header_push(
1353 headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5, hex_digest);
1355 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1356 compare_root, compare_path,
1358 hex_digest = svn_checksum_to_cstring(checksum, pool);
1360 svn_repos__dumpfile_header_push(
1361 headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1, hex_digest);
1367 if ((! must_dump_text) && (! must_dump_props))
1369 /* If we're not supposed to dump text or props, so be it, we can
1370 just go home. However, if either one needs to be dumped,
1371 then our dumpstream format demands that at a *minimum*, we
1372 see a lone "PROPS-END" as a divider between text and props
1373 content within the content-block. */
1374 SVN_ERR(svn_repos__dump_headers(eb->stream, headers, pool));
1376 return svn_stream_write(eb->stream, "\n", &len); /* ### needed? */
1379 /*** Start prepping content to dump... ***/
1381 /* If we are supposed to dump properties, write out a property
1382 length header and generate a stringbuf that contains those
1383 property values here. */
1384 if (must_dump_props)
1386 apr_hash_t *prophash, *oldhash = NULL;
1387 svn_stream_t *propstream;
1389 SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
1391 /* If this is a partial dump, then issue a warning if we dump mergeinfo
1392 properties that refer to revisions older than the first revision
1394 if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1)
1396 svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
1397 SVN_PROP_MERGEINFO);
1400 /* An error in verifying the mergeinfo must not prevent dumping
1401 the data. Ignore any such error. */
1402 svn_error_clear(verify_mergeinfo_revisions(
1403 eb->found_old_mergeinfo,
1404 mergeinfo_str->data, eb->oldest_dumped_rev,
1405 eb->notify_func, eb->notify_baton,
1410 /* If we're checking UCS normalization, also parse any changed
1411 mergeinfo and warn about denormalized paths and name
1412 collisions there. */
1413 if (eb->verify && eb->check_normalization && eb->notify_func)
1415 /* N.B.: This hash lookup happens only once; the conditions
1416 for verifying historic mergeinfo references and checking
1417 UCS normalization are mutually exclusive. */
1418 svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
1419 SVN_PROP_MERGEINFO);
1422 svn_string_t *oldinfo_str = NULL;
1425 SVN_ERR(svn_fs_node_proplist(&oldhash,
1426 compare_root, compare_path,
1428 oldinfo_str = svn_hash_gets(oldhash, SVN_PROP_MERGEINFO);
1430 SVN_ERR(check_mergeinfo_normalization(
1431 path, mergeinfo_str->data,
1432 (oldinfo_str ? oldinfo_str->data : NULL),
1433 eb->notify_func, eb->notify_baton, pool));
1437 if (eb->use_deltas && compare_root)
1439 /* Fetch the old property hash to diff against and output a header
1440 saying that our property contents are a delta. */
1441 if (!oldhash) /* May have been set for normalization check */
1442 SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
1444 svn_repos__dumpfile_header_push(
1445 headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true");
1448 oldhash = apr_hash_make(pool);
1449 propstring = svn_stringbuf_create_ensure(0, pool);
1450 propstream = svn_stream_from_stringbuf(propstring, pool);
1451 SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream,
1452 "PROPS-END", pool));
1453 SVN_ERR(svn_stream_close(propstream));
1456 /* If we are supposed to dump text, write out a text length header
1457 here, and an MD5 checksum (if available). */
1458 if (must_dump_text && (kind == svn_node_file))
1460 svn_checksum_t *checksum;
1461 const char *hex_digest;
1465 /* Compute the text delta now and write it into a temporary
1466 file, so that we can find its length. Output a header
1467 saying our text contents are a delta. */
1468 SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
1469 compare_path, eb->fs_root, path, pool));
1470 svn_repos__dumpfile_header_push(
1471 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true");
1475 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1476 compare_root, compare_path,
1478 hex_digest = svn_checksum_to_cstring(checksum, pool);
1480 svn_repos__dumpfile_header_push(
1481 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, hex_digest);
1483 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1484 compare_root, compare_path,
1486 hex_digest = svn_checksum_to_cstring(checksum, pool);
1488 svn_repos__dumpfile_header_push(
1489 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1, hex_digest);
1494 /* Just fetch the length of the file. */
1495 SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
1498 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1499 eb->fs_root, path, FALSE, pool));
1500 hex_digest = svn_checksum_to_cstring(checksum, pool);
1502 svn_repos__dumpfile_header_push(
1503 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, hex_digest);
1505 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1506 eb->fs_root, path, FALSE, pool));
1507 hex_digest = svn_checksum_to_cstring(checksum, pool);
1509 svn_repos__dumpfile_header_push(
1510 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1, hex_digest);
1513 /* 'Content-length:' is the last header before we dump the content,
1514 and is the sum of the text and prop contents lengths. We write
1515 this only for the benefit of non-Subversion RFC-822 parsers. */
1516 SVN_ERR(svn_repos__dump_node_record(eb->stream, headers,
1517 must_dump_props ? propstring : NULL,
1519 must_dump_text ? textlen : 0,
1520 TRUE /*content_length_always*/,
1523 /* Dump text content */
1524 if (must_dump_text && (kind == svn_node_file))
1526 svn_stream_t *contents;
1530 /* Make sure to close the underlying file when the stream is
1532 contents = svn_stream_from_aprfile2(delta_file, FALSE, pool);
1535 SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
1537 SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool),
1542 return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
1546 static svn_error_t *
1547 open_root(void *edit_baton,
1548 svn_revnum_t base_revision,
1552 return svn_error_trace(make_dir_baton((struct dir_baton **)root_baton,
1553 NULL, NULL, SVN_INVALID_REVNUM,
1554 edit_baton, NULL, pool));
1558 static svn_error_t *
1559 delete_entry(const char *path,
1560 svn_revnum_t revision,
1564 struct dir_baton *pb = parent_baton;
1565 const char *mypath = apr_pstrdup(pb->pool, path);
1567 /* remember this path needs to be deleted. */
1568 svn_hash_sets(pb->deleted_entries, mypath, pb);
1570 return SVN_NO_ERROR;
1574 static svn_error_t *
1575 add_directory(const char *path,
1577 const char *copyfrom_path,
1578 svn_revnum_t copyfrom_rev,
1582 struct dir_baton *pb = parent_baton;
1583 struct edit_baton *eb = pb->edit_baton;
1585 svn_boolean_t is_copy = FALSE;
1586 struct dir_baton *new_db;
1588 SVN_ERR(make_dir_baton(&new_db, path, copyfrom_path, copyfrom_rev, eb,
1591 /* This might be a replacement -- is the path already deleted? */
1592 was_deleted = svn_hash_gets(pb->deleted_entries, path);
1594 /* Detect an add-with-history. */
1595 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1597 /* Dump the node. */
1598 SVN_ERR(dump_node(eb, path,
1600 was_deleted ? svn_node_action_replace : svn_node_action_add,
1602 is_copy ? copyfrom_path : NULL,
1603 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1607 /* Delete the path, it's now been dumped. */
1608 svn_hash_sets(pb->deleted_entries, path, NULL);
1610 /* Check for normalized name clashes, but only if this is actually a
1611 new name in the parent, not a replacement. */
1612 if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
1614 pb->check_name_collision = TRUE;
1617 new_db->written_out = TRUE;
1619 *child_baton = new_db;
1620 return SVN_NO_ERROR;
1624 static svn_error_t *
1625 open_directory(const char *path,
1627 svn_revnum_t base_revision,
1631 struct dir_baton *pb = parent_baton;
1632 struct edit_baton *eb = pb->edit_baton;
1633 struct dir_baton *new_db;
1634 const char *cmp_path = NULL;
1635 svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
1637 /* If the parent directory has explicit comparison path and rev,
1638 record the same for this one. */
1639 if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
1641 cmp_path = svn_relpath_join(pb->cmp_path,
1642 svn_relpath_basename(path, pool), pool);
1643 cmp_rev = pb->cmp_rev;
1646 SVN_ERR(make_dir_baton(&new_db, path, cmp_path, cmp_rev, eb, pb, pool));
1647 *child_baton = new_db;
1648 return SVN_NO_ERROR;
1652 static svn_error_t *
1653 close_directory(void *dir_baton,
1656 struct dir_baton *db = dir_baton;
1657 struct edit_baton *eb = db->edit_baton;
1658 apr_pool_t *subpool = svn_pool_create(pool);
1660 apr_array_header_t *sorted_entries;
1662 /* Sort entries lexically instead of as paths. Even though the entries
1663 * are full paths they're all in the same directory (see comment in struct
1664 * dir_baton definition). So we really want to sort by basename, in which
1665 * case the lexical sort function is more efficient. */
1666 sorted_entries = svn_sort__hash(db->deleted_entries,
1667 svn_sort_compare_items_lexically, pool);
1668 for (i = 0; i < sorted_entries->nelts; i++)
1670 const char *path = APR_ARRAY_IDX(sorted_entries, i,
1671 svn_sort__item_t).key;
1673 svn_pool_clear(subpool);
1675 /* By sending 'svn_node_unknown', the Node-kind: header simply won't
1676 be written out. No big deal at all, really. The loader
1678 SVN_ERR(dump_node(eb, path,
1679 svn_node_unknown, svn_node_action_delete,
1680 FALSE, NULL, SVN_INVALID_REVNUM, subpool));
1683 svn_pool_destroy(subpool);
1684 return SVN_NO_ERROR;
1688 static svn_error_t *
1689 add_file(const char *path,
1691 const char *copyfrom_path,
1692 svn_revnum_t copyfrom_rev,
1696 struct dir_baton *pb = parent_baton;
1697 struct edit_baton *eb = pb->edit_baton;
1699 svn_boolean_t is_copy = FALSE;
1701 /* This might be a replacement -- is the path already deleted? */
1702 was_deleted = svn_hash_gets(pb->deleted_entries, path);
1704 /* Detect add-with-history. */
1705 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1707 /* Dump the node. */
1708 SVN_ERR(dump_node(eb, path,
1710 was_deleted ? svn_node_action_replace : svn_node_action_add,
1712 is_copy ? copyfrom_path : NULL,
1713 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1717 /* delete the path, it's now been dumped. */
1718 svn_hash_sets(pb->deleted_entries, path, NULL);
1720 /* Check for normalized name clashes, but only if this is actually a
1721 new name in the parent, not a replacement. */
1722 if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
1724 pb->check_name_collision = TRUE;
1727 *file_baton = NULL; /* muhahahaha */
1728 return SVN_NO_ERROR;
1732 static svn_error_t *
1733 open_file(const char *path,
1735 svn_revnum_t ancestor_revision,
1739 struct dir_baton *pb = parent_baton;
1740 struct edit_baton *eb = pb->edit_baton;
1741 const char *cmp_path = NULL;
1742 svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
1744 /* If the parent directory has explicit comparison path and rev,
1745 record the same for this one. */
1746 if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
1748 cmp_path = svn_relpath_join(pb->cmp_path,
1749 svn_relpath_basename(path, pool), pool);
1750 cmp_rev = pb->cmp_rev;
1753 SVN_ERR(dump_node(eb, path,
1754 svn_node_file, svn_node_action_change,
1755 FALSE, cmp_path, cmp_rev, pool));
1757 *file_baton = NULL; /* muhahahaha again */
1758 return SVN_NO_ERROR;
1762 static svn_error_t *
1763 change_dir_prop(void *parent_baton,
1765 const svn_string_t *value,
1768 struct dir_baton *db = parent_baton;
1769 struct edit_baton *eb = db->edit_baton;
1771 /* This function is what distinguishes between a directory that is
1772 opened to merely get somewhere, vs. one that is opened because it
1773 *actually* changed by itself.
1775 Instead of recording the prop changes here, we just use this method
1776 to trigger writing the node; dump_node() finds all the changes. */
1777 if (! db->written_out)
1779 SVN_ERR(dump_node(eb, db->path,
1780 svn_node_dir, svn_node_action_change,
1781 /* ### We pass is_copy=FALSE; this might be wrong
1782 but the parameter isn't used when action=change. */
1783 FALSE, db->cmp_path, db->cmp_rev, pool));
1784 db->written_out = TRUE;
1786 return SVN_NO_ERROR;
1789 static svn_error_t *
1790 fetch_props_func(apr_hash_t **props,
1793 svn_revnum_t base_revision,
1794 apr_pool_t *result_pool,
1795 apr_pool_t *scratch_pool)
1797 struct edit_baton *eb = baton;
1799 svn_fs_root_t *fs_root;
1801 if (!SVN_IS_VALID_REVNUM(base_revision))
1802 base_revision = eb->current_rev - 1;
1804 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1806 err = svn_fs_node_proplist(props, fs_root, path, result_pool);
1807 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1809 svn_error_clear(err);
1810 *props = apr_hash_make(result_pool);
1811 return SVN_NO_ERROR;
1814 return svn_error_trace(err);
1816 return SVN_NO_ERROR;
1819 static svn_error_t *
1820 fetch_kind_func(svn_node_kind_t *kind,
1823 svn_revnum_t base_revision,
1824 apr_pool_t *scratch_pool)
1826 struct edit_baton *eb = baton;
1827 svn_fs_root_t *fs_root;
1829 if (!SVN_IS_VALID_REVNUM(base_revision))
1830 base_revision = eb->current_rev - 1;
1832 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1834 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
1836 return SVN_NO_ERROR;
1839 static svn_error_t *
1840 fetch_base_func(const char **filename,
1843 svn_revnum_t base_revision,
1844 apr_pool_t *result_pool,
1845 apr_pool_t *scratch_pool)
1847 struct edit_baton *eb = baton;
1848 svn_stream_t *contents;
1849 svn_stream_t *file_stream;
1850 const char *tmp_filename;
1852 svn_fs_root_t *fs_root;
1854 if (!SVN_IS_VALID_REVNUM(base_revision))
1855 base_revision = eb->current_rev - 1;
1857 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1859 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
1860 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1862 svn_error_clear(err);
1864 return SVN_NO_ERROR;
1867 return svn_error_trace(err);
1868 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
1869 svn_io_file_del_on_pool_cleanup,
1870 scratch_pool, scratch_pool));
1871 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
1873 *filename = apr_pstrdup(result_pool, tmp_filename);
1875 return SVN_NO_ERROR;
1879 static svn_error_t *
1880 get_dump_editor(const svn_delta_editor_t **editor,
1883 svn_revnum_t to_rev,
1884 const char *root_path,
1885 svn_stream_t *stream,
1886 svn_boolean_t *found_old_reference,
1887 svn_boolean_t *found_old_mergeinfo,
1888 svn_error_t *(*custom_close_directory)(void *dir_baton,
1889 apr_pool_t *scratch_pool),
1890 svn_repos_notify_func_t notify_func,
1892 svn_revnum_t oldest_dumped_rev,
1893 svn_boolean_t use_deltas,
1894 svn_boolean_t verify,
1895 svn_boolean_t check_normalization,
1898 /* Allocate an edit baton to be stored in every directory baton.
1899 Set it up for the directory baton we create here, which is the
1901 struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1902 svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
1903 svn_delta_shim_callbacks_t *shim_callbacks =
1904 svn_delta_shim_callbacks_default(pool);
1906 /* Set up the edit baton. */
1907 eb->stream = stream;
1908 eb->notify_func = notify_func;
1909 eb->notify_baton = notify_baton;
1910 eb->oldest_dumped_rev = oldest_dumped_rev;
1911 eb->path = apr_pstrdup(pool, root_path);
1912 SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
1914 eb->current_rev = to_rev;
1915 eb->use_deltas = use_deltas;
1916 eb->verify = verify;
1917 eb->check_normalization = check_normalization;
1918 eb->found_old_reference = found_old_reference;
1919 eb->found_old_mergeinfo = found_old_mergeinfo;
1921 /* In non-verification mode, we will allow anything to be dumped because
1922 it might be an incremental dump with possible manual intervention.
1923 Also, this might be the last resort when it comes to data recovery.
1925 Else, make sure that all paths exists at their respective revisions.
1927 eb->path_tracker = verify ? tracker_create(to_rev, pool) : NULL;
1929 /* Set up the editor. */
1930 dump_editor->open_root = open_root;
1931 dump_editor->delete_entry = delete_entry;
1932 dump_editor->add_directory = add_directory;
1933 dump_editor->open_directory = open_directory;
1934 if (custom_close_directory)
1935 dump_editor->close_directory = custom_close_directory;
1937 dump_editor->close_directory = close_directory;
1938 dump_editor->change_dir_prop = change_dir_prop;
1939 dump_editor->add_file = add_file;
1940 dump_editor->open_file = open_file;
1943 *editor = dump_editor;
1945 shim_callbacks->fetch_kind_func = fetch_kind_func;
1946 shim_callbacks->fetch_props_func = fetch_props_func;
1947 shim_callbacks->fetch_base_func = fetch_base_func;
1948 shim_callbacks->fetch_baton = eb;
1950 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1951 NULL, NULL, shim_callbacks, pool, pool));
1953 return SVN_NO_ERROR;
1956 /*----------------------------------------------------------------------*/
1958 /** The main dumping routine, svn_repos_dump_fs. **/
1961 /* Helper for svn_repos_dump_fs.
1963 Write a revision record of REV in REPOS to writable STREAM, using POOL.
1964 Dump revision properties as well if INCLUDE_REVPROPS has been set.
1965 AUTHZ_FUNC and AUTHZ_BATON are passed directly to the repos layer.
1967 static svn_error_t *
1968 write_revision_record(svn_stream_t *stream,
1971 svn_boolean_t include_revprops,
1972 svn_repos_authz_func_t authz_func,
1978 if (include_revprops)
1980 SVN_ERR(svn_repos_fs_revision_proplist(&props, repos, rev,
1981 authz_func, authz_baton, pool));
1985 /* Although we won't use it, we still need this container for the
1987 props = apr_hash_make(pool);
1990 SVN_ERR(svn_repos__dump_revision_record(stream, rev, NULL, props,
1993 return SVN_NO_ERROR;
1996 /* Baton for dump_filter_authz_func(). */
1997 typedef struct dump_filter_baton_t
1999 svn_repos_dump_filter_func_t filter_func;
2001 } dump_filter_baton_t;
2003 /* Implements svn_repos_authz_func_t. */
2004 static svn_error_t *
2005 dump_filter_authz_func(svn_boolean_t *allowed,
2006 svn_fs_root_t *root,
2011 dump_filter_baton_t *b = baton;
2013 /* For some nodes (e.g. files under copied directory) PATH may be
2014 * non-canonical (missing leading '/'). Canonicalize PATH before
2015 * passing it to FILTER_FUNC. */
2016 path = svn_fspath__canonicalize(path, pool);
2018 return svn_error_trace(b->filter_func(allowed, root, path, b->filter_baton,
2024 /* The main dumper. */
2026 svn_repos_dump_fs4(svn_repos_t *repos,
2027 svn_stream_t *stream,
2028 svn_revnum_t start_rev,
2029 svn_revnum_t end_rev,
2030 svn_boolean_t incremental,
2031 svn_boolean_t use_deltas,
2032 svn_boolean_t include_revprops,
2033 svn_boolean_t include_changes,
2034 svn_repos_notify_func_t notify_func,
2036 svn_repos_dump_filter_func_t filter_func,
2038 svn_cancel_func_t cancel_func,
2042 const svn_delta_editor_t *dump_editor;
2043 void *dump_edit_baton = NULL;
2045 svn_fs_t *fs = svn_repos_fs(repos);
2046 apr_pool_t *iterpool = svn_pool_create(pool);
2047 svn_revnum_t youngest;
2050 svn_boolean_t found_old_reference = FALSE;
2051 svn_boolean_t found_old_mergeinfo = FALSE;
2052 svn_repos_notify_t *notify;
2053 svn_repos_authz_func_t authz_func;
2054 dump_filter_baton_t authz_baton = {0};
2056 /* Make sure we catch up on the latest revprop changes. This is the only
2057 * time we will refresh the revprop data in this query. */
2058 SVN_ERR(svn_fs_refresh_revision_props(fs, pool));
2060 /* Determine the current youngest revision of the filesystem. */
2061 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2063 /* Use default vals if necessary. */
2064 if (! SVN_IS_VALID_REVNUM(start_rev))
2066 if (! SVN_IS_VALID_REVNUM(end_rev))
2069 stream = svn_stream_empty(pool);
2071 /* Validate the revisions. */
2072 if (start_rev > end_rev)
2073 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2074 _("Start revision %ld"
2075 " is greater than end revision %ld"),
2076 start_rev, end_rev);
2077 if (end_rev > youngest)
2078 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2079 _("End revision %ld is invalid "
2080 "(youngest revision is %ld)"),
2083 /* We use read authz callback to implement dump filtering. If there is no
2084 * read access for some node, it will be excluded from dump as well as
2085 * references to it (e.g. copy source). */
2088 authz_func = dump_filter_authz_func;
2089 authz_baton.filter_func = filter_func;
2090 authz_baton.filter_baton = filter_baton;
2097 /* Write out the UUID. */
2098 SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
2100 /* If we're not using deltas, use the previous version, for
2101 compatibility with svn 1.0.x. */
2102 version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
2106 /* Write out "general" metadata for the dumpfile. In this case, a
2107 magic header followed by a dumpfile format version. */
2108 SVN_ERR(svn_repos__dump_magic_header_record(stream, version, pool));
2109 SVN_ERR(svn_repos__dump_uuid_header_record(stream, uuid, pool));
2111 /* Create a notify object that we can reuse in the loop. */
2113 notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
2116 /* Main loop: we're going to dump revision REV. */
2117 for (rev = start_rev; rev <= end_rev; rev++)
2119 svn_fs_root_t *to_root;
2120 svn_boolean_t use_deltas_for_rev;
2122 svn_pool_clear(iterpool);
2124 /* Check for cancellation. */
2126 SVN_ERR(cancel_func(cancel_baton));
2128 /* Write the revision record. */
2129 SVN_ERR(write_revision_record(stream, repos, rev, include_revprops,
2130 authz_func, &authz_baton, iterpool));
2132 /* When dumping revision 0, we just write out the revision record.
2133 The parser might want to use its properties.
2134 If we don't want revision changes at all, skip in any case. */
2135 if (rev == 0 || !include_changes)
2138 /* Fetch the editor which dumps nodes to a file. Regardless of
2139 what we've been told, don't use deltas for the first rev of a
2140 non-incremental dump. */
2141 use_deltas_for_rev = use_deltas && (incremental || rev != start_rev);
2142 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, rev,
2143 "", stream, &found_old_reference,
2144 &found_old_mergeinfo, NULL,
2145 notify_func, notify_baton,
2146 start_rev, use_deltas_for_rev, FALSE, FALSE,
2149 /* Drive the editor in one way or another. */
2150 SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool));
2152 /* If this is the first revision of a non-incremental dump,
2153 we're in for a full tree dump. Otherwise, we want to simply
2154 replay the revision. */
2155 if ((rev == start_rev) && (! incremental))
2157 /* Compare against revision 0, so everything appears to be added. */
2158 svn_fs_root_t *from_root;
2159 SVN_ERR(svn_fs_revision_root(&from_root, fs, 0, iterpool));
2160 SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
2162 dump_editor, dump_edit_baton,
2163 authz_func, &authz_baton,
2164 FALSE, /* don't send text-deltas */
2166 FALSE, /* don't send entry props */
2167 FALSE, /* don't ignore ancestry */
2172 /* The normal case: compare consecutive revs. */
2173 SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2174 dump_editor, dump_edit_baton,
2175 authz_func, &authz_baton, iterpool));
2177 /* While our editor close_edit implementation is a no-op, we still
2178 do this for completeness. */
2179 SVN_ERR(dump_editor->close_edit(dump_edit_baton, iterpool));
2185 notify->revision = rev;
2186 notify_func(notify_baton, notify, iterpool);
2192 /* Did we issue any warnings about references to revisions older than
2193 the oldest dumped revision? If so, then issue a final generic
2194 warning, since the inline warnings already issued might easily be
2197 notify = svn_repos_notify_create(svn_repos_notify_dump_end, iterpool);
2198 notify_func(notify_baton, notify, iterpool);
2200 if (found_old_reference)
2202 notify_warning(iterpool, notify_func, notify_baton,
2203 svn_repos_notify_warning_found_old_reference,
2204 _("The range of revisions dumped "
2205 "contained references to "
2206 "copy sources outside that "
2210 /* Ditto if we issued any warnings about old revisions referenced
2211 in dumped mergeinfo. */
2212 if (found_old_mergeinfo)
2214 notify_warning(iterpool, notify_func, notify_baton,
2215 svn_repos_notify_warning_found_old_mergeinfo,
2216 _("The range of revisions dumped "
2217 "contained mergeinfo "
2218 "which reference revisions outside "
2223 svn_pool_destroy(iterpool);
2225 return SVN_NO_ERROR;
2229 /*----------------------------------------------------------------------*/
2231 /* verify, based on dump */
2234 /* Creating a new revision that changes /A/B/E/bravo means creating new
2235 directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
2236 each entry not changed in the new revision a link back to the entry in a
2237 previous revision. svn_repos_replay()ing a revision does not verify that
2238 those links are correct.
2240 For paths actually changed in the revision we verify, we get directory
2241 contents or file length twice: once in the dump editor, and once here.
2242 We could create a new verify baton, store in it the changed paths, and
2243 skip those here, but that means building an entire wrapper editor and
2244 managing two levels of batons. The impact from checking these entries
2245 twice should be minimal, while the code to avoid it is not.
2248 static svn_error_t *
2249 verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
2250 void *val, apr_pool_t *pool)
2252 struct dir_baton *db = baton;
2253 svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
2255 svn_boolean_t right_kind;
2257 path = svn_relpath_join(db->path, (const char *)key, pool);
2259 /* since we can't access the directory entries directly by their ID,
2260 we need to navigate from the FS_ROOT to them (relatively expensive
2261 because we may start at a never rev than the last change to node).
2262 We check that the node kind stored in the noderev matches the dir
2263 entry. This also ensures that all entries point to valid noderevs.
2265 switch (dirent->kind) {
2267 SVN_ERR(svn_fs_is_dir(&right_kind, db->edit_baton->fs_root, path, pool));
2269 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2270 _("Node '%s' is not a directory."),
2275 SVN_ERR(svn_fs_is_file(&right_kind, db->edit_baton->fs_root, path, pool));
2277 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2278 _("Node '%s' is not a file."),
2282 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2283 _("Unexpected node kind %d for '%s'"),
2284 dirent->kind, path);
2287 return SVN_NO_ERROR;
2290 /* Baton used by the check_name_collision hash iterator. */
2291 struct check_name_collision_baton
2293 struct dir_baton *dir_baton;
2294 apr_hash_t *normalized;
2295 svn_membuf_t buffer;
2298 /* Scan the directory and report all entry names that differ only in
2299 Unicode character representation. */
2300 static svn_error_t *
2301 check_name_collision(void *baton, const void *key, apr_ssize_t klen,
2302 void *val, apr_pool_t *iterpool)
2304 struct check_name_collision_baton *const cb = baton;
2308 SVN_ERR(svn_utf__normalize(&name, key, klen, &cb->buffer));
2310 found = svn_hash_gets(cb->normalized, name);
2312 svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2314 else if (found == normalized_collision)
2315 /* Skip already reported collision */;
2318 struct dir_baton *const db = cb->dir_baton;
2319 struct edit_baton *const eb = db->edit_baton;
2320 const char* normpath;
2322 svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2323 normalized_collision);
2325 SVN_ERR(svn_utf__normalize(
2326 &normpath, svn_relpath_join(db->path, name, iterpool),
2327 SVN_UTF__UNKNOWN_LENGTH, &cb->buffer));
2328 notify_warning(iterpool, eb->notify_func, eb->notify_baton,
2329 svn_repos_notify_warning_name_collision,
2330 _("Duplicate representation of path '%s'"), normpath);
2332 return SVN_NO_ERROR;
2336 static svn_error_t *
2337 verify_close_directory(void *dir_baton, apr_pool_t *pool)
2339 struct dir_baton *db = dir_baton;
2340 apr_hash_t *dirents;
2341 SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root,
2343 SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
2346 if (db->check_name_collision)
2348 struct check_name_collision_baton check_baton;
2349 check_baton.dir_baton = db;
2350 check_baton.normalized = apr_hash_make(pool);
2351 svn_membuf__create(&check_baton.buffer, 0, pool);
2352 SVN_ERR(svn_iter_apr_hash(NULL, dirents, check_name_collision,
2353 &check_baton, pool));
2356 return close_directory(dir_baton, pool);
2359 /* Verify revision REV in file system FS. */
2360 static svn_error_t *
2361 verify_one_revision(svn_fs_t *fs,
2363 svn_repos_notify_func_t notify_func,
2365 svn_revnum_t start_rev,
2366 svn_boolean_t check_normalization,
2367 svn_cancel_func_t cancel_func,
2369 apr_pool_t *scratch_pool)
2371 const svn_delta_editor_t *dump_editor;
2372 void *dump_edit_baton;
2373 svn_fs_root_t *to_root;
2375 const svn_delta_editor_t *cancel_editor;
2376 void *cancel_edit_baton;
2378 /* Get cancellable dump editor, but with our close_directory handler.*/
2379 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
2381 svn_stream_empty(scratch_pool),
2383 verify_close_directory,
2384 notify_func, notify_baton,
2386 FALSE, TRUE, /* use_deltas, verify */
2387 check_normalization,
2389 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2390 dump_editor, dump_edit_baton,
2394 SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, scratch_pool));
2395 SVN_ERR(svn_fs_verify_root(to_root, scratch_pool));
2396 SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2397 cancel_editor, cancel_edit_baton,
2398 NULL, NULL, scratch_pool));
2400 /* While our editor close_edit implementation is a no-op, we still
2401 do this for completeness. */
2402 SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, scratch_pool));
2404 SVN_ERR(svn_fs_revision_proplist2(&props, fs, rev, FALSE, scratch_pool,
2407 return SVN_NO_ERROR;
2410 /* Baton type used for forwarding notifications from FS API to REPOS API. */
2411 struct verify_fs_notify_func_baton_t
2413 /* notification function to call (must not be NULL) */
2414 svn_repos_notify_func_t notify_func;
2416 /* baton to use for it */
2419 /* type of notification to send (we will simply plug in the revision) */
2420 svn_repos_notify_t *notify;
2423 /* Forward the notification to BATON. */
2425 verify_fs_notify_func(svn_revnum_t revision,
2429 struct verify_fs_notify_func_baton_t *notify_baton = baton;
2431 notify_baton->notify->revision = revision;
2432 notify_baton->notify_func(notify_baton->notify_baton,
2433 notify_baton->notify, pool);
2436 static svn_error_t *
2437 report_error(svn_revnum_t revision,
2438 svn_error_t *verify_err,
2439 svn_repos_verify_callback_t verify_callback,
2443 if (verify_callback)
2445 svn_error_t *cb_err;
2447 /* The caller provided us with a callback, so make him responsible
2448 for what's going to happen with the error. */
2449 cb_err = verify_callback(verify_baton, revision, verify_err, pool);
2450 svn_error_clear(verify_err);
2453 return SVN_NO_ERROR;
2457 /* No callback -- no second guessing. Just return the error. */
2458 return svn_error_trace(verify_err);
2463 svn_repos_verify_fs3(svn_repos_t *repos,
2464 svn_revnum_t start_rev,
2465 svn_revnum_t end_rev,
2466 svn_boolean_t check_normalization,
2467 svn_boolean_t metadata_only,
2468 svn_repos_notify_func_t notify_func,
2470 svn_repos_verify_callback_t verify_callback,
2472 svn_cancel_func_t cancel_func,
2476 svn_fs_t *fs = svn_repos_fs(repos);
2477 svn_revnum_t youngest;
2479 apr_pool_t *iterpool = svn_pool_create(pool);
2480 svn_repos_notify_t *notify;
2481 svn_fs_progress_notify_func_t verify_notify = NULL;
2482 struct verify_fs_notify_func_baton_t *verify_notify_baton = NULL;
2485 /* Make sure we catch up on the latest revprop changes. This is the only
2486 * time we will refresh the revprop data in this query. */
2487 SVN_ERR(svn_fs_refresh_revision_props(fs, pool));
2489 /* Determine the current youngest revision of the filesystem. */
2490 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2492 /* Use default vals if necessary. */
2493 if (! SVN_IS_VALID_REVNUM(start_rev))
2495 if (! SVN_IS_VALID_REVNUM(end_rev))
2498 /* Validate the revisions. */
2499 if (start_rev > end_rev)
2500 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2501 _("Start revision %ld"
2502 " is greater than end revision %ld"),
2503 start_rev, end_rev);
2504 if (end_rev > youngest)
2505 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2506 _("End revision %ld is invalid "
2507 "(youngest revision is %ld)"),
2510 /* Create a notify object that we can reuse within the loop and a
2511 forwarding structure for notifications from inside svn_fs_verify(). */
2514 notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, pool);
2516 verify_notify = verify_fs_notify_func;
2517 verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
2518 verify_notify_baton->notify_func = notify_func;
2519 verify_notify_baton->notify_baton = notify_baton;
2520 verify_notify_baton->notify
2521 = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool);
2524 /* Verify global metadata and backend-specific data first. */
2525 err = svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
2527 verify_notify, verify_notify_baton,
2528 cancel_func, cancel_baton, pool);
2530 if (err && err->apr_err == SVN_ERR_CANCELLED)
2532 return svn_error_trace(err);
2536 SVN_ERR(report_error(SVN_INVALID_REVNUM, err, verify_callback,
2537 verify_baton, iterpool));
2541 for (rev = start_rev; rev <= end_rev; rev++)
2543 svn_pool_clear(iterpool);
2545 /* Wrapper function to catch the possible errors. */
2546 err = verify_one_revision(fs, rev, notify_func, notify_baton,
2547 start_rev, check_normalization,
2548 cancel_func, cancel_baton,
2551 if (err && err->apr_err == SVN_ERR_CANCELLED)
2553 return svn_error_trace(err);
2557 SVN_ERR(report_error(rev, err, verify_callback, verify_baton,
2560 else if (notify_func)
2562 /* Tell the caller that we're done with this revision. */
2563 notify->revision = rev;
2564 notify_func(notify_baton, notify, iterpool);
2571 notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool);
2572 notify_func(notify_baton, notify, iterpool);
2575 svn_pool_destroy(iterpool);
2577 return SVN_NO_ERROR;