Hi!
I've attached a patch that adds '--include/--exclude' options to 'svnadmin
dump'. These options work similarly to 'svndumpfilter include/exclude'
but provide proper handling of 'copy from' paths.
Consider the following example with svndumpfilter:
[[
$ svnadmin create /repo
$ svn mkdir -m "" file:///repo/A
$ svn copy -m "" file:///repo/A file:///repo/B
$ svnadmin dump /repo | svndumpfilter include /B > dump
...
Revision 0 committed as 0.
Revision 1 committed as 1.
svndumpfilter: E200003: Invalid copy source path '/A'
]]
The 'svnadmin dump' with proposed include/exclude options can handle this use
case properly:
[[
$ svnadmin dump /repo --include /B > dump
* Dumped revision 0.
* Dumped revision 1.
* Dumped revision 2.
]]
The proposed options are implemented using 'svn_repos_authz_func_t' and the
not-included copy sources are hidden by the 'svn_repos' layer.
Some usage examples for the proposed options:
[[
$ svnadmin dump repos --include /calc > calc-dumpfile
...
$ svnadmin dump repos --include /calc --include /calendar > dumpfile
...
$ svnadmin dump repos --exclude /secret > dumpfile
...
$ svnadmin dump repos --include /calc --exclude /calendar > dumpfile
svnadmin: E205000: Try 'svnadmin help' for more info
svnadmin: E205000: '--exclude' and '--include' options cannot be used
simultaneously
$ svnadmin dump repos --include /cal* --pattern > dumpfile
...
]]
Log message:
[[
Add '--include' and '--exclude' options to 'svnadmin dump'.
* include/svn_repos.h
(svn_repos_dump_filter_func_t): New.
(svn_repos_dump_fs4): Update function signature and comment.
(svn_repos_dump_fs3): Update comment.
* libsvn_repos/deprecated.c
(svn_repos_dump_fs3): Update caller.
* libsvn_repos/dump.c
(write_revision_record): Call to svn_repos_fs_revision_proplist() (with
AUTHZ_FUNC) instead of svn_fs_revision_proplist2() to filter revision
properties as well as revisions. Update comment.
(dump_filter_baton_t,
dump_filter_authz_func): New.
(svn_repos_dump_fs4): Initialize and pass AUTHZ_FUNC and AUTHZ_BATON to
the repos layer API if FILTER_FUNC is specified by caller.
* subversion/svnadmin/svnadmin.c
(svnadmin__cmdline_options_t): Add enum values for new options.
(options_table): Add new options.
(cmd_table): Add new options to 'dump' subcommand.
(svnadmin_opt_state): Add new fields to represent new options.
(ary_prefix_match): New. Copied from svndumpfilter.
(dump_filter_baton_t,
dump_filter_func): New.
(subcommand_dump): Initialize FILTER_BATON. Pass DUMP_FILTER_FUNC and a
pointer to FILTER_BATON to svn_repos_dump_fs() if any filtering prefixes
specified.
(sub_main): Handle new options.
* subversion/tests/cmdline/svnadmin_tests.py
(dump_exclude,
dump_exclude_copysource,
dump_include,
dump_not_include_copysource,
dump_exclude_by_pattern,
dump_include_by_pattern,
dump_exclude_all_rev_changes,
dump_invalid_filtering_option): New.
(test_list): Add new tests to table.
* subversion/tests/libsvn_repos/dump-load-test.c
(test_dump_bad_props): Update caller.
Patch by: sergey.raevskiy{_AT_}visualsvn.com
]]
Index: subversion/include/svn_repos.h
===================================================================
--- subversion/include/svn_repos.h (revision 1764423)
+++ subversion/include/svn_repos.h (working copy)
@@ -375,6 +375,24 @@ typedef void (*svn_repos_notify_func_t)(void *bato
const svn_repos_notify_t *notify,
apr_pool_t *scratch_pool);
+/** Callback for filtering repository contents during dump.
+ *
+ * Set @a *include to TRUE to indicate that node, identified by path
+ * @a path in @a root should be included in dump, or set it to @c FALSE
+ * to indicate that node should be excluded (presumably according to state
+ * stored in @a baton).
+ *
+ * Do not assume @a scratch_pool has any lifetime beyond this call.
+ *
+ * @since New in 1.10.
+ */
+typedef svn_error_t * (*svn_repos_dump_filter_func_t)(
+ svn_boolean_t *include,
+ svn_fs_root_t *root,
+ const char *path,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
/**
* Allocate an #svn_repos_notify_t structure in @a result_pool, initialize
* and return it.
@@ -3170,6 +3188,9 @@ svn_repos_verify_fs(svn_repos_t *repos,
* reiterating the existence of previous warnings
* ### This is a presentation issue. Caller could do this itself.
*
+ * If @a filter_func is not @c NULL, it is called for each node being
+ * dumped, allowing the caller to exclude it from dump.
+ *
* If @a cancel_func is not @c NULL, it is called periodically with
* @a cancel_baton as argument to see if the client wishes to cancel
* the dump.
@@ -3189,6 +3210,8 @@ svn_repos_dump_fs4(svn_repos_t *repos,
svn_boolean_t include_changes,
svn_repos_notify_func_t notify_func,
void *notify_baton,
+ svn_repos_dump_filter_func_t filter_func,
+ void *filter_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool);
@@ -3195,7 +3218,8 @@ svn_repos_dump_fs4(svn_repos_t *repos,
/**
* Similar to svn_repos_dump_fs4(), but with @a include_revprops and
- * @a include_changes both set to @c TRUE.
+ * @a include_changes both set to @c TRUE and @a filter_func and
+ * @a filter_baton set to @c NULL.
*
* @since New in 1.7.
* @deprecated Provided for backward compatibility with the 1.9 API.
Index: subversion/libsvn_repos/deprecated.c
===================================================================
--- subversion/libsvn_repos/deprecated.c (revision 1764423)
+++ subversion/libsvn_repos/deprecated.c (working copy)
@@ -780,6 +780,7 @@ svn_repos_dump_fs3(svn_repos_t *repos,
TRUE,
notify_func,
notify_baton,
+ NULL, NULL,
cancel_func,
cancel_baton,
pool));
Index: subversion/libsvn_repos/dump.c
===================================================================
--- subversion/libsvn_repos/dump.c (revision 1764423)
+++ subversion/libsvn_repos/dump.c (working copy)
@@ -1918,14 +1918,17 @@ get_dump_editor(const svn_delta_editor_t **editor,
/* Helper for svn_repos_dump_fs.
- Write a revision record of REV in FS to writable STREAM, using POOL.
+ Write a revision record of REV in REPOS to writable STREAM, using POOL.
Dump revision properties as well if INCLUDE_REVPROPS has been set.
+ AUTHZ_FUNC and AUTHZ_BATON are passed directly to the repos layer.
*/
static svn_error_t *
write_revision_record(svn_stream_t *stream,
- svn_fs_t *fs,
+ svn_repos_t *repos,
svn_revnum_t rev,
svn_boolean_t include_revprops,
+ svn_repos_authz_func_t authz_func,
+ void *authz_baton,
apr_pool_t *pool)
{
apr_hash_t *props;
@@ -1934,7 +1937,8 @@ write_revision_record(svn_stream_t *stream,
if (include_revprops)
{
- SVN_ERR(svn_fs_revision_proplist2(&props, fs, rev, FALSE, pool, pool));
+ SVN_ERR(svn_repos_fs_revision_proplist(&props, repos, rev,
+ authz_func, authz_baton, pool));
/* Run revision date properties through the time conversion to
canonicalize them. */
@@ -1961,8 +1965,29 @@ write_revision_record(svn_stream_t *stream,
return SVN_NO_ERROR;
}
+/* Baton for dump_filter_authz_func(). */
+typedef struct dump_filter_baton_t
+{
+ svn_repos_dump_filter_func_t filter_func;
+ void *filter_baton;
+} dump_filter_baton_t;
+/* Implements svn_repos_authz_func_t. */
+static svn_error_t *
+dump_filter_authz_func(svn_boolean_t *allowed,
+ svn_fs_root_t *root,
+ const char *path,
+ void *baton,
+ apr_pool_t *pool)
+{
+ dump_filter_baton_t *b = baton;
+ return svn_error_trace(b->filter_func(allowed, root, path, b->filter_baton,
+ pool));
+}
+
+
+
/* The main dumper. */
svn_error_t *
svn_repos_dump_fs4(svn_repos_t *repos,
@@ -1975,6 +2000,8 @@ svn_repos_dump_fs4(svn_repos_t *repos,
svn_boolean_t include_changes,
svn_repos_notify_func_t notify_func,
void *notify_baton,
+ svn_repos_dump_filter_func_t filter_func,
+ void *filter_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
@@ -1990,6 +2017,8 @@ svn_repos_dump_fs4(svn_repos_t *repos,
svn_boolean_t found_old_reference = FALSE;
svn_boolean_t found_old_mergeinfo = FALSE;
svn_repos_notify_t *notify;
+ svn_repos_authz_func_t authz_func;
+ dump_filter_baton_t authz_baton = {0};
/* Make sure we catch up on the latest revprop changes. This is the only
* time we will refresh the revprop data in this query. */
@@ -2018,6 +2047,20 @@ svn_repos_dump_fs4(svn_repos_t *repos,
"(youngest revision is %ld)"),
end_rev, youngest);
+ /* We use read authz callback to implement dump filtering. If there is no
+ * read access for some node, it will be excluded from dump as well as
+ * references to it (e.g. copy source). */
+ if (filter_func)
+ {
+ authz_func = dump_filter_authz_func;
+ authz_baton.filter_func = filter_func;
+ authz_baton.filter_baton = filter_baton;
+ }
+ else
+ {
+ authz_func = NULL;
+ }
+
/* Write out the UUID. */
SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
@@ -2053,8 +2096,8 @@ svn_repos_dump_fs4(svn_repos_t *repos,
SVN_ERR(cancel_func(cancel_baton));
/* Write the revision record. */
- SVN_ERR(write_revision_record(stream, fs, rev, include_revprops,
- iterpool));
+ SVN_ERR(write_revision_record(stream, repos, rev, include_revprops,
+ authz_func, &authz_baton, iterpool));
/* When dumping revision 0, we just write out the revision record.
The parser might want to use its properties.
@@ -2087,8 +2130,7 @@ svn_repos_dump_fs4(svn_repos_t *repos,
SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
to_root, "",
dump_editor, dump_edit_baton,
- NULL,
- NULL,
+ authz_func, &authz_baton,
FALSE, /* don't send text-deltas */
svn_depth_infinity,
FALSE, /* don't send entry props */
@@ -2100,7 +2142,7 @@ svn_repos_dump_fs4(svn_repos_t *repos,
/* The normal case: compare consecutive revs. */
SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
dump_editor, dump_edit_baton,
- NULL, NULL, iterpool));
+ authz_func, &authz_baton, iterpool));
/* While our editor close_edit implementation is a no-op, we still
do this for completeness. */
Index: subversion/svnadmin/svnadmin.c
===================================================================
--- subversion/svnadmin/svnadmin.c (revision 1764423)
+++ subversion/svnadmin/svnadmin.c (working copy)
@@ -150,7 +150,10 @@ enum svnadmin__cmdline_options_t
svnadmin__compatible_version,
svnadmin__check_normalization,
svnadmin__metadata_only,
- svnadmin__no_flush_to_disk
+ svnadmin__no_flush_to_disk,
+ svnadmin__exclude,
+ svnadmin__include,
+ svnadmin__glob
};
/* Option codes and descriptions.
@@ -273,6 +276,15 @@ static const apr_getopt_option_t options_table[] =
N_("disable flushing to disk during the operation\n"
" (faster, but unsafe on power off)")},
+ {"exclude", svnadmin__exclude, 1,
+ N_("filter out nodes with given prefix(es) from dump")},
+
+ {"include", svnadmin__include, 1,
+ N_("filter out nodes without given prefix(es) from dump")},
+
+ {"pattern", svnadmin__glob, 0,
+ N_("treat the path prefixes as file glob patterns")},
+
{NULL}
};
@@ -330,7 +342,8 @@ static const svn_opt_subcommand_desc2_t cmd_table[
"every path present in the repository as of that revision. (In either\n"
"case, the second and subsequent revisions, if any, describe only paths\n"
"changed in those revisions.)\n"),
- {'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M', 'F'},
+ {'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M', 'F',
+ svnadmin__exclude, svnadmin__include, svnadmin__glob },
{{'F', N_("write to file ARG instead of stdout")}} },
{"dump-revprops", subcommand_dump_revprops, {0}, N_
@@ -550,6 +563,9 @@ struct svnadmin_opt_state
apr_uint64_t memory_cache_size; /* --memory-cache-size M */
const char *parent_dir; /* --parent-dir */
const char *file; /* --file */
+ apr_array_header_t *exclude; /* --exclude */
+ apr_array_header_t *include; /* --include */
+ svn_boolean_t glob; /* --pattern */
const char *config_dir; /* Overriding Configuration Directory */
};
@@ -1233,6 +1249,57 @@ get_dump_range(svn_revnum_t *lower,
return SVN_NO_ERROR;
}
+/* Compare the node-path PATH with the (const char *) prefixes in PFXLIST.
+ * Return TRUE if any prefix is a prefix of PATH (matching whole path
+ * components); FALSE otherwise.
+ * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
+static svn_boolean_t
+ary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
+{
+ int i;
+ size_t path_len = strlen(path);
+
+ for (i = 0; i < pfxlist->nelts; i++)
+ {
+ const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
+ size_t pfx_len = strlen(pfx);
+
+ if (path_len < pfx_len)
+ continue;
+ if (strncmp(path, pfx, pfx_len) == 0
+ && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Baton for dump_filter_func(). */
+struct dump_filter_baton_t
+{
+ apr_array_header_t *prefixes;
+ svn_boolean_t glob;
+ svn_boolean_t do_exclude;
+};
+
+/* Implements svn_repos_dump_filter_func_t. */
+static svn_error_t *
+dump_filter_func(svn_boolean_t *include,
+ svn_fs_root_t *root,
+ const char *path,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct dump_filter_baton_t *b = baton;
+ const svn_boolean_t matches =
+ (b->glob
+ ? svn_cstring_match_glob_list(path, b->prefixes)
+ : ary_prefix_match(b->prefixes, path));
+
+ *include = b->do_exclude ? !matches : matches;
+ return SVN_NO_ERROR;
+}
+
/* This implements `svn_opt_subcommand_t'. */
static svn_error_t *
subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
@@ -1242,6 +1309,7 @@ subcommand_dump(apr_getopt_t *os, void *baton, apr
svn_stream_t *out_stream;
svn_revnum_t lower, upper;
svn_stream_t *feedback_stream = NULL;
+ struct dump_filter_baton_t filter_baton = {0};
/* Expect no more arguments. */
SVN_ERR(parse_args(NULL, os, 0, 0, pool));
@@ -1267,11 +1335,34 @@ subcommand_dump(apr_getopt_t *os, void *baton, apr
if (! opt_state->quiet)
feedback_stream = recode_stream_create(stderr, pool);
+ /* Initialize the filter baton. */
+ filter_baton.glob = opt_state->glob;
+
+ if (opt_state->exclude && !opt_state->include)
+ {
+ filter_baton.prefixes = opt_state->exclude;
+ filter_baton.do_exclude = TRUE;
+ }
+ else if (opt_state->include && !opt_state->exclude)
+ {
+ filter_baton.prefixes = opt_state->include;
+ filter_baton.do_exclude = FALSE;
+ }
+ else if (opt_state->include && opt_state->exclude)
+ {
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'--exclude' and '--include' options "
+ "cannot be used simultaneously"));
+ }
+
SVN_ERR(svn_repos_dump_fs4(repos, out_stream, lower, upper,
opt_state->incremental, opt_state->use_deltas,
TRUE, TRUE,
!opt_state->quiet ? repos_notify_handler : NULL,
- feedback_stream, check_cancel, NULL, pool));
+ feedback_stream,
+ filter_baton.prefixes ? dump_filter_func : NULL,
+ &filter_baton,
+ check_cancel, NULL, pool));
return SVN_NO_ERROR;
}
@@ -1313,7 +1404,8 @@ subcommand_dump_revprops(apr_getopt_t *os, void *b
SVN_ERR(svn_repos_dump_fs4(repos, out_stream, lower, upper,
FALSE, FALSE, TRUE, FALSE,
!opt_state->quiet ? repos_notify_handler : NULL,
- feedback_stream, check_cancel, NULL, pool));
+ feedback_stream, NULL, NULL,
+ check_cancel, NULL, pool));
return SVN_NO_ERROR;
}
@@ -2860,6 +2952,23 @@ sub_main(int *exit_code, int argc, const char *arg
case svnadmin__no_flush_to_disk:
opt_state.no_flush_to_disk = TRUE;
break;
+ case svnadmin__exclude:
+ SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+
+ if (! opt_state.exclude)
+ opt_state.exclude = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(opt_state.exclude, const char *) = utf8_opt_arg;
+ break;
+ case svnadmin__include:
+ SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+
+ if (! opt_state.include)
+ opt_state.include = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(opt_state.include, const char *) = utf8_opt_arg;
+ break;
+ case svnadmin__glob:
+ opt_state.glob = TRUE;
+ break;
default:
{
SVN_ERR(subcommand_help(NULL, NULL, pool));
Index: subversion/tests/cmdline/svnadmin_tests.py
===================================================================
--- subversion/tests/cmdline/svnadmin_tests.py (revision 1764423)
+++ subversion/tests/cmdline/svnadmin_tests.py (working copy)
@@ -3436,6 +3436,336 @@ def load_from_file(sbox):
'update', sbox.wc_dir)
svntest.actions.verify_disk(sbox.wc_dir, expected_tree, check_props=True)
+def dump_exclude(sbox):
+ "svnadmin dump with excluded paths"
+
+ sbox.build(create_wc=False)
+
+ # Dump repository with /A/D/H and /A/B/E paths excluded.
+ _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [],
+ 'dump', '-q',
+ '--exclude', '/A/D/H',
+ '--exclude', '/A/B/E',
+ sbox.repo_dir)
+
+ # Load repository from dump.
+ sbox2 = sbox.clone_dependent()
+ sbox2.build(create_wc=False, empty=True)
+ load_and_verify_dumpstream(sbox2, None, [], None, False, dump)
+
+ # Check log.
+ expected_output = svntest.verify.RegexListOutput([
+ '-+\\n',
+ 'r1\ .*\n',
+ # '/A/D/H' and '/A/B/E' is not added.
+ re.escape('Changed paths:\n'),
+ re.escape(' A /A\n'),
+ re.escape(' A /A/B\n'),
+ re.escape(' A /A/B/F\n'),
+ re.escape(' A /A/B/lambda\n'),
+ re.escape(' A /A/C\n'),
+ re.escape(' A /A/D\n'),
+ re.escape(' A /A/D/G\n'),
+ re.escape(' A /A/D/G/pi\n'),
+ re.escape(' A /A/D/G/rho\n'),
+ re.escape(' A /A/D/G/tau\n'),
+ re.escape(' A /A/D/gamma\n'),
+ re.escape(' A /A/mu\n'),
+ re.escape(' A /iota\n'),
+ '-+\\n'
+ ])
+ svntest.actions.run_and_verify_svn(expected_output, [],
+ 'log', '-v', '-q', sbox2.repo_url)
+
+def dump_exclude_copysource(sbox):
+ "svnadmin dump with excluded copysource"
+
+ sbox.build(create_wc=False, empty=True)
+
+ # Create default repository structure.
+ svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir",
+ sbox.repo_url + '/trunk',
+ sbox.repo_url + '/branches',
+ sbox.repo_url + '/tags',
+ "-m", "Create repository structure.")
+
+ # Create a branch.
+ svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "copy",
+ sbox.repo_url + '/trunk',
+ sbox.repo_url + '/branches/branch1',
+ "-m", "Create branch.")
+
+ # Dump repository with /trunk excluded.
+ _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [],
+ 'dump', '-q',
+ '--exclude', '/trunk',
+ sbox.repo_dir)
+
+ # Load repository from dump.
+ sbox2 = sbox.clone_dependent()
+ sbox2.build(create_wc=False, empty=True)
+ load_and_verify_dumpstream(sbox2, None, [], None, False, dump)
+
+ # Check log.
+ expected_output = svntest.verify.RegexListOutput([
+ '-+\\n',
+ 'r2\ .*\n',
+ re.escape('Changed paths:\n'),
+ # Simple add, not copy.
+ re.escape(' A /branches/branch1\n'),
+ '-+\\n',
+ 'r1\ .*\n',
+ # '/trunk' is not added.
+ re.escape('Changed paths:\n'),
+ re.escape(' A /branches\n'),
+ re.escape(' A /tags\n'),
+ '-+\\n'
+ ])
+ svntest.actions.run_and_verify_svn(expected_output, [],
+ 'log', '-v', '-q', sbox2.repo_url)
+
+def dump_include(sbox):
+ "svnadmin dump with included paths"
+
+ sbox.build(create_wc=False, empty=True)
+
+ # Create a couple of directories.
+ # Note that we can't use greek tree as it contains only two top-level
+ # nodes. Including non top-level nodes (e.g. '--include /A/B/E') will
+ # produce unloadable dump for now.
+ svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir",
+ sbox.repo_url + '/A',
+ sbox.repo_url + '/B',
+ sbox.repo_url + '/C',
+ "-m", "Create folder.")
+
+ # Dump repository with /A and /C paths included.
+ _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [],
+ 'dump', '-q',
+ '--include', '/A',
+ '--include', '/C',
+ sbox.repo_dir)
+
+ # Load repository from dump.
+ sbox2 = sbox.clone_dependent()
+ sbox2.build(create_wc=False, empty=True)
+ load_and_verify_dumpstream(sbox2, None, [], None, False, dump)
+
+ # Check log.
+ expected_output = svntest.verify.RegexListOutput([
+ '-+\\n',
+ 'r1\ .*\n',
+ # '/B' is not added.
+ re.escape('Changed paths:\n'),
+ re.escape(' A /A\n'),
+ re.escape(' A /C\n'),
+ '-+\\n'
+ ])
+ svntest.actions.run_and_verify_svn(expected_output, [],
+ 'log', '-v', '-q', sbox2.repo_url)
+
+def dump_not_include_copysource(sbox):
+ "svnadmin dump with not included copysource"
+
+ sbox.build(create_wc=False, empty=True)
+
+ # Create default repository structure.
+ svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir",
+ sbox.repo_url + '/trunk',
+ sbox.repo_url + '/branches',
+ sbox.repo_url + '/tags',
+ "-m", "Create repository structure.")
+
+ # Create a branch.
+ svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "copy",
+ sbox.repo_url + '/trunk',
+ sbox.repo_url + '/branches/branch1',
+ "-m", "Create branch.")
+
+ # Dump repository with only /branches included.
+ _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [],
+ 'dump', '-q',
+ '--include',
'/branches',
+ sbox.repo_dir)
+
+ # Load repository from dump.
+ sbox2 = sbox.clone_dependent()
+ sbox2.build(create_wc=False, empty=True)
+ load_and_verify_dumpstream(sbox2, None, [], None, False, dump)
+
+ # Check log.
+ expected_output = svntest.verify.RegexListOutput([
+ '-+\\n',
+ 'r2\ .*\n',
+ re.escape('Changed paths:\n'),
+ # Simple add, not copy.
+ re.escape(' A /branches/branch1\n'),
+ '-+\\n',
+ 'r1\ .*\n',
+ # Only '/branches' is added in r1.
+ re.escape('Changed paths:\n'),
+ re.escape(' A /branches\n'),
+ '-+\\n'
+ ])
+ svntest.actions.run_and_verify_svn(expected_output, [],
+ 'log', '-v', '-q', sbox2.repo_url)
+
+def dump_exclude_by_pattern(sbox):
+ "svnadmin dump with paths excluded by pattern"
+
+ sbox.build(create_wc=False, empty=True)
+
+ # Create a couple of directories.
+ svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir",
+ sbox.repo_url + '/aaa',
+ sbox.repo_url + '/aab',
+ sbox.repo_url + '/aac',
+ sbox.repo_url + '/bbc',
+ "-m", "Create repository structure.")
+
+ # Dump with paths excluded by pattern.
+ _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [],
+ 'dump', '-q',
+ '--exclude', '/aa?',
+ '--pattern',
+ sbox.repo_dir)
+
+ # Load repository from dump.
+ sbox2 = sbox.clone_dependent()
+ sbox2.build(create_wc=False, empty=True)
+ load_and_verify_dumpstream(sbox2, None, [], None, False, dump)
+
+ # Check log.
+ expected_output = svntest.verify.RegexListOutput([
+ '-+\\n',
+ 'r1\ .*\n',
+ re.escape('Changed paths:\n'),
+ # Only '/bbc' is added in r1.
+ re.escape(' A /bbc\n'),
+ '-+\\n'
+ ])
+ svntest.actions.run_and_verify_svn(expected_output, [],
+ 'log', '-v', '-q', sbox2.repo_url)
+
+def dump_include_by_pattern(sbox):
+ "svnadmin dump with paths included by pattern"
+
+ sbox.build(create_wc=False, empty=True)
+
+ # Create a couple of directories.
+ svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir",
+ sbox.repo_url + '/aaa',
+ sbox.repo_url + '/aab',
+ sbox.repo_url + '/aac',
+ sbox.repo_url + '/bbc',
+ "-m", "Create repository structure.")
+
+ # Dump with paths included by pattern.
+ _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [],
+ 'dump', '-q',
+ '--include', '/aa?',
+ '--pattern',
+ sbox.repo_dir)
+
+ # Load repository from dump.
+ sbox2 = sbox.clone_dependent()
+ sbox2.build(create_wc=False, empty=True)
+ load_and_verify_dumpstream(sbox2, None, [], None, False, dump)
+
+ # Check log.
+ expected_output = svntest.verify.RegexListOutput([
+ '-+\\n',
+ 'r1\ .*\n',
+ # '/bbc' is not added.
+ re.escape('Changed paths:\n'),
+ re.escape(' A /aaa\n'),
+ re.escape(' A /aab\n'),
+ re.escape(' A /aac\n'),
+ '-+\\n'
+ ])
+ svntest.actions.run_and_verify_svn(expected_output, [],
+ 'log', '-v', '-q', sbox2.repo_url)
+
+def dump_exclude_all_rev_changes(sbox):
+ "svnadmin dump with all revision changes excluded"
+
+ sbox.build(create_wc=False, empty=True)
+
+ # Create a couple of directories (r1).
+ svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir",
+ sbox.repo_url + '/r1a',
+ sbox.repo_url + '/r1b',
+ sbox.repo_url + '/r1c',
+ "-m", "Revision 1.")
+
+ # Create a couple of directories (r2).
+ svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir",
+ sbox.repo_url + '/r2a',
+ sbox.repo_url + '/r2b',
+ sbox.repo_url + '/r2c',
+ "-m", "Revision 2.")
+
+ # Create a couple of directories (r3).
+ svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir",
+ sbox.repo_url + '/r3a',
+ sbox.repo_url + '/r3b',
+ sbox.repo_url + '/r3c',
+ "-m", "Revision 3.")
+
+ # Dump with paths excluded by pattern.
+ _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [],
+ 'dump', '-q',
+ '--exclude', '/r2?',
+ '--pattern',
+ sbox.repo_dir)
+
+ # Load repository from dump.
+ sbox2 = sbox.clone_dependent()
+ sbox2.build(create_wc=False, empty=True)
+ load_and_verify_dumpstream(sbox2, None, [], None, False, dump)
+
+ # Check log. Revision properties ('svn:log' etc.) should be empty for r2.
+ expected_output = svntest.verify.RegexListOutput([
+ '-+\\n',
+ 'r3\ |\ jrandom\ |\ .*\ |\ 1\ line\\n',
+ re.escape('Changed paths:'),
+ re.escape(' A /r3a'),
+ re.escape(' A /r3b'),
+ re.escape(' A /r3c'),
+ '',
+ re.escape('Revision 3.'),
+ '-+\\n',
+ re.escape('r2 | (no author) | (no date) | 1 line'),
+ '',
+ '',
+ '-+\\n',
+ 'r1\ |\ jrandom\ |\ .*\ |\ 1\ line\\n',
+ re.escape('Changed paths:'),
+ re.escape(' A /r1a'),
+ re.escape(' A /r1b'),
+ re.escape(' A /r1c'),
+ '',
+ re.escape('Revision 1.'),
+ '-+\\n',
+ ])
+ svntest.actions.run_and_verify_svn(expected_output, [],
+ 'log', '-v', sbox2.repo_url)
+
+def dump_invalid_filtering_option(sbox):
+ "dump with --include and --exclude simultaneously"
+
+ sbox.build(create_wc=False, empty=False)
+
+ # Attempt to dump repository with '--include' and '--exclude' options
+ # specified simultaneously.
+ expected_error = ".*: '--exclude' and '--include' options cannot be used " \
+ "simultaneously"
+ svntest.actions.run_and_verify_svnadmin(None, expected_error,
+ 'dump', '-q',
+ '--exclude', '/A/D/H',
+ '--include', '/A/B/E',
+ sbox.repo_dir)
+
########################################################################
# Run the tests
@@ -3500,7 +3830,16 @@ test_list = [ None,
dump_no_op_prop_change,
load_no_flush_to_disk,
dump_to_file,
- load_from_file
+ load_from_file,
+ dump_no_op_prop_change,
+ dump_exclude,
+ dump_exclude_copysource,
+ dump_include,
+ dump_not_include_copysource,
+ dump_exclude_by_pattern,
+ dump_include_by_pattern,
+ dump_exclude_all_rev_changes,
+ dump_invalid_filtering_option
]
if __name__ == '__main__':
Index: subversion/tests/libsvn_repos/dump-load-test.c
===================================================================
--- subversion/tests/libsvn_repos/dump-load-test.c (revision 1764423)
+++ subversion/tests/libsvn_repos/dump-load-test.c (working copy)
@@ -79,7 +79,7 @@ test_dump_bad_props(svn_stringbuf_t **dump_data_p,
SVN_ERR(svn_repos_dump_fs4(repos, stream, start_rev, end_rev,
FALSE, FALSE, TRUE, TRUE,
notify_func, notify_baton,
- NULL, NULL,
+ NULL, NULL, NULL, NULL,
pool));
svn_stream_close(stream);