diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
commit | cf46733632c7279a9fd0fe6ce26f9185a4ae82a9 (patch) | |
tree | da27775a2161723ef342e91af41a8b51fedef405 /subversion/libsvn_repos/dump.c | |
parent | bb0ef45f7c46b0ae221b26265ef98a768c33f820 (diff) | |
download | subversion-tarball-master.tar.gz |
subversion-1.9.7HEADsubversion-1.9.7master
Diffstat (limited to 'subversion/libsvn_repos/dump.c')
-rw-r--r-- | subversion/libsvn_repos/dump.c | 1597 |
1 files changed, 1278 insertions, 319 deletions
diff --git a/subversion/libsvn_repos/dump.c b/subversion/libsvn_repos/dump.c index a64b180..189d724 100644 --- a/subversion/libsvn_repos/dump.c +++ b/subversion/libsvn_repos/dump.c @@ -21,6 +21,8 @@ */ +#include <stdarg.h> + #include "svn_private_config.h" #include "svn_pools.h" #include "svn_error.h" @@ -36,14 +38,281 @@ #include "svn_props.h" #include "svn_sorts.h" +#include "private/svn_repos_private.h" #include "private/svn_mergeinfo_private.h" #include "private/svn_fs_private.h" +#include "private/svn_sorts_private.h" +#include "private/svn_utf_private.h" +#include "private/svn_cache.h" #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) /*----------------------------------------------------------------------*/ +/* To be able to check whether a path exists in the current revision + (as changes come in), we need to track the relevant tree changes. + + In particular, we remember deletions, additions and copies including + their copy-from info. Since the dump performs a pre-order tree walk, + we only need to store the data for the stack of parent folders. + + The problem that we are trying to solve is that the dump receives + transforming operations whose validity depends on previous operations + in the same revision but cannot be checked against the final state + as stored in the repository as that is the state *after* we applied + the respective tree changes. + + Note that the tracker functions don't perform any sanity or validity + checks. Those higher-level tests have to be done in the calling code. + However, there is no way to corrupt the data structure using the + provided functions. + */ + +/* Single entry in the path tracker. Not all levels along the path + hierarchy do need to have an instance of this struct but only those + that got changed by a tree modification. + + Please note that the path info in this struct is stored in re-usable + stringbuf objects such that we don't need to allocate more memory than + the longest path we encounter. + */ +typedef struct path_tracker_entry_t +{ + /* path in the current tree */ + svn_stringbuf_t *path; + + /* copy-from path (must be empty if COPYFROM_REV is SVN_INVALID_REVNUM) */ + svn_stringbuf_t *copyfrom_path; + + /* copy-from revision (SVN_INVALID_REVNUM for additions / replacements + that don't copy history, i.e. with no sub-tree) */ + svn_revnum_t copyfrom_rev; + + /* if FALSE, PATH has been deleted */ + svn_boolean_t exists; +} path_tracker_entry_t; + +/* Tracks all tree modifications above the current path. + */ +typedef struct path_tracker_t +{ + /* Container for all relevant tree changes in depth order. + May contain more entries than DEPTH to allow for reusing memory. + Only entries 0 .. DEPTH-1 are valid. + */ + apr_array_header_t *stack; + + /* Number of relevant entries in STACK. May be 0 */ + int depth; + + /* Revision that we current track. If DEPTH is 0, paths are exist in + REVISION exactly when they exist in REVISION-1. This applies only + to the current state of our tree walk. + */ + svn_revnum_t revision; + + /* Allocate container entries here. */ + apr_pool_t *pool; +} path_tracker_t; + +/* Return a new path tracker object for REVISION, allocated in POOL. + */ +static path_tracker_t * +tracker_create(svn_revnum_t revision, + apr_pool_t *pool) +{ + path_tracker_t *result = apr_pcalloc(pool, sizeof(*result)); + result->stack = apr_array_make(pool, 16, sizeof(path_tracker_entry_t)); + result->revision = revision; + result->pool = pool; + + return result; +} + +/* Remove all entries from TRACKER that are not relevant to PATH anymore. + * If ALLOW_EXACT_MATCH is FALSE, keep only entries that pertain to + * parent folders but not to PATH itself. + * + * This internal function implicitly updates the tracker state during the + * tree by removing "past" entries. Other functions will add entries when + * we encounter a new tree change. + */ +static void +tracker_trim(path_tracker_t *tracker, + const char *path, + svn_boolean_t allow_exact_match) +{ + /* remove everything that is unrelated to PATH. + Note that TRACKER->STACK is depth-ordered, + i.e. stack[N] is a (maybe indirect) parent of stack[N+1] + for N+1 < DEPTH. + */ + for (; tracker->depth; --tracker->depth) + { + path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack, + tracker->depth - 1, + path_tracker_entry_t); + const char *rel_path + = svn_dirent_skip_ancestor(parent->path->data, path); + + /* always keep parents. Keep exact matches when allowed. */ + if (rel_path && (allow_exact_match || *rel_path != '\0')) + break; + } +} + +/* Using TRACKER, check what path at what revision in the repository must + be checked to decide that whether PATH exists. Return the info in + *ORIG_PATH and *ORIG_REV, respectively. + + If the path is known to not exist, *ORIG_PATH will be NULL and *ORIG_REV + will be SVN_INVALID_REVNUM. If *ORIG_REV is SVN_INVALID_REVNUM, PATH + has just been added in the revision currently being tracked. + + Use POOL for allocations. Note that *ORIG_PATH may be allocated in POOL, + a reference to internal data with the same lifetime as TRACKER or just + PATH. + */ +static void +tracker_lookup(const char **orig_path, + svn_revnum_t *orig_rev, + path_tracker_t *tracker, + const char *path, + apr_pool_t *pool) +{ + tracker_trim(tracker, path, TRUE); + if (tracker->depth == 0) + { + /* no tree changes -> paths are the same as in the previous rev. */ + *orig_path = path; + *orig_rev = tracker->revision - 1; + } + else + { + path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack, + tracker->depth - 1, + path_tracker_entry_t); + if (parent->exists) + { + const char *rel_path + = svn_dirent_skip_ancestor(parent->path->data, path); + + if (parent->copyfrom_rev != SVN_INVALID_REVNUM) + { + /* parent is a copy with history. Translate path. */ + *orig_path = svn_dirent_join(parent->copyfrom_path->data, + rel_path, pool); + *orig_rev = parent->copyfrom_rev; + } + else if (*rel_path == '\0') + { + /* added in this revision with no history */ + *orig_path = path; + *orig_rev = tracker->revision; + } + else + { + /* parent got added but not this path */ + *orig_path = NULL; + *orig_rev = SVN_INVALID_REVNUM; + } + } + else + { + /* (maybe parent) path has been deleted */ + *orig_path = NULL; + *orig_rev = SVN_INVALID_REVNUM; + } + } +} + +/* Return a reference to the stack entry in TRACKER for PATH. If no + suitable entry exists, add one. Implicitly updates the tracked tree + location. + + Only the PATH member of the result is being updated. All other members + will have undefined values. + */ +static path_tracker_entry_t * +tracker_add_entry(path_tracker_t *tracker, + const char *path) +{ + path_tracker_entry_t *entry; + tracker_trim(tracker, path, FALSE); + + if (tracker->depth == tracker->stack->nelts) + { + entry = apr_array_push(tracker->stack); + entry->path = svn_stringbuf_create_empty(tracker->pool); + entry->copyfrom_path = svn_stringbuf_create_empty(tracker->pool); + } + else + { + entry = &APR_ARRAY_IDX(tracker->stack, tracker->depth, + path_tracker_entry_t); + } + + svn_stringbuf_set(entry->path, path); + ++tracker->depth; + + return entry; +} + +/* Update the TRACKER with a copy from COPYFROM_PATH@COPYFROM_REV to + PATH in the tracked revision. + */ +static void +tracker_path_copy(path_tracker_t *tracker, + const char *path, + const char *copyfrom_path, + svn_revnum_t copyfrom_rev) +{ + path_tracker_entry_t *entry = tracker_add_entry(tracker, path); + + svn_stringbuf_set(entry->copyfrom_path, copyfrom_path); + entry->copyfrom_rev = copyfrom_rev; + entry->exists = TRUE; +} + +/* Update the TRACKER with a plain addition of PATH (without history). + */ +static void +tracker_path_add(path_tracker_t *tracker, + const char *path) +{ + path_tracker_entry_t *entry = tracker_add_entry(tracker, path); + + svn_stringbuf_setempty(entry->copyfrom_path); + entry->copyfrom_rev = SVN_INVALID_REVNUM; + entry->exists = TRUE; +} + +/* Update the TRACKER with a replacement of PATH with a plain addition + (without history). + */ +static void +tracker_path_replace(path_tracker_t *tracker, + const char *path) +{ + /* this will implicitly purge all previous sub-tree info from STACK. + Thus, no need to tack the deletion explicitly. */ + tracker_path_add(tracker, path); +} + +/* Update the TRACKER with a deletion of PATH. + */ +static void +tracker_path_delete(path_tracker_t *tracker, + const char *path) +{ + path_tracker_entry_t *entry = tracker_add_entry(tracker, path); + + svn_stringbuf_setempty(entry->copyfrom_path); + entry->copyfrom_rev = SVN_INVALID_REVNUM; + entry->exists = FALSE; +} + /* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and store it into a new temporary file *TEMPFILE. OLDROOT may be NULL, @@ -84,6 +353,269 @@ store_delta(apr_file_t **tempfile, svn_filesize_t *len, } +/* Send a notification of type #svn_repos_notify_warning, subtype WARNING, + with message WARNING_FMT formatted with the remaining variable arguments. + Send it by calling NOTIFY_FUNC (if not null) with NOTIFY_BATON. + */ +__attribute__((format(printf, 5, 6))) +static void +notify_warning(apr_pool_t *scratch_pool, + svn_repos_notify_func_t notify_func, + void *notify_baton, + svn_repos_notify_warning_t warning, + const char *warning_fmt, + ...) +{ + va_list va; + svn_repos_notify_t *notify; + + if (notify_func == NULL) + return; + + notify = svn_repos_notify_create(svn_repos_notify_warning, scratch_pool); + notify->warning = warning; + va_start(va, warning_fmt); + notify->warning_str = apr_pvsprintf(scratch_pool, warning_fmt, va); + va_end(va); + + notify_func(notify_baton, notify, scratch_pool); +} + + +/*----------------------------------------------------------------------*/ + +/* Write to STREAM the header in HEADERS named KEY, if present. + */ +static svn_error_t * +write_header(svn_stream_t *stream, + apr_hash_t *headers, + const char *key, + apr_pool_t *scratch_pool) +{ + const char *val = svn_hash_gets(headers, key); + + if (val) + { + SVN_ERR(svn_stream_printf(stream, scratch_pool, + "%s: %s\n", key, val)); + } + return SVN_NO_ERROR; +} + +/* Write headers, in arbitrary order. + * ### TODO: use a stable order + * ### Modifies HEADERS. + */ +static svn_error_t * +write_revision_headers(svn_stream_t *stream, + apr_hash_t *headers, + apr_pool_t *scratch_pool) +{ + const char **h; + apr_hash_index_t *hi; + + static const char *revision_headers_order[] = + { + SVN_REPOS_DUMPFILE_REVISION_NUMBER, /* must be first */ + NULL + }; + + /* Write some headers in a given order */ + for (h = revision_headers_order; *h; h++) + { + SVN_ERR(write_header(stream, headers, *h, scratch_pool)); + svn_hash_sets(headers, *h, NULL); + } + + /* Write any and all remaining headers except Content-length. + * ### TODO: use a stable order + */ + for (hi = apr_hash_first(scratch_pool, headers); hi; hi = apr_hash_next(hi)) + { + const char *key = apr_hash_this_key(hi); + + if (strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH) != 0) + SVN_ERR(write_header(stream, headers, key, scratch_pool)); + } + + /* Content-length must be last */ + SVN_ERR(write_header(stream, headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* A header entry: the element type of the apr_array_header_t which is + * the real type of svn_repos__dumpfile_headers_t. + */ +typedef struct svn_repos__dumpfile_header_entry_t { + const char *key, *val; +} svn_repos__dumpfile_header_entry_t; + +svn_repos__dumpfile_headers_t * +svn_repos__dumpfile_headers_create(apr_pool_t *pool) +{ + svn_repos__dumpfile_headers_t *headers + = apr_array_make(pool, 5, sizeof(svn_repos__dumpfile_header_entry_t)); + + return headers; +} + +void +svn_repos__dumpfile_header_push(svn_repos__dumpfile_headers_t *headers, + const char *key, + const char *val) +{ + svn_repos__dumpfile_header_entry_t *h + = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t); + + h->key = apr_pstrdup(headers->pool, key); + h->val = apr_pstrdup(headers->pool, val); +} + +void +svn_repos__dumpfile_header_pushf(svn_repos__dumpfile_headers_t *headers, + const char *key, + const char *val_fmt, + ...) +{ + va_list ap; + svn_repos__dumpfile_header_entry_t *h + = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t); + + h->key = apr_pstrdup(headers->pool, key); + va_start(ap, val_fmt); + h->val = apr_pvsprintf(headers->pool, val_fmt, ap); + va_end(ap); +} + +svn_error_t * +svn_repos__dump_headers(svn_stream_t *stream, + svn_repos__dumpfile_headers_t *headers, + apr_pool_t *scratch_pool) +{ + int i; + + for (i = 0; i < headers->nelts; i++) + { + svn_repos__dumpfile_header_entry_t *h + = &APR_ARRAY_IDX(headers, i, svn_repos__dumpfile_header_entry_t); + + SVN_ERR(svn_stream_printf(stream, scratch_pool, + "%s: %s\n", h->key, h->val)); + } + + /* End of headers */ + SVN_ERR(svn_stream_puts(stream, "\n")); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_repos__dump_revision_record(svn_stream_t *dump_stream, + svn_revnum_t revision, + apr_hash_t *extra_headers, + apr_hash_t *revprops, + svn_boolean_t props_section_always, + apr_pool_t *scratch_pool) +{ + svn_stringbuf_t *propstring = NULL; + apr_hash_t *headers; + + if (extra_headers) + headers = apr_hash_copy(scratch_pool, extra_headers); + else + headers = apr_hash_make(scratch_pool); + + /* ### someday write a revision-content-checksum */ + + svn_hash_sets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER, + apr_psprintf(scratch_pool, "%ld", revision)); + + if (apr_hash_count(revprops) || props_section_always) + { + svn_stream_t *propstream; + + propstring = svn_stringbuf_create_empty(scratch_pool); + propstream = svn_stream_from_stringbuf(propstring, scratch_pool); + SVN_ERR(svn_hash_write2(revprops, propstream, "PROPS-END", scratch_pool)); + SVN_ERR(svn_stream_close(propstream)); + + svn_hash_sets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH, + apr_psprintf(scratch_pool, + "%" APR_SIZE_T_FMT, propstring->len)); + } + + if (propstring) + { + /* Write out a regular Content-length header for the benefit of + non-Subversion RFC-822 parsers. */ + svn_hash_sets(headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH, + apr_psprintf(scratch_pool, + "%" APR_SIZE_T_FMT, propstring->len)); + } + + SVN_ERR(write_revision_headers(dump_stream, headers, scratch_pool)); + + /* End of headers */ + SVN_ERR(svn_stream_puts(dump_stream, "\n")); + + /* Property data. */ + if (propstring) + { + SVN_ERR(svn_stream_write(dump_stream, propstring->data, &propstring->len)); + } + + /* put an end to revision */ + SVN_ERR(svn_stream_puts(dump_stream, "\n")); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_repos__dump_node_record(svn_stream_t *dump_stream, + svn_repos__dumpfile_headers_t *headers, + svn_stringbuf_t *props_str, + svn_boolean_t has_text, + svn_filesize_t text_content_length, + svn_boolean_t content_length_always, + apr_pool_t *scratch_pool) +{ + svn_filesize_t content_length = 0; + + /* add content-length headers */ + if (props_str) + { + svn_repos__dumpfile_header_pushf( + headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH, + "%" APR_SIZE_T_FMT, props_str->len); + content_length += props_str->len; + } + if (has_text) + { + svn_repos__dumpfile_header_pushf( + headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH, + "%" SVN_FILESIZE_T_FMT, text_content_length); + content_length += text_content_length; + } + if (content_length_always || props_str || has_text) + { + svn_repos__dumpfile_header_pushf( + headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH, + "%" SVN_FILESIZE_T_FMT, content_length); + } + + /* write the headers */ + SVN_ERR(svn_repos__dump_headers(dump_stream, headers, scratch_pool)); + + /* write the props */ + if (props_str) + { + SVN_ERR(svn_stream_write(dump_stream, props_str->data, &props_str->len)); + } + return SVN_NO_ERROR; +} + /*----------------------------------------------------------------------*/ /** An editor which dumps node-data in 'dumpfile format' to a file. **/ @@ -116,6 +648,9 @@ struct edit_baton /* True if this "dump" is in fact a verify. */ svn_boolean_t verify; + /* True if checking UCS normalization during a verify. */ + svn_boolean_t check_normalization; + /* The first revision dumped in this dumpstream. */ svn_revnum_t oldest_dumped_rev; @@ -127,18 +662,14 @@ struct edit_baton revisions older than OLDEST_DUMPED_REV. */ svn_boolean_t *found_old_mergeinfo; - /* reusable buffer for writing file contents */ - char buffer[SVN__STREAM_CHUNK_SIZE]; - apr_size_t bufsize; + /* Structure allows us to verify the paths currently being dumped. + If NULL, validity checks are being skipped. */ + path_tracker_t *path_tracker; }; struct dir_baton { struct edit_baton *edit_baton; - struct dir_baton *parent_dir_baton; - - /* is this directory a new addition to this revision? */ - svn_boolean_t added; /* has this directory been written to the output stream? */ svn_boolean_t written_out; @@ -159,6 +690,12 @@ struct dir_baton really, they're all within this directory.) */ apr_hash_t *deleted_entries; + /* A flag indicating that new entries have been added to this + directory in this revision. Used to optimize detection of UCS + representation collisions; we will only check for that in + revisions where new names appear in the directory. */ + svn_boolean_t check_name_collision; + /* pool to be used for deleting the hash items */ apr_pool_t *pool; }; @@ -172,21 +709,19 @@ struct dir_baton path, SVN_INVALID_REVNUM for the rev), just compare this directory PATH against itself in the previous revision. - PARENT_DIR_BATON is the directory baton of this directory's parent, - or NULL if this is the top-level directory of the edit. ADDED - indicated if this directory is newly added in this revision. + PB is the directory baton of this directory's parent, + or NULL if this is the top-level directory of the edit. + Perform all allocations in POOL. */ static struct dir_baton * make_dir_baton(const char *path, const char *cmp_path, svn_revnum_t cmp_rev, void *edit_baton, - void *parent_dir_baton, - svn_boolean_t added, + struct dir_baton *pb, apr_pool_t *pool) { struct edit_baton *eb = edit_baton; - struct dir_baton *pb = parent_dir_baton; struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db)); const char *full_path; @@ -204,18 +739,106 @@ make_dir_baton(const char *path, cmp_path = svn_relpath_canonicalize(cmp_path, pool); new_db->edit_baton = eb; - new_db->parent_dir_baton = pb; new_db->path = full_path; new_db->cmp_path = cmp_path; new_db->cmp_rev = cmp_rev; - new_db->added = added; new_db->written_out = FALSE; new_db->deleted_entries = apr_hash_make(pool); + new_db->check_name_collision = FALSE; new_db->pool = pool; return new_db; } +static svn_error_t * +fetch_kind_func(svn_node_kind_t *kind, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *scratch_pool); + +/* Return an error when PATH in REVISION does not exist or is of a + different kind than EXPECTED_KIND. If the latter is svn_node_unknown, + skip that check. Use EB for context information. If REVISION is the + current revision, use EB's path tracker to follow renames, deletions, + etc. + + Use SCRATCH_POOL for temporary allocations. + No-op if EB's path tracker has not been initialized. + */ +static svn_error_t * +node_must_exist(struct edit_baton *eb, + const char *path, + svn_revnum_t revision, + svn_node_kind_t expected_kind, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t kind = svn_node_none; + + /* in case the caller is trying something stupid ... */ + if (eb->path_tracker == NULL) + return SVN_NO_ERROR; + + /* paths pertaining to the revision currently being processed must + be translated / checked using our path tracker. */ + if (revision == eb->path_tracker->revision) + tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool); + + /* determine the node type (default: no such node) */ + if (path) + SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool)); + + /* check results */ + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, + _("Path '%s' not found in r%ld."), + path, revision); + + if (expected_kind != kind && expected_kind != svn_node_unknown) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Unexpected node kind %d for '%s' at r%ld. " + "Expected kind was %d."), + kind, path, revision, expected_kind); + + return SVN_NO_ERROR; +} + +/* Return an error when PATH exists in REVISION. Use EB for context + information. If REVISION is the current revision, use EB's path + tracker to follow renames, deletions, etc. + + Use SCRATCH_POOL for temporary allocations. + No-op if EB's path tracker has not been initialized. + */ +static svn_error_t * +node_must_not_exist(struct edit_baton *eb, + const char *path, + svn_revnum_t revision, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t kind = svn_node_none; + + /* in case the caller is trying something stupid ... */ + if (eb->path_tracker == NULL) + return SVN_NO_ERROR; + + /* paths pertaining to the revision currently being processed must + be translated / checked using our path tracker. */ + if (revision == eb->path_tracker->revision) + tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool); + + /* determine the node type (default: no such node) */ + if (path) + SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool)); + + /* check results */ + if (kind != svn_node_none) + return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, + _("Path '%s' exists in r%ld."), + path, revision); + + return SVN_NO_ERROR; +} /* If the mergeinfo in MERGEINFO_STR refers to any revisions older than * OLDEST_DUMPED_REV, issue a warning and set *FOUND_OLD_MERGEINFO to TRUE, @@ -239,33 +862,222 @@ verify_mergeinfo_revisions(svn_boolean_t *found_old_mergeinfo, if (apr_hash_count(old_mergeinfo)) { - svn_repos_notify_t *notify = - svn_repos_notify_create(svn_repos_notify_warning, pool); - - notify->warning = svn_repos_notify_warning_found_old_mergeinfo; - notify->warning_str = apr_psprintf( - pool, - _("Mergeinfo referencing revision(s) prior " - "to the oldest dumped revision (r%ld). " - "Loading this dump may result in invalid " - "mergeinfo."), - oldest_dumped_rev); + notify_warning(pool, notify_func, notify_baton, + svn_repos_notify_warning_found_old_mergeinfo, + _("Mergeinfo referencing revision(s) prior " + "to the oldest dumped revision (r%ld). " + "Loading this dump may result in invalid " + "mergeinfo."), + oldest_dumped_rev); if (found_old_mergeinfo) *found_old_mergeinfo = TRUE; - notify_func(notify_baton, notify, pool); } return SVN_NO_ERROR; } +/* Unique string pointers used by verify_mergeinfo_normalization() + and check_name_collision() */ +static const char normalized_unique[] = "normalized_unique"; +static const char normalized_collision[] = "normalized_collision"; + + +/* Baton for extract_mergeinfo_paths */ +struct extract_mergeinfo_paths_baton +{ + apr_hash_t *result; + svn_boolean_t normalize; + svn_membuf_t buffer; +}; + +/* Hash iterator that uniquifies all keys into a single hash table, + optionally normalizing them first. */ +static svn_error_t * +extract_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen, + void *val, apr_pool_t *iterpool) +{ + struct extract_mergeinfo_paths_baton *const xb = baton; + if (xb->normalize) + { + const char *normkey; + SVN_ERR(svn_utf__normalize(&normkey, key, klen, &xb->buffer)); + svn_hash_sets(xb->result, + apr_pstrdup(xb->buffer.pool, normkey), + normalized_unique); + } + else + apr_hash_set(xb->result, + apr_pmemdup(xb->buffer.pool, key, klen + 1), klen, + normalized_unique); + return SVN_NO_ERROR; +} + +/* Baton for filter_mergeinfo_paths */ +struct filter_mergeinfo_paths_baton +{ + apr_hash_t *paths; +}; + +/* Compare two sets of denormalized paths from mergeinfo entries, + removing duplicates. */ +static svn_error_t * +filter_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen, + void *val, apr_pool_t *iterpool) +{ + struct filter_mergeinfo_paths_baton *const fb = baton; + + if (apr_hash_get(fb->paths, key, klen)) + apr_hash_set(fb->paths, key, klen, NULL); + + return SVN_NO_ERROR; +} + +/* Baton used by the check_mergeinfo_normalization hash iterator. */ +struct verify_mergeinfo_normalization_baton +{ + const char* path; + apr_hash_t *normalized_paths; + svn_membuf_t buffer; + svn_repos_notify_func_t notify_func; + void *notify_baton; +}; + +/* Hash iterator that verifies normalization and collision of paths in + an svn:mergeinfo property. */ +static svn_error_t * +verify_mergeinfo_normalization(void *baton, const void *key, apr_ssize_t klen, + void *val, apr_pool_t *iterpool) +{ + struct verify_mergeinfo_normalization_baton *const vb = baton; + + const char *const path = key; + const char *normpath; + const char *found; + + SVN_ERR(svn_utf__normalize(&normpath, path, klen, &vb->buffer)); + found = svn_hash_gets(vb->normalized_paths, normpath); + if (!found) + svn_hash_sets(vb->normalized_paths, + apr_pstrdup(vb->buffer.pool, normpath), + normalized_unique); + else if (found == normalized_collision) + /* Skip already reported collision */; + else + { + /* Report path collision in mergeinfo */ + svn_hash_sets(vb->normalized_paths, + apr_pstrdup(vb->buffer.pool, normpath), + normalized_collision); + + notify_warning(iterpool, vb->notify_func, vb->notify_baton, + svn_repos_notify_warning_mergeinfo_collision, + _("Duplicate representation of path '%s'" + " in %s property of '%s'"), + normpath, SVN_PROP_MERGEINFO, vb->path); + } + return SVN_NO_ERROR; +} + +/* Check UCS normalization of mergeinfo for PATH. NEW_MERGEINFO is the + svn:mergeinfo property value being set; OLD_MERGEINFO is the + previous property value, which may be NULL. Only the paths that + were added in are checked, including collision checks. This + minimizes the number of notifications we generate for a given + mergeinfo property. */ +static svn_error_t * +check_mergeinfo_normalization(const char *path, + const char *new_mergeinfo, + const char *old_mergeinfo, + svn_repos_notify_func_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + svn_mergeinfo_t mergeinfo; + apr_hash_t *normalized_paths; + apr_hash_t *added_paths; + struct extract_mergeinfo_paths_baton extract_baton; + struct verify_mergeinfo_normalization_baton verify_baton; + + SVN_ERR(svn_mergeinfo_parse(&mergeinfo, new_mergeinfo, pool)); + + extract_baton.result = apr_hash_make(pool); + extract_baton.normalize = FALSE; + svn_membuf__create(&extract_baton.buffer, 0, pool); + SVN_ERR(svn_iter_apr_hash(NULL, mergeinfo, + extract_mergeinfo_paths, + &extract_baton, pool)); + added_paths = extract_baton.result; + + if (old_mergeinfo) + { + struct filter_mergeinfo_paths_baton filter_baton; + svn_mergeinfo_t oldinfo; + + extract_baton.result = apr_hash_make(pool); + extract_baton.normalize = TRUE; + SVN_ERR(svn_mergeinfo_parse(&oldinfo, old_mergeinfo, pool)); + SVN_ERR(svn_iter_apr_hash(NULL, oldinfo, + extract_mergeinfo_paths, + &extract_baton, pool)); + normalized_paths = extract_baton.result; + + filter_baton.paths = added_paths; + SVN_ERR(svn_iter_apr_hash(NULL, oldinfo, + filter_mergeinfo_paths, + &filter_baton, pool)); + } + else + normalized_paths = apr_hash_make(pool); + + verify_baton.path = path; + verify_baton.normalized_paths = normalized_paths; + verify_baton.buffer = extract_baton.buffer; + verify_baton.notify_func = notify_func; + verify_baton.notify_baton = notify_baton; + SVN_ERR(svn_iter_apr_hash(NULL, added_paths, + verify_mergeinfo_normalization, + &verify_baton, pool)); + + return SVN_NO_ERROR; +} + + +/* A special case of dump_node(), for a delete record. + * + * The only thing special about this version is it only writes one blank + * line, not two, after the headers. Why? Historical precedent for the + * case where a delete record is used as part of a (delete + add-with-history) + * in implementing a replacement. + * + * Also it doesn't do a path-tracker check. + */ +static svn_error_t * +dump_node_delete(svn_stream_t *stream, + const char *node_relpath, + apr_pool_t *pool) +{ + svn_repos__dumpfile_headers_t *headers + = svn_repos__dumpfile_headers_create(pool); + + /* Node-path: ... */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath); + + /* Node-action: delete */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete"); + + SVN_ERR(svn_repos__dump_headers(stream, headers, pool)); + return SVN_NO_ERROR; +} /* This helper is the main "meat" of the editor -- it does all the work of writing a node record. Write out a node record for PATH of type KIND under EB->FS_ROOT. ACTION describes what is happening to the node (see enum svn_node_action). - Write record to writable EB->STREAM, using EB->BUFFER to write in chunks. + Write record to writable EB->STREAM. If the node was itself copied, IS_COPY is TRUE and the path/revision of the copy source are in CMP_PATH/CMP_REV. If @@ -283,13 +1095,15 @@ dump_node(struct edit_baton *eb, apr_pool_t *pool) { svn_stringbuf_t *propstring; - svn_filesize_t content_length = 0; apr_size_t len; svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE; const char *compare_path = path; svn_revnum_t compare_rev = eb->current_rev - 1; svn_fs_root_t *compare_root = NULL; apr_file_t *delta_file = NULL; + svn_repos__dumpfile_headers_t *headers + = svn_repos__dumpfile_headers_create(pool); + svn_filesize_t textlen; /* Maybe validate the path. */ if (eb->verify || eb->notify_func) @@ -301,17 +1115,12 @@ dump_node(struct edit_baton *eb, if (eb->notify_func) { char errbuf[512]; /* ### svn_strerror() magic number */ - svn_repos_notify_t *notify; - notify = svn_repos_notify_create(svn_repos_notify_warning, pool); - - notify->warning = svn_repos_notify_warning_invalid_fspath; - notify->warning_str = apr_psprintf( - pool, - _("E%06d: While validating fspath '%s': %s"), - err->apr_err, path, - svn_err_best_message(err, errbuf, sizeof(errbuf))); - eb->notify_func(eb->notify_baton, notify, pool); + notify_warning(pool, eb->notify_func, eb->notify_baton, + svn_repos_notify_warning_invalid_fspath, + _("E%06d: While validating fspath '%s': %s"), + err->apr_err, path, + svn_err_best_message(err, errbuf, sizeof(errbuf))); } /* Return the error in addition to notifying about it. */ @@ -323,15 +1132,14 @@ dump_node(struct edit_baton *eb, } /* Write out metadata headers for this file node. */ - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n", - path)); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_PATH, path); if (kind == svn_node_file) - SVN_ERR(svn_stream_puts(eb->stream, - SVN_REPOS_DUMPFILE_NODE_KIND ": file\n")); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file"); else if (kind == svn_node_dir) - SVN_ERR(svn_stream_puts(eb->stream, - SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n")); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir"); /* Remove leading slashes from copyfrom paths. */ if (cmp_path) @@ -344,10 +1152,16 @@ dump_node(struct edit_baton *eb, compare_rev = cmp_rev; } - if (action == svn_node_action_change) + switch (action) { - SVN_ERR(svn_stream_puts(eb->stream, - SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n")); + case svn_node_action_change: + if (eb->path_tracker) + SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool), + apr_psprintf(pool, _("Change invalid path '%s' in r%ld"), + path, eb->current_rev)); + + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change"); /* either the text or props changed, or possibly both. */ SVN_ERR(svn_fs_revision_root(&compare_root, @@ -361,58 +1175,83 @@ dump_node(struct edit_baton *eb, SVN_ERR(svn_fs_contents_changed(&must_dump_text, compare_root, compare_path, eb->fs_root, path, pool)); - } - else if (action == svn_node_action_replace) - { + break; + + case svn_node_action_delete: + if (eb->path_tracker) + { + SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool), + apr_psprintf(pool, _("Deleting invalid path '%s' in r%ld"), + path, eb->current_rev)); + tracker_path_delete(eb->path_tracker, path); + } + + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete"); + + /* we can leave this routine quietly now, don't need to dump + any content. */ + must_dump_text = FALSE; + must_dump_props = FALSE; + break; + + case svn_node_action_replace: + if (eb->path_tracker) + SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, + svn_node_unknown, pool), + apr_psprintf(pool, + _("Replacing non-existent path '%s' in r%ld"), + path, eb->current_rev)); + if (! is_copy) { + if (eb->path_tracker) + tracker_path_replace(eb->path_tracker, path); + /* a simple delete+add, implied by a single 'replace' action. */ - SVN_ERR(svn_stream_puts(eb->stream, - SVN_REPOS_DUMPFILE_NODE_ACTION - ": replace\n")); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace"); /* definitely need to dump all content for a replace. */ if (kind == svn_node_file) must_dump_text = TRUE; must_dump_props = TRUE; + break; } else { /* more complex: delete original, then add-with-history. */ + /* ### Why not write a 'replace' record? Don't know. */ - /* the path & kind headers have already been printed; just - add a delete action, and end the current record.*/ - SVN_ERR(svn_stream_puts(eb->stream, - SVN_REPOS_DUMPFILE_NODE_ACTION - ": delete\n\n")); + if (eb->path_tracker) + { + tracker_path_delete(eb->path_tracker, path); + } - /* recurse: print an additional add-with-history record. */ - SVN_ERR(dump_node(eb, path, kind, svn_node_action_add, - is_copy, compare_path, compare_rev, pool)); + /* ### Unusually, we end this 'delete' node record with only a single + blank line after the header block -- no extra blank line. */ + SVN_ERR(dump_node_delete(eb->stream, path, pool)); - /* we can leave this routine quietly now, don't need to dump - any content; that was already done in the second record. */ - must_dump_text = FALSE; - must_dump_props = FALSE; + /* The remaining action is a non-replacing add-with-history */ + /* action = svn_node_action_add; */ } - } - else if (action == svn_node_action_delete) - { - SVN_ERR(svn_stream_puts(eb->stream, - SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n")); + /* FALL THROUGH to 'add' */ - /* we can leave this routine quietly now, don't need to dump - any content. */ - must_dump_text = FALSE; - must_dump_props = FALSE; - } - else if (action == svn_node_action_add) - { - SVN_ERR(svn_stream_puts(eb->stream, - SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n")); + case svn_node_action_add: + if (eb->path_tracker) + SVN_ERR_W(node_must_not_exist(eb, path, eb->current_rev, pool), + apr_psprintf(pool, + _("Adding already existing path '%s' in r%ld"), + path, eb->current_rev)); + + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add"); if (! is_copy) { + if (eb->path_tracker) + tracker_path_add(eb->path_tracker, path); + /* Dump all contents for a simple 'add'. */ if (kind == svn_node_file) must_dump_text = TRUE; @@ -420,32 +1259,37 @@ dump_node(struct edit_baton *eb, } else { + if (eb->path_tracker) + { + SVN_ERR_W(node_must_exist(eb, compare_path, compare_rev, + kind, pool), + apr_psprintf(pool, + _("Copying from invalid path to " + "'%s' in r%ld"), + path, eb->current_rev)); + tracker_path_copy(eb->path_tracker, path, compare_path, + compare_rev); + } + if (!eb->verify && cmp_rev < eb->oldest_dumped_rev && eb->notify_func) { - svn_repos_notify_t *notify = - svn_repos_notify_create(svn_repos_notify_warning, pool); - - notify->warning = svn_repos_notify_warning_found_old_reference; - notify->warning_str = apr_psprintf( - pool, - _("Referencing data in revision %ld," - " which is older than the oldest" - " dumped revision (r%ld). Loading this dump" - " into an empty repository" - " will fail."), - cmp_rev, eb->oldest_dumped_rev); + notify_warning(pool, eb->notify_func, eb->notify_baton, + svn_repos_notify_warning_found_old_reference, + _("Referencing data in revision %ld," + " which is older than the oldest" + " dumped revision (r%ld). Loading this dump" + " into an empty repository" + " will fail."), + cmp_rev, eb->oldest_dumped_rev); if (eb->found_old_reference) *eb->found_old_reference = TRUE; - eb->notify_func(eb->notify_baton, notify, pool); } - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV - ": %ld\n" - SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH - ": %s\n", - cmp_rev, cmp_path)); + svn_repos__dumpfile_header_pushf( + headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", cmp_rev); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, cmp_path); SVN_ERR(svn_fs_revision_root(&compare_root, svn_fs_root_fs(eb->fs_root), @@ -469,20 +1313,19 @@ dump_node(struct edit_baton *eb, FALSE, pool)); hex_digest = svn_checksum_to_cstring(checksum, pool); if (hex_digest) - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5 - ": %s\n", hex_digest)); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5, hex_digest); SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, compare_root, compare_path, FALSE, pool)); hex_digest = svn_checksum_to_cstring(checksum, pool); if (hex_digest) - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1 - ": %s\n", hex_digest)); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1, hex_digest); } } + break; } if ((! must_dump_text) && (! must_dump_props)) @@ -492,8 +1335,9 @@ dump_node(struct edit_baton *eb, then our dumpstream format demands that at a *minimum*, we see a lone "PROPS-END" as a divider between text and props content within the content-block. */ - len = 2; - return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */ + SVN_ERR(svn_repos__dump_headers(eb->stream, headers, pool)); + len = 1; + return svn_stream_write(eb->stream, "\n", &len); /* ### needed? */ } /*** Start prepping content to dump... ***/ @@ -504,7 +1348,6 @@ dump_node(struct edit_baton *eb, if (must_dump_props) { apr_hash_t *prophash, *oldhash = NULL; - apr_size_t proplen; svn_stream_t *propstream; SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool)); @@ -528,14 +1371,42 @@ dump_node(struct edit_baton *eb, } } + /* If we're checking UCS normalization, also parse any changed + mergeinfo and warn about denormalized paths and name + collisions there. */ + if (eb->verify && eb->check_normalization && eb->notify_func) + { + /* N.B.: This hash lookup happens only once; the conditions + for verifying historic mergeinfo references and checking + UCS normalization are mutually exclusive. */ + svn_string_t *mergeinfo_str = svn_hash_gets(prophash, + SVN_PROP_MERGEINFO); + if (mergeinfo_str) + { + svn_string_t *oldinfo_str = NULL; + if (compare_root) + { + SVN_ERR(svn_fs_node_proplist(&oldhash, + compare_root, compare_path, + pool)); + oldinfo_str = svn_hash_gets(oldhash, SVN_PROP_MERGEINFO); + } + SVN_ERR(check_mergeinfo_normalization( + path, mergeinfo_str->data, + (oldinfo_str ? oldinfo_str->data : NULL), + eb->notify_func, eb->notify_baton, pool)); + } + } + if (eb->use_deltas && compare_root) { /* Fetch the old property hash to diff against and output a header saying that our property contents are a delta. */ - SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path, - pool)); - SVN_ERR(svn_stream_puts(eb->stream, - SVN_REPOS_DUMPFILE_PROP_DELTA ": true\n")); + if (!oldhash) /* May have been set for normalization check */ + SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path, + pool)); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true"); } else oldhash = apr_hash_make(pool); @@ -544,11 +1415,6 @@ dump_node(struct edit_baton *eb, SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream, "PROPS-END", pool)); SVN_ERR(svn_stream_close(propstream)); - proplen = propstring->len; - content_length += proplen; - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH - ": %" APR_SIZE_T_FMT "\n", proplen)); } /* If we are supposed to dump text, write out a text length header @@ -557,7 +1423,6 @@ dump_node(struct edit_baton *eb, { svn_checksum_t *checksum; const char *hex_digest; - svn_filesize_t textlen; if (eb->use_deltas) { @@ -566,8 +1431,8 @@ dump_node(struct edit_baton *eb, saying our text contents are a delta. */ SVN_ERR(store_delta(&delta_file, &textlen, compare_root, compare_path, eb->fs_root, path, pool)); - SVN_ERR(svn_stream_puts(eb->stream, - SVN_REPOS_DUMPFILE_TEXT_DELTA ": true\n")); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true"); if (compare_root) { @@ -576,18 +1441,16 @@ dump_node(struct edit_baton *eb, FALSE, pool)); hex_digest = svn_checksum_to_cstring(checksum, pool); if (hex_digest) - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5 - ": %s\n", hex_digest)); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, hex_digest); SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, compare_root, compare_path, FALSE, pool)); hex_digest = svn_checksum_to_cstring(checksum, pool); if (hex_digest) - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1 - ": %s\n", hex_digest)); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1, hex_digest); } } else @@ -596,42 +1459,30 @@ dump_node(struct edit_baton *eb, SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool)); } - content_length += textlen; - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH - ": %" SVN_FILESIZE_T_FMT "\n", textlen)); - SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, eb->fs_root, path, FALSE, pool)); hex_digest = svn_checksum_to_cstring(checksum, pool); if (hex_digest) - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5 - ": %s\n", hex_digest)); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, hex_digest); SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, eb->fs_root, path, FALSE, pool)); hex_digest = svn_checksum_to_cstring(checksum, pool); if (hex_digest) - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1 - ": %s\n", hex_digest)); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1, hex_digest); } /* 'Content-length:' is the last header before we dump the content, and is the sum of the text and prop contents lengths. We write this only for the benefit of non-Subversion RFC-822 parsers. */ - SVN_ERR(svn_stream_printf(eb->stream, pool, - SVN_REPOS_DUMPFILE_CONTENT_LENGTH - ": %" SVN_FILESIZE_T_FMT "\n\n", - content_length)); - - /* Dump property content if we're supposed to do so. */ - if (must_dump_props) - { - len = propstring->len; - SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len)); - } + SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, + must_dump_props ? propstring : NULL, + must_dump_text, + must_dump_text ? textlen : 0, + TRUE /*content_length_always*/, + pool)); /* Dump text content */ if (must_dump_text && (kind == svn_node_file)) @@ -663,7 +1514,7 @@ open_root(void *edit_baton, void **root_baton) { *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, - edit_baton, NULL, FALSE, pool); + edit_baton, NULL, pool); return SVN_NO_ERROR; } @@ -694,13 +1545,13 @@ add_directory(const char *path, { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; - void *val; + void *was_deleted; svn_boolean_t is_copy = FALSE; struct dir_baton *new_db - = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool); + = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, pool); /* This might be a replacement -- is the path already deleted? */ - val = svn_hash_gets(pb->deleted_entries, path); + was_deleted = svn_hash_gets(pb->deleted_entries, path); /* Detect an add-with-history. */ is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); @@ -708,16 +1559,23 @@ add_directory(const char *path, /* Dump the node. */ SVN_ERR(dump_node(eb, path, svn_node_dir, - val ? svn_node_action_replace : svn_node_action_add, + was_deleted ? svn_node_action_replace : svn_node_action_add, is_copy, is_copy ? copyfrom_path : NULL, is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, pool)); - if (val) + if (was_deleted) /* Delete the path, it's now been dumped. */ svn_hash_sets(pb->deleted_entries, path, NULL); + /* Check for normalized name clashes, but only if this is actually a + new name in the parent, not a replacement. */ + if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func) + { + pb->check_name_collision = TRUE; + } + new_db->written_out = TRUE; *child_baton = new_db; @@ -747,7 +1605,7 @@ open_directory(const char *path, cmp_rev = pb->cmp_rev; } - new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, FALSE, pool); + new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, pool); *child_baton = new_db; return SVN_NO_ERROR; } @@ -799,11 +1657,11 @@ add_file(const char *path, { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; - void *val; + void *was_deleted; svn_boolean_t is_copy = FALSE; /* This might be a replacement -- is the path already deleted? */ - val = svn_hash_gets(pb->deleted_entries, path); + was_deleted = svn_hash_gets(pb->deleted_entries, path); /* Detect add-with-history. */ is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); @@ -811,16 +1669,23 @@ add_file(const char *path, /* Dump the node. */ SVN_ERR(dump_node(eb, path, svn_node_file, - val ? svn_node_action_replace : svn_node_action_add, + was_deleted ? svn_node_action_replace : svn_node_action_add, is_copy, is_copy ? copyfrom_path : NULL, is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, pool)); - if (val) + if (was_deleted) /* delete the path, it's now been dumped. */ svn_hash_sets(pb->deleted_entries, path, NULL); + /* Check for normalized name clashes, but only if this is actually a + new name in the parent, not a replacement. */ + if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func) + { + pb->check_name_collision = TRUE; + } + *file_baton = NULL; /* muhahahaha */ return SVN_NO_ERROR; } @@ -867,11 +1732,16 @@ change_dir_prop(void *parent_baton, /* This function is what distinguishes between a directory that is opened to merely get somewhere, vs. one that is opened because it - *actually* changed by itself. */ + *actually* changed by itself. + + Instead of recording the prop changes here, we just use this method + to trigger writing the node; dump_node() finds all the changes. */ if (! db->written_out) { SVN_ERR(dump_node(eb, db->path, svn_node_dir, svn_node_action_change, + /* ### We pass is_copy=FALSE; this might be wrong + but the parameter isn't used when action=change. */ FALSE, db->cmp_path, db->cmp_rev, pool)); db->written_out = TRUE; } @@ -984,6 +1854,7 @@ get_dump_editor(const svn_delta_editor_t **editor, svn_revnum_t oldest_dumped_rev, svn_boolean_t use_deltas, svn_boolean_t verify, + svn_boolean_t check_normalization, apr_pool_t *pool) { /* Allocate an edit baton to be stored in every directory baton. @@ -999,16 +1870,24 @@ get_dump_editor(const svn_delta_editor_t **editor, eb->notify_func = notify_func; eb->notify_baton = notify_baton; eb->oldest_dumped_rev = oldest_dumped_rev; - eb->bufsize = sizeof(eb->buffer); eb->path = apr_pstrdup(pool, root_path); SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool)); eb->fs = fs; eb->current_rev = to_rev; eb->use_deltas = use_deltas; eb->verify = verify; + eb->check_normalization = check_normalization; eb->found_old_reference = found_old_reference; eb->found_old_mergeinfo = found_old_mergeinfo; + /* In non-verification mode, we will allow anything to be dumped because + it might be an incremental dump with possible manual intervention. + Also, this might be the last resort when it comes to data recovery. + + Else, make sure that all paths exists at their respective revisions. + */ + eb->path_tracker = verify ? tracker_create(to_rev, pool) : NULL; + /* Set up the editor. */ dump_editor->open_root = open_root; dump_editor->delete_entry = delete_entry; @@ -1051,15 +1930,10 @@ write_revision_record(svn_stream_t *stream, svn_revnum_t rev, apr_pool_t *pool) { - apr_size_t len; apr_hash_t *props; - svn_stringbuf_t *encoded_prophash; apr_time_t timetemp; svn_string_t *datevalue; - svn_stream_t *propstream; - /* Read the revision props even if we're aren't going to dump - them for verification purposes */ SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool)); /* Run revision date properties through the time conversion to @@ -1074,33 +1948,10 @@ write_revision_record(svn_stream_t *stream, svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue); } - encoded_prophash = svn_stringbuf_create_ensure(0, pool); - propstream = svn_stream_from_stringbuf(encoded_prophash, pool); - SVN_ERR(svn_hash_write2(props, propstream, "PROPS-END", pool)); - SVN_ERR(svn_stream_close(propstream)); - - /* ### someday write a revision-content-checksum */ - - SVN_ERR(svn_stream_printf(stream, pool, - SVN_REPOS_DUMPFILE_REVISION_NUMBER - ": %ld\n", rev)); - SVN_ERR(svn_stream_printf(stream, pool, - SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH - ": %" APR_SIZE_T_FMT "\n", - encoded_prophash->len)); - - /* Write out a regular Content-length header for the benefit of - non-Subversion RFC-822 parsers. */ - SVN_ERR(svn_stream_printf(stream, pool, - SVN_REPOS_DUMPFILE_CONTENT_LENGTH - ": %" APR_SIZE_T_FMT "\n\n", - encoded_prophash->len)); - - len = encoded_prophash->len; - SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len)); - - len = 1; - return svn_stream_write(stream, "\n", &len); + SVN_ERR(svn_repos__dump_revision_record(stream, rev, NULL, props, + TRUE /*props_section_always*/, + pool)); + return SVN_NO_ERROR; } @@ -1121,7 +1972,7 @@ svn_repos_dump_fs3(svn_repos_t *repos, { const svn_delta_editor_t *dump_editor; void *dump_edit_baton = NULL; - svn_revnum_t i; + svn_revnum_t rev; svn_fs_t *fs = svn_repos_fs(repos); apr_pool_t *subpool = svn_pool_create(pool); svn_revnum_t youngest; @@ -1153,10 +2004,6 @@ svn_repos_dump_fs3(svn_repos_t *repos, _("End revision %ld is invalid " "(youngest revision is %ld)"), end_rev, youngest); - if ((start_rev == 0) && incremental) - incremental = FALSE; /* revision 0 looks the same regardless of - whether or not this is an incremental - dump, so just simplify things. */ /* Write out the UUID. */ SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool)); @@ -1180,10 +2027,9 @@ svn_repos_dump_fs3(svn_repos_t *repos, notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end, pool); - /* Main loop: we're going to dump revision i. */ - for (i = start_rev; i <= end_rev; i++) + /* Main loop: we're going to dump revision REV. */ + for (rev = start_rev; rev <= end_rev; rev++) { - svn_revnum_t from_rev, to_rev; svn_fs_root_t *to_root; svn_boolean_t use_deltas_for_rev; @@ -1193,56 +2039,36 @@ svn_repos_dump_fs3(svn_repos_t *repos, if (cancel_func) SVN_ERR(cancel_func(cancel_baton)); - /* Special-case the initial revision dump: it needs to contain - *all* nodes, because it's the foundation of all future - revisions in the dumpfile. */ - if ((i == start_rev) && (! incremental)) - { - /* Special-special-case a dump of revision 0. */ - if (i == 0) - { - /* Just write out the one revision 0 record and move on. - The parser might want to use its properties. */ - SVN_ERR(write_revision_record(stream, fs, 0, subpool)); - to_rev = 0; - goto loop_end; - } - - /* Compare START_REV to revision 0, so that everything - appears to be added. */ - from_rev = 0; - to_rev = i; - } - else - { - /* In the normal case, we want to compare consecutive revs. */ - from_rev = i - 1; - to_rev = i; - } - /* Write the revision record. */ - SVN_ERR(write_revision_record(stream, fs, to_rev, subpool)); + SVN_ERR(write_revision_record(stream, fs, rev, subpool)); + + /* When dumping revision 0, we just write out the revision record. + The parser might want to use its properties. */ + if (rev == 0) + goto loop_end; /* Fetch the editor which dumps nodes to a file. Regardless of what we've been told, don't use deltas for the first rev of a non-incremental dump. */ - use_deltas_for_rev = use_deltas && (incremental || i != start_rev); - SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, to_rev, + use_deltas_for_rev = use_deltas && (incremental || rev != start_rev); + SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, rev, "", stream, &found_old_reference, &found_old_mergeinfo, NULL, notify_func, notify_baton, - start_rev, use_deltas_for_rev, FALSE, subpool)); + start_rev, use_deltas_for_rev, FALSE, FALSE, + subpool)); /* Drive the editor in one way or another. */ - SVN_ERR(svn_fs_revision_root(&to_root, fs, to_rev, subpool)); + SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, subpool)); /* If this is the first revision of a non-incremental dump, we're in for a full tree dump. Otherwise, we want to simply replay the revision. */ - if ((i == start_rev) && (! incremental)) + if ((rev == start_rev) && (! incremental)) { + /* Compare against revision 0, so everything appears to be added. */ svn_fs_root_t *from_root; - SVN_ERR(svn_fs_revision_root(&from_root, fs, from_rev, subpool)); + SVN_ERR(svn_fs_revision_root(&from_root, fs, 0, subpool)); SVN_ERR(svn_repos_dir_delta2(from_root, "", "", to_root, "", dump_editor, dump_edit_baton, @@ -1256,6 +2082,7 @@ svn_repos_dump_fs3(svn_repos_t *repos, } else { + /* The normal case: compare consecutive revs. */ SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, dump_editor, dump_edit_baton, NULL, NULL, subpool)); @@ -1268,7 +2095,7 @@ svn_repos_dump_fs3(svn_repos_t *repos, loop_end: if (notify_func) { - notify->revision = to_rev; + notify->revision = rev; notify_func(notify_baton, notify, subpool); } } @@ -1285,28 +2112,24 @@ svn_repos_dump_fs3(svn_repos_t *repos, if (found_old_reference) { - notify = svn_repos_notify_create(svn_repos_notify_warning, subpool); - - notify->warning = svn_repos_notify_warning_found_old_reference; - notify->warning_str = _("The range of revisions dumped " - "contained references to " - "copy sources outside that " - "range."); - notify_func(notify_baton, notify, subpool); + notify_warning(subpool, notify_func, notify_baton, + svn_repos_notify_warning_found_old_reference, + _("The range of revisions dumped " + "contained references to " + "copy sources outside that " + "range.")); } /* Ditto if we issued any warnings about old revisions referenced in dumped mergeinfo. */ if (found_old_mergeinfo) { - notify = svn_repos_notify_create(svn_repos_notify_warning, subpool); - - notify->warning = svn_repos_notify_warning_found_old_mergeinfo; - notify->warning_str = _("The range of revisions dumped " - "contained mergeinfo " - "which reference revisions outside " - "that range."); - notify_func(notify_baton, notify, subpool); + notify_warning(subpool, notify_func, notify_baton, + svn_repos_notify_warning_found_old_mergeinfo, + _("The range of revisions dumped " + "contained mergeinfo " + "which reference revisions outside " + "that range.")); } } @@ -1341,23 +2164,32 @@ verify_directory_entry(void *baton, const void *key, apr_ssize_t klen, { struct dir_baton *db = baton; svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val; - char *path = svn_relpath_join(db->path, (const char *)key, pool); - apr_hash_t *dirents; - svn_filesize_t len; + char *path; + svn_boolean_t right_kind; + + path = svn_relpath_join(db->path, (const char *)key, pool); /* since we can't access the directory entries directly by their ID, we need to navigate from the FS_ROOT to them (relatively expensive - because we may start at a never rev than the last change to node). */ + because we may start at a never rev than the last change to node). + We check that the node kind stored in the noderev matches the dir + entry. This also ensures that all entries point to valid noderevs. + */ switch (dirent->kind) { case svn_node_dir: - /* Getting this directory's contents is enough to ensure that our - link to it is correct. */ - SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, path, pool)); + SVN_ERR(svn_fs_is_dir(&right_kind, db->edit_baton->fs_root, path, pool)); + if (!right_kind) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Node '%s' is not a directory."), + path); + break; case svn_node_file: - /* Getting this file's size is enough to ensure that our link to it - is correct. */ - SVN_ERR(svn_fs_file_length(&len, db->edit_baton->fs_root, path, pool)); + SVN_ERR(svn_fs_is_file(&right_kind, db->edit_baton->fs_root, path, pool)); + if (!right_kind) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Node '%s' is not a file."), + path); break; default: return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, @@ -1368,9 +2200,54 @@ verify_directory_entry(void *baton, const void *key, apr_ssize_t klen, return SVN_NO_ERROR; } +/* Baton used by the check_name_collision hash iterator. */ +struct check_name_collision_baton +{ + struct dir_baton *dir_baton; + apr_hash_t *normalized; + svn_membuf_t buffer; +}; + +/* Scan the directory and report all entry names that differ only in + Unicode character representation. */ static svn_error_t * -verify_close_directory(void *dir_baton, - apr_pool_t *pool) +check_name_collision(void *baton, const void *key, apr_ssize_t klen, + void *val, apr_pool_t *iterpool) +{ + struct check_name_collision_baton *const cb = baton; + const char *name; + const char *found; + + SVN_ERR(svn_utf__normalize(&name, key, klen, &cb->buffer)); + + found = svn_hash_gets(cb->normalized, name); + if (!found) + svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name), + normalized_unique); + else if (found == normalized_collision) + /* Skip already reported collision */; + else + { + struct dir_baton *const db = cb->dir_baton; + struct edit_baton *const eb = db->edit_baton; + const char* normpath; + + svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name), + normalized_collision); + + SVN_ERR(svn_utf__normalize( + &normpath, svn_relpath_join(db->path, name, iterpool), + SVN_UTF__UNKNOWN_LENGTH, &cb->buffer)); + notify_warning(iterpool, eb->notify_func, eb->notify_baton, + svn_repos_notify_warning_name_collision, + _("Duplicate representation of path '%s'"), normpath); + } + return SVN_NO_ERROR; +} + + +static svn_error_t * +verify_close_directory(void *dir_baton, apr_pool_t *pool) { struct dir_baton *db = dir_baton; apr_hash_t *dirents; @@ -1378,11 +2255,72 @@ verify_close_directory(void *dir_baton, db->path, pool)); SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry, dir_baton, pool)); + + if (db->check_name_collision) + { + struct check_name_collision_baton check_baton; + check_baton.dir_baton = db; + check_baton.normalized = apr_hash_make(pool); + svn_membuf__create(&check_baton.buffer, 0, pool); + SVN_ERR(svn_iter_apr_hash(NULL, dirents, check_name_collision, + &check_baton, pool)); + } + return close_directory(dir_baton, pool); } +/* Verify revision REV in file system FS. */ +static svn_error_t * +verify_one_revision(svn_fs_t *fs, + svn_revnum_t rev, + svn_repos_notify_func_t notify_func, + void *notify_baton, + svn_revnum_t start_rev, + svn_boolean_t check_normalization, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_delta_editor_t *dump_editor; + void *dump_edit_baton; + svn_fs_root_t *to_root; + apr_hash_t *props; + const svn_delta_editor_t *cancel_editor; + void *cancel_edit_baton; + + /* Get cancellable dump editor, but with our close_directory handler.*/ + SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, + fs, rev, "", + svn_stream_empty(scratch_pool), + NULL, NULL, + verify_close_directory, + notify_func, notify_baton, + start_rev, + FALSE, TRUE, /* use_deltas, verify */ + check_normalization, + scratch_pool)); + SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, + dump_editor, dump_edit_baton, + &cancel_editor, + &cancel_edit_baton, + scratch_pool)); + SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, scratch_pool)); + SVN_ERR(svn_fs_verify_root(to_root, scratch_pool)); + SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, + cancel_editor, cancel_edit_baton, + NULL, NULL, scratch_pool)); + + /* While our editor close_edit implementation is a no-op, we still + do this for completeness. */ + SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, scratch_pool)); + + SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, scratch_pool)); + + return SVN_NO_ERROR; +} + /* Baton type used for forwarding notifications from FS API to REPOS API. */ -struct verify_fs2_notify_func_baton_t +struct verify_fs_notify_func_baton_t { /* notification function to call (must not be NULL) */ svn_repos_notify_func_t notify_func; @@ -1396,23 +2334,53 @@ struct verify_fs2_notify_func_baton_t /* Forward the notification to BATON. */ static void -verify_fs2_notify_func(svn_revnum_t revision, +verify_fs_notify_func(svn_revnum_t revision, void *baton, apr_pool_t *pool) { - struct verify_fs2_notify_func_baton_t *notify_baton = baton; + struct verify_fs_notify_func_baton_t *notify_baton = baton; notify_baton->notify->revision = revision; notify_baton->notify_func(notify_baton->notify_baton, notify_baton->notify, pool); } +static svn_error_t * +report_error(svn_revnum_t revision, + svn_error_t *verify_err, + svn_repos_verify_callback_t verify_callback, + void *verify_baton, + apr_pool_t *pool) +{ + if (verify_callback) + { + svn_error_t *cb_err; + + /* The caller provided us with a callback, so make him responsible + for what's going to happen with the error. */ + cb_err = verify_callback(verify_baton, revision, verify_err, pool); + svn_error_clear(verify_err); + SVN_ERR(cb_err); + + return SVN_NO_ERROR; + } + else + { + /* No callback -- no second guessing. Just return the error. */ + return svn_error_trace(verify_err); + } +} + svn_error_t * -svn_repos_verify_fs2(svn_repos_t *repos, +svn_repos_verify_fs3(svn_repos_t *repos, svn_revnum_t start_rev, svn_revnum_t end_rev, + svn_boolean_t check_normalization, + svn_boolean_t metadata_only, svn_repos_notify_func_t notify_func, void *notify_baton, + svn_repos_verify_callback_t verify_callback, + void *verify_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool) @@ -1423,7 +2391,8 @@ svn_repos_verify_fs2(svn_repos_t *repos, apr_pool_t *iterpool = svn_pool_create(pool); svn_repos_notify_t *notify; svn_fs_progress_notify_func_t verify_notify = NULL; - struct verify_fs2_notify_func_baton_t *verify_notify_baton = NULL; + struct verify_fs_notify_func_baton_t *verify_notify_baton = NULL; + svn_error_t *err; /* Determine the current youngest revision of the filesystem. */ SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); @@ -1450,10 +2419,9 @@ svn_repos_verify_fs2(svn_repos_t *repos, forwarding structure for notifications from inside svn_fs_verify(). */ if (notify_func) { - notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, - pool); + notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, pool); - verify_notify = verify_fs2_notify_func; + verify_notify = verify_fs_notify_func; verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton)); verify_notify_baton->notify_func = notify_func; verify_notify_baton->notify_baton = notify_baton; @@ -1462,56 +2430,48 @@ svn_repos_verify_fs2(svn_repos_t *repos, } /* Verify global metadata and backend-specific data first. */ - SVN_ERR(svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool), - start_rev, end_rev, - verify_notify, verify_notify_baton, - cancel_func, cancel_baton, pool)); + err = svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool), + start_rev, end_rev, + verify_notify, verify_notify_baton, + cancel_func, cancel_baton, pool); - for (rev = start_rev; rev <= end_rev; rev++) + if (err && err->apr_err == SVN_ERR_CANCELLED) { - const svn_delta_editor_t *dump_editor; - void *dump_edit_baton; - const svn_delta_editor_t *cancel_editor; - void *cancel_edit_baton; - svn_fs_root_t *to_root; - apr_hash_t *props; + return svn_error_trace(err); + } + else if (err) + { + SVN_ERR(report_error(SVN_INVALID_REVNUM, err, verify_callback, + verify_baton, iterpool)); + } - svn_pool_clear(iterpool); + if (!metadata_only) + for (rev = start_rev; rev <= end_rev; rev++) + { + svn_pool_clear(iterpool); - /* Get cancellable dump editor, but with our close_directory handler. */ - SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, - fs, rev, "", - svn_stream_empty(iterpool), - NULL, NULL, - verify_close_directory, - notify_func, notify_baton, - start_rev, - FALSE, TRUE, /* use_deltas, verify */ - iterpool)); - SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, - dump_editor, dump_edit_baton, - &cancel_editor, - &cancel_edit_baton, - iterpool)); - - SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool)); - SVN_ERR(svn_fs_verify_root(to_root, iterpool)); - - SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, - cancel_editor, cancel_edit_baton, - NULL, NULL, iterpool)); - /* While our editor close_edit implementation is a no-op, we still - do this for completeness. */ - SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, iterpool)); - - SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, iterpool)); + /* Wrapper function to catch the possible errors. */ + err = verify_one_revision(fs, rev, notify_func, notify_baton, + start_rev, check_normalization, + cancel_func, cancel_baton, + iterpool); - if (notify_func) - { - notify->revision = rev; - notify_func(notify_baton, notify, iterpool); - } - } + if (err && err->apr_err == SVN_ERR_CANCELLED) + { + return svn_error_trace(err); + } + else if (err) + { + SVN_ERR(report_error(rev, err, verify_callback, verify_baton, + iterpool)); + } + else if (notify_func) + { + /* Tell the caller that we're done with this revision. */ + notify->revision = rev; + notify_func(notify_baton, notify, iterpool); + } + } /* We're done. */ if (notify_func) @@ -1520,7 +2480,6 @@ svn_repos_verify_fs2(svn_repos_t *repos, notify_func(notify_baton, notify, iterpool); } - /* Per-backend verification. */ svn_pool_destroy(iterpool); return SVN_NO_ERROR; |