summaryrefslogtreecommitdiff
path: root/subversion/libsvn_repos/dump.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_repos/dump.c')
-rw-r--r--subversion/libsvn_repos/dump.c1597
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;