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);