Hello! I've attached a patch which adds a new option (--include-parents) to 'svnadmin dump'. This option works with existing --include option and includes parent nodes for nodes specified in --include option.
This should fix the mentioned case. The option doesn't work with globbing for now, since I don't see a simple way to implement this functionality for globbing. Thus the option is explicitly forbidden if --pattern option is specified. The option is also forbidden to use with --exclude option because it doesn't make sense. Log message: [[[ Add new option (--include-parents) to 'svnadmin dump': include parent nodes for nodes specified in --include option. * subversion/svnadmin/svnadmin.c (svnadmin__cmdline_options_t): Add new member SVNADMIN__INCLUDE_PARENTS. (options_table): Add an entry for new option (--include-parents). (cmd_table): Add new options (--include-parents) for 'dump' subcommand. (struct svnadmin_opt_state): Add member for new option (--include-parents). (ary_prefix_match): Add new parameter INCLUDE_PARENTS. Return TRUE if PATH is parent of any of specified prefixes and INCLUDE_PARENTS set to true. Update comment. (struct dump_filter_baton_t): Add new member INCLUDE_PARENTS. (dump_filter_func): Pass B->INCLUDE_PARENTS to ary_prefix_match(). (subcommand_dump): Initialize INCLUDE_PARENTS member of FILTER_BATON. Rework of filtering options validation code. (sub_main): Handle new option (--include-parents). * subversion/tests/cmdline/svnadmin_tests.py (dump_invalid_filtering_option): Extend test with new cases. Update docstring. (dump_include_parents): New test. (test_list): Add new test to table. Patch by: sergey.raevskiy{_AT_}visualsvn.com ]]] On Mon, Mar 23, 2020 at 8:11 AM 钱海远(Nathan) <qianhaiy...@hikvision.com> wrote: > > Yes, I think so. It's just for include. It's not a very good patch, I think , > sorry for this . > > And it has a known issue , it will include the parent node path , this is a > problem. I made another program to delete parent node. > > > Best Regards! > Haiyuan Qian > R & D Management Group > Hangzhou Hikvision Digital Technology Co.,Ltd > No.555 Qianmo Road, Binjiang District, Hangzhou 310052, China > M (86)18969199712 > > 本邮件及其附件含有海康威视公司的保密信息,仅限于发送给上面地址中列出的个人或群组。禁止任何其他人以任何形式使用(包括但不限于全部或部分地泄露、复制、或散发)本邮件中的信息。如果您错收了本邮件,请您立即电话或邮件通知发件人并删除本邮件! > This e-mail and its attachments contain confidential information from > HIKVISION, which is intended only for the person or entity whose address is > listed above. Any use of the information contained herein in any way > (including, but not limited to, total or partial disclosure, reproduction, or > dissemination) by persons other than the intended recipient(s) is > prohibited. If you receive this e-mail in error, please notify the sender by > phone or email immediately and delete it! > > > -----邮件原件----- > 发件人: Daniel Shahaf <d...@daniel.shahaf.name> > 发送时间: 2020年3月22日 1:35 > 收件人: 钱海远(Nathan) <qianhaiy...@hikvision.com> > 抄送: us...@subversion.apache.org; dev@subversion.apache.org > 主题: Re: Svnadmin dump with include can not dump the subdir into add when it's > parent path was a branch > > [moving to dev@; please remove users@ from replies] > > 钱海远(Nathan) wrote on Sat, 21 Mar 2020 06:12 +0000: > > I found the there is a BUG in subversion 1.10.6. > > > > Svnadmin dump with include can not dump the subdir into add when it's > > parent path was a branch: > > 1. 1、/A was copy from/XX , revision was 50, it the first revision of /A; 2. > > 2、There is a subdir named /A/subdir, and several files and dir under > > /A/subdir; 3. 3、Try to run “svnadmin dump /data/repos_root --include > > /A/subdir >a” . The expected dump file will include /A/subdir(revision 50) > > into add. But in fact there was nothing. > > 4. 4、I was try to fix this bug in the svnadmin.c , a function named > > ary_prefix_match. If the change list is the parent dir for include path, it > > return true. Then the dump file will include /A/subdir and it's subdir or > > subfile into add , but regrettably, the dump file will also include /A into > > add. > > 钱海远(Nathan) wrote on Sat, 21 Mar 2020 06:12 +0000: > > --- > > C:/Users/QIANHA~1/AppData/Local/Temp/svnadmin.c-rev30397.svn001.tmp.c____ > > ____ 25 11:51:32 2020 > > +++ > > C:/Users/QIANHA~1/AppData/Local/Temp/svnadmin.c-rev30398.svn000.tmp.c____ > > ____ 21 12:58:58 2020 > > @@ -1297,3 +1297,3 @@ ary_prefix_match(const apr_array_header_t *pfxlist > > - if (path_len < pfx_len) > > - continue; > > - if (strncmp(path, pfx, pfx_len) == 0 > > + /*if (path_len < pfx_len) > > + continue;*/ > > + if ((strncmp(path, pfx, pfx_len) == 0 > > @@ -1300,0 +1301,3 @@ ary_prefix_match(const apr_array_header_t > > *pfxlist > > + || (strncmp(pfx,path, path_len) == 0 > > + && (path_len == 1 || path[path_len] == '\0' || path[path_len] == > > '/')) > > + ) > > Thanks for the patch. I don't see any obvious problems with this approach. > However, the patch as it stands causes a regression: > > [[[ > W: /home/daniel/src/svn/t1/./subversion/libsvn_repos/load.c:667, > W: /home/daniel/src/svn/t1/./subversion/libsvn_repos/load-fs-vtable.c:718, > W: /home/daniel/src/svn/t1/./subversion/libsvn_repos/load-fs-vtable.c:591, > W: /home/daniel/src/svn/t1/./subversion/libsvn_fs/fs-loader.c:1482, > W: /home/daniel/src/svn/t1/./subversion/libsvn_fs_fs/tree.c:2524, > W: /home/daniel/src/svn/t1/./subversion/libsvn_fs_fs/tree.c:1145: > (apr_err=SVN_ERR_FS_NOT_FOUND) > W: svnadmin: E160013: File not found: transaction '0-0', path '/A/B/F' > W: CWD: /tmp/svn/subversion/tests/cmdline > W: EXCEPTION: Failure: Command failed: "/tmp/svn/subversion/svnadmin/svnadmin > load --quiet svn-test-work/repositories/svnadmin_tests-60-1"; exit code 1 > Traceback (most recent call last): > File "/home/daniel/src/svn/t1/subversion/tests/cmdline/svntest/main.py", > line 1931, in run > rc = self.pred.run(sandbox) > File > "/home/daniel/src/svn/t1/subversion/tests/cmdline/svntest/testcase.py", line > 178, in run > result = self.func(sandbox) > File "/home/daniel/src/svn/t1/subversion/tests/cmdline/svnadmin_tests.py", > line 3510, in dump_exclude > load_and_verify_dumpstream(sbox2, None, [], None, False, dump) > File "/home/daniel/src/svn/t1/subversion/tests/cmdline/svnadmin_tests.py", > line 304, in load_and_verify_dumpstream > 'load', '--quiet', sbox.repo_dir, *varargs) > File "/home/daniel/src/svn/t1/subversion/tests/cmdline/svntest/main.py", > line 666, in run_command_stdin > '"; exit code ' + str(exit_code)) > svntest.Failure: Command failed: "/tmp/svn/subversion/svnadmin/svnadmin load > --quiet svn-test-work/repositories/svnadmin_tests-60-1"; exit code 1 > FAIL: svnadmin_tests.py 60: svnadmin dump with excluded paths ]]] > > So, I guess the new condition should be applied to --include's but not to > --exclude's? > > Cheers, > > Daniel > > ________________________________ > CONFIDENTIALITY NOTICE: This electronic message is intended to be viewed only > by the individual or entity to whom it is addressed. It may contain > information that is privileged, confidential and exempt from disclosure under > applicable law. Any dissemination, distribution or copying of this > communication is strictly prohibited without our prior permission. If the > reader of this message is not the intended recipient, or the employee or > agent responsible for delivering the message to the intended recipient, or if > you have received this communication in error, please notify us immediately > by return e-mail and delete the original message and any copies of it from > your computer system. For further information about Hikvision company. please > see our website at www.hikvision.com<http://www.hikvision.com> >
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__':