Index: subversion/svnadmin/svnadmin.c =================================================================== --- subversion/svnadmin/svnadmin.c (revision 1875560) +++ subversion/svnadmin/svnadmin.c (working copy) @@ -158,7 +158,8 @@ enum svnadmin__cmdline_options_t svnadmin__normalize_props, svnadmin__exclude, svnadmin__include, - svnadmin__glob + svnadmin__glob, + svnadmin__include_parents }; /* Option codes and descriptions. @@ -297,6 +298,9 @@ static const apr_getopt_option_t options_table[] = " Character '/' is not treated specially, so\n" " pattern /*/foo matches paths /a/foo and /a/b/foo.") }, + {"include-parents", svnadmin__include_parents, 0, + N_("include parent nodes for nodes specified with --include option")}, + {NULL} }; @@ -372,7 +376,7 @@ static const svn_opt_subcommand_desc3_t cmd_table[ "excluded, the copy is transformed into an add (unlike in 'svndumpfilter').\n" )}, {'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M', 'F', - svnadmin__exclude, svnadmin__include, svnadmin__glob }, + svnadmin__exclude, svnadmin__include, svnadmin__glob, svnadmin__include_parents }, {{'F', N_("write to file ARG instead of stdout")}} }, {"dump-revprops", subcommand_dump_revprops, {0}, {N_( @@ -662,6 +666,7 @@ struct svnadmin_opt_state apr_array_header_t *exclude; /* --exclude */ apr_array_header_t *include; /* --include */ svn_boolean_t glob; /* --pattern */ + svn_boolean_t include_parents; /* --include-parents */ const char *config_dir; /* Overriding Configuration Directory */ }; @@ -1361,10 +1366,13 @@ get_dump_range(svn_revnum_t *lower, /* 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. + * If INCLUDE_PARENTS is set to TRUE, return TRUE if PATH is parent of + * any of specified prefixes. * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */ -/* This function is a duplicate of svndumpfilter.c:ary_prefix_match(). */ +/* This function is an extended version of svndumpfilter.c:ary_prefix_match(). */ static svn_boolean_t -ary_prefix_match(const apr_array_header_t *pfxlist, const char *path) +ary_prefix_match(const apr_array_header_t *pfxlist, const char *path, + svn_boolean_t include_parents) { int i; size_t path_len = strlen(path); @@ -1374,11 +1382,18 @@ static svn_boolean_t 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; + if (path_len < pfx_len && include_parents) + { + if (strncmp(pfx, path, path_len) == 0 + && (path_len == 1 || pfx[path_len] == '/')) + return TRUE; + } + else if (path_len >= pfx_len) + { + if (strncmp(path, pfx, pfx_len) == 0 + && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/')) + return TRUE; + } } return FALSE; @@ -1390,6 +1405,7 @@ struct dump_filter_baton_t apr_array_header_t *prefixes; svn_boolean_t glob; svn_boolean_t do_exclude; + svn_boolean_t include_parents; }; /* Implements svn_repos_dump_filter_func_t. */ @@ -1404,7 +1420,7 @@ dump_filter_func(svn_boolean_t *include, const svn_boolean_t matches = (b->glob ? svn_cstring_match_glob_list(path, b->prefixes) - : ary_prefix_match(b->prefixes, path)); + : ary_prefix_match(b->prefixes, path, b->include_parents)); *include = b->do_exclude ? !matches : matches; return SVN_NO_ERROR; @@ -1445,25 +1461,42 @@ subcommand_dump(apr_getopt_t *os, void *baton, apr if (! opt_state->quiet) feedback_stream = recode_stream_create(stderr, pool); + /* Validate filtering options. */ + if (opt_state->exclude && opt_state->include) + { + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'--exclude' and '--include' options " + "cannot be used simultaneously")); + } + + if (opt_state->glob && opt_state->include_parents) + { + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'--pattern' and '--include-parents' options " + "cannot be used simultaneously")); + } + + if (opt_state->exclude && opt_state->include_parents) + { + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'--exclude' and '--include-parents' options " + "cannot be used simultaneously")); + } + /* Initialize the filter baton. */ filter_baton.glob = opt_state->glob; + filter_baton.include_parents = opt_state->include_parents; - if (opt_state->exclude && !opt_state->include) + if (opt_state->exclude) { filter_baton.prefixes = opt_state->exclude; filter_baton.do_exclude = TRUE; } - else if (opt_state->include && !opt_state->exclude) + else if (opt_state->include) { 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, @@ -3178,6 +3211,9 @@ sub_main(int *exit_code, int argc, const char *arg case svnadmin__glob: opt_state.glob = TRUE; break; + case svnadmin__include_parents: + opt_state.include_parents = TRUE; + break; default: { SVN_ERR(subcommand_help(NULL, NULL, pool)); Index: subversion/tests/cmdline/svnadmin_tests.py =================================================================== --- subversion/tests/cmdline/svnadmin_tests.py (revision 1875560) +++ subversion/tests/cmdline/svnadmin_tests.py (working copy) @@ -3808,7 +3808,7 @@ def dump_exclude_all_rev_changes(sbox): 'log', '-v', sbox2.repo_url) def dump_invalid_filtering_option(sbox): - "dump with --include and --exclude simultaneously" + "dump with invalid filtering options" sbox.build(create_wc=False, empty=False) @@ -3822,6 +3822,27 @@ def dump_invalid_filtering_option(sbox): '--include', '/A/B/E', sbox.repo_dir) + # Attempt to dump repository with '--include-parents' and '--exclude' options + # specified simultaneously. + expected_error = ".*: '--exclude' and '--include-parents' options cannot be used " \ + "simultaneously" + svntest.actions.run_and_verify_svnadmin(None, expected_error, + 'dump', '-q', + '--exclude', '/A/D/H', + '--include-parents', + sbox.repo_dir) + + # Attempt to dump repository with '--include-parents' and '--pattern' options + # specified simultaneously. + expected_error = ".*: '--pattern' and '--include-parents' options cannot be used " \ + "simultaneously" + svntest.actions.run_and_verify_svnadmin(None, expected_error, + 'dump', '-q', + '--include', '/A/D/H', + '--pattern', + '--include-parents', + sbox.repo_dir) + @Issue(4725) def load_issue4725(sbox): """load that triggers issue 4725""" @@ -4036,7 +4057,62 @@ PROPS-END if output != ['\n', '\n']: raise svntest.Failure("Unexpected property value %s" % output) +def dump_include_parents(sbox): + "svnadmin dump --include with parents" + 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 + '/project/trunk', + sbox.repo_url + '/project/branches', + sbox.repo_url + '/project/tags', + "-m", "Create repository structure.", + "--parents") + + # Make some changes in trunk. + svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir", + sbox.repo_url + '/project/trunk/dir', + "-m", "Add directory.") + + # Create branch 'b1'. + svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "copy", + sbox.repo_url + '/project/trunk', + sbox.repo_url + '/project/branches/b1', + "-m", "Create branch.") + + # Dump with only 'b1' included. + _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [], + 'dump', '-q', + '--include', '/project/branches/b1', + '--include-parents', + 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', + 'r3\ .*\n', + re.escape('Changed paths:'), + re.escape(' A /project/branches/b1'), + re.escape(' A /project/branches/b1/dir'), + '-+\\n', + 'r2\ .*\n', + '-+\\n', + 'r1\ .*\n', + re.escape('Changed paths:'), + re.escape(' A /project'), + re.escape(' A /project/branches'), + '-+\\n' + ]) + svntest.actions.run_and_verify_svn(expected_output, [], + 'log', '-v', '-q', sbox2.repo_url) + + ######################################################################## # Run the tests @@ -4116,6 +4192,7 @@ test_list = [ None, recover_prunes_rep_cache_when_disabled, dump_include_copied_directory, load_normalize_node_props, + dump_include_parents ] if __name__ == '__main__':