Hi all,

    Thanks to all of you, for the exhaustive review!

I have attached herewith the patch with the changes requested by David James and Giovanni Bajo (the ones that I have not implemented, I have replied by mails, explaining why).

[[[
Implement `svnmerge rollback'.
The `rollback' sub-command can be used to rollback previously integrated
revisions.

Review by: David James <[EMAIL PROTECTED]>
           Daniel Rall <[EMAIL PROTECTED]>
           Giovanni Bajo <[EMAIL PROTECTED]>

* contrib/client-side/svnmerge_test.py
  (TestCase_TestRepo.testRollbackWithoutInit):
  (TestCase_TestRepo.testRollbackOutsidePossibleRange):
  (TestCase_TestRepo.testRollbackWithoutRevisionOpt):
  (TestCase_TestRepo.testInitAndRollbackRecordOnly):
  (TestCase_TestRepo.testInitAndRollback):
  (TestCase_TestRepo.testMergeAndRollbackEmptyRevisionRange):
  (TestCase_TestRepo.testBlockMergeAndRollback):
  (TestCase_TestRepo.testMergeAndRollback):
  (TestCase_TestRepo.testBlockMergeAndRollback): New tests for rollback
   functionality.

* contrib/client-side/svnmerge.py
  (warn): New function to print warning message to stdout.
  (action_rollback): New function. Used to rollback the merges between
   the given revision numbers.
  (command_table): New entry for `rollback'.
]]]

Once again, thanks all!

Regards,
Madan.
Index: contrib/client-side/svnmerge_test.py
===================================================================
--- contrib/client-side/svnmerge_test.py        (revision 19641)
+++ contrib/client-side/svnmerge_test.py        (working copy)
@@ -748,6 +748,169 @@
 
         self.svnmerge("integrated", match=r"^3-20$")
 
+    def testRollbackWithoutInit(self):
+        """Rollback should error out if invoked prior to init"""
+
+        self.svnmerge("rollback -vv --head ../trunk",
+                      error = True,
+                      match = r"no integration info available for repository 
path")
+
+    def testRollbackOutsidePossibleRange(self):
+        """`svnmerge rollback' should error out if range contains revisions 
prior to
+        SOURCE creation date."""
+
+        # Initialize svnmerge
+        self.svnmerge2(["init", self.test_repo_url + "/trunk"])
+        self.launch("svn commit -F svnmerge-commit-message.txt",
+                    match = r"Committed revision 14")
+        os.remove("svnmerge-commit-message.txt")
+
+        expected_error  = r"""Specified revision range falls out of the 
rollback range."""
+        self.svnmerge("rollback -vv --head ../trunk -r 2-14",
+                      error = True,
+                      match = expected_error)
+
+    def testRollbackWithoutRevisionOpt(self):
+        """`svnmerge rollback' should error out if -r option is not given"""
+
+        # Initialize svnmerge
+        self.svnmerge2(["init", self.test_repo_url + "/trunk"])
+        self.launch("svn commit -F svnmerge-commit-message.txt",
+                    match=r"Committed revision 14")
+        os.remove("svnmerge-commit-message.txt")
+
+        self.svnmerge("rollback -vv --head ../trunk",
+                      error = True,
+                      match = r"The '-r' option is mandatory for rollback")
+
+    def testInitAndRollbackRecordOnly(self):
+        """Init svnmerge, modify source head, merge, rollback --record-only."""
+
+        # Initialize svnmerge
+        self.svnmerge2(["init", self.test_repo_url + "/trunk"])
+        self.launch("svn commit -F svnmerge-commit-message.txt",
+                    match = r"Committed revision 14")
+        os.remove("svnmerge-commit-message.txt")
+
+        # Rollback record-only
+        expected_output = r"property 'svnmerge-integrated' set on '.'"
+        detested_output = r"""
+D    test2
+D    test3"""
+        self.svnmerge("rollback -vv --record-only --head ../trunk -r5-7",
+                      match = expected_output,
+                      nonmatch = detested_output)
+
+    def testInitAndRollback(self):
+        """Init svnmerge, modify source head, merge, rollback."""
+
+        # Initialize svnmerge
+        self.svnmerge2(["init", self.test_repo_url + "/trunk"])
+        self.launch("svn commit -F svnmerge-commit-message.txt",
+                    match = r"Committed revision 14")
+        os.remove("svnmerge-commit-message.txt")
+
+        # Svnmerge rollback r5-7
+        expected_output = r"""
+D    test2
+D    test3"""
+        self.svnmerge("rollback -vv --head ../trunk -r5-7",
+                      match = expected_output)
+
+    def testMergeAndRollbackEmptyRevisionRange(self):
+        """Init svnmerge, modify source head, merge, rollback where no merge
+           occured."""
+
+        # Initialize svnmerge
+        self.svnmerge2(["init", self.test_repo_url + "/trunk"])
+        self.launch("svn commit -F svnmerge-commit-message.txt",
+                    match = r"Committed revision 14")
+        os.remove("svnmerge-commit-message.txt")
+
+        # Make changes to trunk
+        os.chdir("../trunk")
+        open("newfile", "w").close()
+        self.launch("svn add newfile")
+        self.launch("svn commit -m 'Adding newfile'", match=r"Committed 
revision 15")
+        open("anothernewfile", "w").close()
+        self.launch("svn add anothernewfile")
+        self.launch("svn commit -m 'Adding anothernewfile'", match=r"Committed 
revision 16")
+
+        # Svnmerge block r15,16
+        os.chdir("../test-branch")
+        self.launch("svn up ..",
+                    error = False)
+        self.svnmerge("block -r 15,16 --head ../trunk")
+        self.launch("svn commit -F svnmerge-commit-message.txt",
+                    match = r"Committed revision 17")
+        self.svnmerge("merge --head ../trunk")
+        self.launch("svn commit -F svnmerge-commit-message.txt")
+
+        # Svnmerge rollback r15-16
+        self.svnmerge("rollback -vv --head ../trunk -r15-16",
+                      error = False,
+                      match = r"Nothing to rollback in revision range r15-16")
+
+    def testMergeAndRollback(self):
+        """Init svnmerge, modify source head, merge, rollback."""
+
+        # Initialize svnmerge
+        self.svnmerge2(["init", self.test_repo_url + "/trunk"])
+        self.launch("svn commit -F svnmerge-commit-message.txt",
+                    match = r"Committed revision 14")
+        os.remove("svnmerge-commit-message.txt")
+
+        # Make changes to trunk
+        os.chdir("../trunk")
+        open("newfile", "w").close()
+        self.launch("svn add newfile")
+        self.launch("svn commit -m 'Adding newfile'", match=r"Committed 
revision 15")
+
+        # Svnmerge merge r15
+        os.chdir("../test-branch")
+        self.svnmerge("merge -r 15 --head ../trunk")
+        self.launch("svn commit -F svnmerge-commit-message.txt",
+                    match = r"Committed revision 16")
+
+        # Svnmerge rollback r15
+        self.svnmerge("rollback -vv --head ../trunk -r15",
+                      match = r"-r 15:14")
+
+    def testBlockMergeAndRollback(self):
+        """Init svnmerge, block, modify head, merge, rollback."""
+
+        # Initialize svnmerge
+        self.svnmerge2(["init", self.test_repo_url + "/trunk"])
+        self.launch("svn commit -F svnmerge-commit-message.txt",
+                    match = r"Committed revision 14")
+        os.remove("svnmerge-commit-message.txt")
+
+        # Make changes to trunk
+        os.chdir("../trunk")
+        open("newfile", "w").close()
+        self.launch("svn add newfile")
+        self.launch("svn commit -m 'Adding newfile'", match=r"Committed 
revision 15")
+        open("anothernewfile", "w").close()
+        self.launch("svn add anothernewfile")
+        self.launch("svn commit -m 'Adding anothernewfile'", match=r"Committed 
revision 16")
+
+        # Svnmerge block r16, merge r15
+        os.chdir("../test-branch")
+        self.launch("svn up ..",
+                    error = False)
+        self.svnmerge("block -r 16 --head ../trunk")
+        self.launch("svn commit -F svnmerge-commit-message.txt",
+                    match = r"Committed revision 17")
+        self.svnmerge("merge --head ../trunk",
+                      nonmatch = r"A    anothernewfile",
+                      match = r"A    newfile")
+        self.launch("svn commit -F svnmerge-commit-message.txt",
+                    match = r"Committed revision 18")
+
+        # Svnmerge rollback revision range 15-18 (in effect only 15,17)
+        self.svnmerge("rollback -vv --head ../trunk -r15-18",
+                      nonmatch = r"D    anothernewfile")
+
 if __name__ == "__main__":
     # If an existing template repository and working copy for testing
     # exists, then always remove it.  This prevents any problems if
Index: contrib/client-side/svnmerge.py
===================================================================
--- contrib/client-side/svnmerge.py     (revision 19641)
+++ contrib/client-side/svnmerge.py     (working copy)
@@ -1208,7 +1208,83 @@
         f.close()
         report('wrote commit message to "%s"' % opts["commit-file"])
 
+def action_rollback(branch_dir, branch_props):
+    """Rollback previously integrated revisions."""
 
+    # Make sure the revision arguments are present
+    if not opts["revision"]:
+        error("The '-r' option is mandatory for rollback")
+
+    # Check branch directory is ready for being modified
+    check_dir_clean(branch_dir)
+
+    # Extract the integration info for the branch_dir
+    branch_props = get_merge_props(branch_dir)
+    check_old_prop_version(branch_dir, branch_props)
+    # Get the list of all revisions already merged into this source-path.
+    merged_revs = merge_props_to_revision_set(branch_props, opts["head-path"])
+
+    # At which revision was the src created?
+    oldest_src_rev = get_created_rev(opts["head-url"])
+    src_pre_exist_range = RevisionSet("1-%d" % oldest_src_rev)
+
+    # Limit to revisions specified by -r (if any)
+    revs = merged_revs & RevisionSet(opts["revision"])
+
+    # make sure theres some revision to rollback
+    if len(revs) == 0:
+        report("Nothing to rollback in revision range r%s" % opts["revision"])
+        return
+
+    # If even one specified revision lies outside the lifetime of the
+    # source head, error out.
+    if len(revs & src_pre_exist_range) != 0:
+        err_str  = "Specified revision range falls out of the rollback 
range.\n"
+        err_str += "%s was created at r%d" % (opts["head-path"], 
oldest_src_rev)
+        error(err_str)
+
+    record_only = opts["record-only"]
+
+    if record_only:
+        report('recording rollback of revision(s) %s from "%s"' %
+               (revs, opts["head-url"]))
+    else:
+        report('rollback of revision(s) %s from "%s"' %
+               (revs, opts["head-url"]))
+
+    # Do the reverse merge(s). Note: the starting revision number
+    # to 'svn merge' is NOT inclusive so we have to subtract one from start.
+    # We try to keep the number of merge operations as low as possible,
+    # because it is faster and reduces the number of conflicts.
+    rollback_intervals = minimal_merge_intervals(revs, [])
+    # rollback in the reverse order of merge
+    rollback_intervals.reverse()
+    for start, end in rollback_intervals:
+        if not record_only:
+            # Do the merge
+            svn_command("merge -r %d:%d %s %s" % \
+                        (end, start - 1, opts["head-url"], branch_dir))
+
+    # Write out commit message if desired
+    # calculate the phantom revs first
+    if opts["commit-file"]:
+        f = open(opts["commit-file"], "w")
+        if record_only:
+            print >>f, 'Recorded rollback of revisions %s via %s from ' % \
+                  (revs , NAME)
+        else:
+            print >>f, 'Rolled back revisions %s via %s from ' % \
+                  (revs , NAME)
+        print >>f, '%s' % opts["head-url"]
+
+        f.close()
+        report('wrote commit message to "%s"' % opts["commit-file"])
+
+    # Update the set of merged revisions.
+    merged_revs = merged_revs - revs 
+    branch_props[opts["head-path"]] = str(merged_revs)
+    set_merge_props(branch_dir, branch_props)
+
 ###############################################################################
 # Command line parsing -- options and commands management
 ###############################################################################
@@ -1614,6 +1690,21 @@
         "-S",
     ]),
 
+    "rollback": (action_rollback,
+    "rollback [OPTION...] [PATH]",
+    """Rollback previously merged in revisions from PATH.  The
+    --revision option is mandatory, and specifies which revisions
+    will be rolled back.  Only the previously integrated merges
+    will be rolled back.
+
+    When manually rolling back changes, --record-only can be used to
+    instruct %s that a manual rollback of a certain revision
+    already happened, so that it can record it and offer that
+    revision for merge henceforth.""" % (NAME),
+    [
+        "-f", "-r", "-S", "-M", # import common opts
+    ]),
+
     "merge": (action_merge,
     "merge [OPTION...] [PATH]",
     """Merge in revisions into PATH from its head. If --revision is omitted,
Implement `svnmerge rollback'.
The `rollback' sub-command can be used to rollback previously integrated
revisions.

Review by: David James <[EMAIL PROTECTED]>
           Daniel Rall <[EMAIL PROTECTED]>
           Giovanni Bajo <[EMAIL PROTECTED]>

* contrib/client-side/svnmerge_test.py
  (TestCase_TestRepo.testRollbackWithoutInit):
  (TestCase_TestRepo.testRollbackOutsidePossibleRange):
  (TestCase_TestRepo.testRollbackWithoutRevisionOpt):
  (TestCase_TestRepo.testInitAndRollbackRecordOnly):
  (TestCase_TestRepo.testInitAndRollback):
  (TestCase_TestRepo.testMergeAndRollbackEmptyRevisionRange):
  (TestCase_TestRepo.testBlockMergeAndRollback):
  (TestCase_TestRepo.testMergeAndRollback):
  (TestCase_TestRepo.testBlockMergeAndRollback): New tests for rollback
   functionality.

* contrib/client-side/svnmerge.py
  (warn): New function to print warning message to stdout.
  (action_rollback): New function. Used to rollback the merges between
   the given revision numbers.
  (command_table): New entry for `rollback'.
_______________________________________________
Svnmerge mailing list
[email protected]
http://www.orcaware.com/mailman/listinfo/svnmerge

Reply via email to