Index: subversion/clients/cmdline/main.c =================================================================== --- subversion/clients/cmdline/main.c (revision 17002) +++ subversion/clients/cmdline/main.c (working copy) @@ -69,6 +69,7 @@ const apr_getopt_option_t svn_cl__option {"quiet", 'q', 0, N_("print as little as possible")}, {"recursive", 'R', 0, N_("descend recursively")}, {"non-recursive", 'N', 0, N_("operate on single directory only")}, + {"change", 'c', 1, N_("Shorthand for the revision range NUMBER-1:NUMBER")}, {"revision", 'r', 1, N_ ("ARG (some commands also take ARG1:ARG2 range)\n" " A revision argument can be one of:\n" @@ -276,7 +277,7 @@ const svn_opt_subcommand_desc_t svn_cl__ { "diff", svn_cl__diff, {"di"}, N_("Display the differences between two paths.\n" - "usage: 1. diff [-r N[:M]] [TARGET[@REV]...]\n" + "usage: 1. diff [-r N[:M] -c L] [TARGET[@REV]...]\n" " 2. diff [-r N[:M]] --old=OLD-TGT[@OLDREV] " "[--new=NEW-TGT[@NEWREV]] \\\n" " [PATH...]\n" @@ -309,7 +310,7 @@ const svn_opt_subcommand_desc_t svn_cl__ "\n" " Use just 'svn diff' to display local modifications in " "a working copy.\n"), - {'r', svn_cl__old_cmd_opt, svn_cl__new_cmd_opt, 'N', + {'r', 'c', svn_cl__old_cmd_opt, svn_cl__new_cmd_opt, 'N', svn_cl__diff_cmd_opt, 'x', svn_cl__no_diff_deleted, svn_cl__notice_ancestry_opt, svn_cl__force_opt, SVN_CL__AUTH_OPTIONS, svn_cl__config_dir_opt} }, @@ -444,7 +445,7 @@ const svn_opt_subcommand_desc_t svn_cl__ N_("Apply the differences between two sources to a working copy path.\n" "usage: 1. merge sourceURL1[@N] sourceURL2[@M] [WCPATH]\n" " 2. merge sourceWCPATH1@N sourceWCPATH2@M [WCPATH]\n" - " 3. merge -r N:M SOURCE[@REV] [WCPATH]\n" + " 3. merge [-r N:M | -c N] SOURCE[@REV] [WCPATH]\n" "\n" " 1. In the first form, the source URLs are specified at revisions\n" " N and M. These are the two sources to be compared. The " @@ -460,13 +461,14 @@ const svn_opt_subcommand_desc_t svn_cl__ " 3. In the third form, SOURCE can be a URL, or working copy item\n" " in which case the corresponding URL is used. This URL in\n" " revision REV is compared as it existed between revisions N and \n" - " M. If REV is not specified, HEAD is assumed.\n" + " M in the case of -r, and as it existed between revisions N-1 and \n" + " N in the case of -c. If REV is not specified, HEAD is assumed.\n" "\n" " WCPATH is the working copy path that will receive the changes.\n" " If WCPATH is omitted, a default value of '.' is assumed, unless\n" " the sources have identical basenames that match a file within '.':\n" " in which case, the differences will be applied to that file.\n"), - {'r', 'N', 'q', svn_cl__force_opt, svn_cl__dry_run_opt, + {'r', 'c', 'N', 'q', svn_cl__force_opt, svn_cl__dry_run_opt, svn_cl__merge_cmd_opt, svn_cl__ignore_ancestry_opt, SVN_CL__AUTH_OPTIONS, svn_cl__config_dir_opt} }, @@ -914,12 +916,42 @@ main (int argc, const char * const *argv opt_state.message = apr_pstrdup (pool, opt_arg); dash_m_arg = opt_arg; break; + case 'c': + { + char *end; + if (opt_state.start_revision.kind != svn_opt_revision_unspecified) + { + err = svn_error_create + (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Multiple revision arguments encountered; " + "can't specify -c twice, or both -c and -r")); + return svn_cmdline_handle_exit_error (err, pool, "svn: "); + } + opt_state.end_revision.value.number = strtol (opt_arg, &end, 10); + opt_state.end_revision.kind = svn_opt_revision_number; + if (end == opt_arg || *end != '\0') + { + err = svn_error_create (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Non-numeric change argument given")); + return svn_cmdline_handle_exit_error (err, pool, "svn: "); + } + else if (opt_state.end_revision.value.number == 0) + { + err = svn_error_create (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("There is no change 0")); + return svn_cmdline_handle_exit_error (err, pool, "svn: "); + } + opt_state.start_revision.value.number = opt_state.end_revision.value.number - 1; + opt_state.start_revision.kind = svn_opt_revision_number; + } + break; case 'r': if (opt_state.start_revision.kind != svn_opt_revision_unspecified) { err = svn_error_create (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Multiple revision arguments encountered; " + "can't specifiy -r and -c, or" "try '-r M:N' instead of '-r M -r N'")); return svn_cmdline_handle_exit_error (err, pool, "svn: "); } Index: subversion/tests/clients/cmdline/merge_tests.py =================================================================== --- subversion/tests/clients/cmdline/merge_tests.py (revision 17002) +++ subversion/tests/clients/cmdline/merge_tests.py (working copy) @@ -1139,6 +1139,79 @@ def merge_one_file(sbox): expected_status.tweak('A/D/G/rho', status='M ') svntest.actions.run_and_verify_status(wc_dir, expected_status) +#---------------------------------------------------------------------- +def merge_one_file_with_c(sbox): + "merge one file using -c" + + sbox.build() + wc_dir = sbox.wc_dir + + rho_rel_path = os.path.join('A', 'D', 'G', 'rho') + rho_path = os.path.join(wc_dir, rho_rel_path) + G_path = os.path.join(wc_dir, 'A', 'D', 'G') + rho_url = svntest.main.current_repo_url + '/A/D/G/rho' + + # Change rho for revision 2 + svntest.main.file_append(rho_path, 'A new line in rho.\n') + + expected_output = wc.State(wc_dir, { rho_rel_path : Item(verb='Sending'), }) + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + expected_status.tweak(wc_rev=1) + expected_status.tweak('A/D/G/rho', wc_rev=2) + svntest.actions.run_and_verify_commit (wc_dir, + expected_output, + expected_status, + None, + None, None, None, None, + wc_dir) + + # Backdate rho to revision 1, so we can merge in the rev 2 changes. + svntest.actions.run_and_verify_svn(None, None, [], + 'up', '-r', '1', rho_path) + + # Try one merge with an explicit target; it should succeed. + # ### Yes, it would be nice to use run_and_verify_merge(), but it + # appears to be impossible to get the expected_foo trees working + # right. I think something is still assuming a directory target. + svntest.actions.run_and_verify_svn(None, + ['U ' + rho_path + '\n'], [], + 'merge', '-c', '2', + rho_url, rho_path) + expected_status.tweak(wc_rev=1) + expected_status.tweak('A/D/G/rho', status='M ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # Inspect rho, make sure it's right. + rho_text = svntest.tree.get_text(rho_path) + if rho_text != "This is the file 'rho'.\nA new line in rho.\n": + print "Unexpected text in merged '" + rho_path + "'" + raise svntest.Failure + + # Restore rho to pristine revision 1, for another merge. + svntest.actions.run_and_verify_svn(None, None, [], 'revert', rho_path) + expected_status.tweak('A/D/G/rho', status=' ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # Cd into the directory and run merge with no targets. + # It should still merge into rho. + saved_cwd = os.getcwd() + try: + os.chdir(G_path) + # Cannot use run_and_verify_merge with a file target + svntest.actions.run_and_verify_svn(None, + ['U rho\n'], [], + 'merge', '-c', '2', rho_url) + + # Inspect rho, make sure it's right. + rho_text = svntest.tree.get_text('rho') + if rho_text != "This is the file 'rho'.\nA new line in rho.\n": + print "Unexpected text merging to 'rho' in '" + G_path + "'" + raise svntest.Failure + finally: + os.chdir(saved_cwd) + + expected_status.tweak('A/D/G/rho', status='M ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) #---------------------------------------------------------------------- # This is a regression for the enhancement added in issue #785. @@ -3141,6 +3214,7 @@ test_list = [ None, merge_with_prev, merge_binary_file, merge_one_file, + merge_one_file_with_c, merge_in_new_file_and_diff, merge_skips_obstructions, merge_into_missing,