[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

[PATCH] back out changes with --change -N

From: Bart Robinson <lomew_at_pobox.com>
Date: 2006-02-01 22:48:55 CET

Here is an patch to enable the -c/--change option to be used to back
out changes via a negative revision number. This idea was recently
discussed in the "--reverse option" thread:
http://svn.haxx.se/dev/archive-2006-01/0338.shtml
(has refs to older discussions.)

There was disagreement as to whether rollback was very common, but
there was some consensus that having *some* way to do a rollback with
-c would be useful.

I felt that allowing a negative revision to -c was the least invasive
and that is what this patch does. (Apparently that is what svk does
as well.)

"make check" passes aside from a utf8 one that was SKIP'd.

-- bart

[[[
Extend -c/--change option in diff and merge to accept negative
revisions as meaning the reverse of a change, making it possible to
back out changes with -c rather than -r. E.g., -c -1234 is the same
as -r1234:1233.

* subversion/svn/main.c
  (svn_cl__options): Update docstring for '--change'
  (svn_cl__cmd_table): Update 'diff' and 'merge' docstrings.
  (main): Handle parsing for negative revision number.

* subversion/tests/cmdline/diff_tests.py
  (diff_only_property_change): Add checks for '-c' with negative and
  positive args.

* subversion/tests/cmdline/merge_tests.py
  (merge_with_implicit_target_using_c): New test: a copy
  of merge_with_implicit_target that uses the '-c' intead of '-r'.
  (test_list): Add the new test.
]]]

Index: subversion/svn/main.c
===================================================================
--- subversion/svn/main.c (revision 18310)
+++ subversion/svn/main.c (working copy)
@@ -66,7 +66,10 @@ 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_("the change made by revision ARG (like -r ARG-1:ARG)")},
+ {"change", 'c', 1, N_
+ ("the change made by revision ARG (like -r ARG-1:ARG)\n"
+ " If ARG is negative this is like -r ARG:ARG-1")
+ },
   {"revision", 'r', 1, N_
    ("ARG (some commands also take ARG1:ARG2 range)\n"
     " A revision argument can be one of:\n"
@@ -282,6 +285,7 @@ const svn_opt_subcommand_desc_t svn_cl__
      " must be specified. M defaults to the current working version if any\n"
      " TARGET is a working copy path, otherwise it defaults to HEAD.\n"
      " The '-c M' option is equivalent to '-r N:M' where N = M-1.\n"
+ " Using '-c -M' does the reverse: '-r M:N' where N = M-1.\n"
      "\n"
      " 2. Display the differences between OLD-TGT as it was seen in OLDREV and\n"
      " NEW-TGT as it was seen in NEWREV. PATHs, if given, are relative to\n"
@@ -290,6 +294,7 @@ const svn_opt_subcommand_desc_t svn_cl__
      " NEW-TGT defaults to OLD-TGT if not specified. -r N makes OLDREV default\n"
      " to N, -r N:M makes OLDREV default to N and NEWREV default to M,\n"
      " -c M makes OLDREV default to M-1 and NEWREV default to M.\n"
+ " -c -M makes OLDREV default to M and NEWREV default to M-1.\n"
      "\n"
      " 3. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] --new=NEW-URL[@NEWREV]'\n"
      "\n"
@@ -430,8 +435,9 @@ 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. The '-c M'\n"
- " option is equivalent to '-r N:M' where N = M-1.\n"
+ " M. If REV is not specified, HEAD is assumed.\n"
+ " The '-c M' option is equivalent to '-r N:M' where N = M-1.\n"
+ " Using '-c -M' does the reverse: '-r M:N' where N = M-1\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"
@@ -872,6 +878,7 @@ main (int argc, const char * const *argv
       case 'c':
         {
           char *end;
+ svn_revnum_t changeno;
           if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
             {
               err = svn_error_create
@@ -880,28 +887,35 @@ main (int argc, const char * const *argv
                    "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;
+ changeno = strtol (opt_arg, &end, 10);
           if (end == opt_arg || *end != '\0')
             {
               err = svn_error_create (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
                                       _("Non-numeric change argument given to -c"));
               return svn_cmdline_handle_exit_error (err, pool, "svn: ");
             }
- else if (opt_state.end_revision.value.number == 0)
+ if (changeno == 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: ");
             }
- else if (opt_state.end_revision.value.number < 0)
+ /* Figure out the range:
+ -c N -> -r N-1:N
+ -c -N -> -r N:N-1 */
+ if (changeno > 0)
             {
- err = svn_error_create (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
- _("Argument to -c must be positive"));
- return svn_cmdline_handle_exit_error (err, pool, "svn: ");
+ opt_state.start_revision.value.number = changeno-1;
+ opt_state.end_revision.value.number = changeno;
+ }
+ else
+ {
+ changeno = -changeno;
+ opt_state.start_revision.value.number = changeno;
+ opt_state.end_revision.value.number = changeno-1;
             }
- opt_state.start_revision.value.number = opt_state.end_revision.value.number - 1;
- opt_state.start_revision.kind = svn_opt_revision_number;
+ opt_state.start_revision.kind = svn_opt_revision_number;
+ opt_state.end_revision.kind = svn_opt_revision_number;
         }
         break;
       case 'r':
Index: subversion/tests/cmdline/diff_tests.py
===================================================================
--- subversion/tests/cmdline/diff_tests.py (revision 18310)
+++ subversion/tests/cmdline/diff_tests.py (working copy)
@@ -668,9 +668,15 @@ def diff_only_property_change(sbox):
     svntest.actions.run_and_verify_svn(None, expected_output, [],
                                        'diff', '-r', '1:2')
 
+ svntest.actions.run_and_verify_svn(None, expected_output, [],
+ 'diff', '-c', '2')
+
     svntest.actions.run_and_verify_svn(None, expected_reverse_output, [],
                                        'diff', '-r', '2:1')
 
+ svntest.actions.run_and_verify_svn(None, expected_reverse_output, [],
+ 'diff', '-c', '-2')
+
     svntest.actions.run_and_verify_svn(None, expected_output, [],
                                        'diff', '-r', '1')
 
Index: subversion/tests/cmdline/merge_tests.py
===================================================================
--- subversion/tests/cmdline/merge_tests.py (revision 18310)
+++ subversion/tests/cmdline/merge_tests.py (working copy)
@@ -1282,6 +1282,72 @@ def merge_with_implicit_target (sbox):
 
 #----------------------------------------------------------------------
 
+def merge_with_implicit_target_using_c (sbox):
+ "merge using the -c option"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # Change mu for revision 2
+ mu_path = os.path.join(wc_dir, 'A', 'mu')
+ orig_mu_text = svntest.tree.get_text(mu_path)
+ added_mu_text = ""
+ for x in range(2,11):
+ added_mu_text = added_mu_text + 'This is line ' + `x` + ' in mu\n'
+ svntest.main.file_append(mu_path, added_mu_text)
+
+ # Create expected output tree for initial commit
+ expected_output = wc.State(wc_dir, {
+ 'A/mu' : Item(verb='Sending'),
+ })
+
+ # Create expected status tree; all local revisions should be at 1,
+ # but mu should be at revision 2.
+ expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
+ expected_status.tweak(wc_rev=1)
+ expected_status.tweak('A/mu', wc_rev=2)
+
+ # Initial commit.
+ svntest.actions.run_and_verify_commit (wc_dir,
+ expected_output,
+ expected_status,
+ None,
+ None, None, None, None,
+ wc_dir)
+
+ # Make the "other" working copy
+ other_wc = sbox.add_wc_path('other')
+ svntest.actions.duplicate_dir(wc_dir, other_wc)
+
+ # Try the merge without an explicit target; it should succeed.
+ # Can't use run_and_verify_merge cuz it expects a directory argument.
+ mu_url = svntest.main.current_repo_url + '/A/mu'
+ was_cwd = os.getcwd()
+ try:
+ os.chdir(os.path.join(other_wc, 'A'))
+
+ # merge using URL for sourcepath
+ svntest.actions.run_and_verify_svn(None, ['U mu\n'], [],
+ 'merge', '-c', '-2', mu_url)
+
+ # sanity-check resulting file
+ if (svntest.tree.get_text('mu') != orig_mu_text):
+ raise svntest.Failure
+
+ # merge using filename for sourcepath
+ # Cannot use run_and_verify_merge with a file target
+ svntest.actions.run_and_verify_svn(None, ['G mu\n'], [],
+ 'merge', '-c', '2', 'mu')
+
+ # sanity-check resulting file
+ if (svntest.tree.get_text('mu') != orig_mu_text + added_mu_text):
+ raise svntest.Failure
+
+ finally:
+ os.chdir(was_cwd)
+
+#----------------------------------------------------------------------
+
 def merge_with_prev (sbox):
   "merge operations using PREV revision"
 
@@ -3208,6 +3274,7 @@ test_list = [ None,
               delete_file_and_dir,
               simple_property_merges,
               merge_with_implicit_target,
+ merge_with_implicit_target_using_c,
               merge_catches_nonexistent_target,
               merge_tree_deleted_in_target,
               merge_similar_unrelated_trees,

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Wed Feb 1 22:49:55 2006

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.