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

[SVNMERGE][PATCH] svnmerge rollback

From: Madan U Sreenivasan <madan_at_collab.net>
Date: 2006-05-16 20:53:36 CEST

Hi,

    pl. find attached the patch/log for the 'svnmerge rollback'.

    Awaiting your comments.

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

Review by: David James <djames@collab.net>
            Daniel Rall <dlr@collab.net>

* 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'.
]]]

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")
+ self.launch("touch newfile")
+ self.launch("svn add newfile")
+ self.launch("svn commit -m 'Adding newfile'", match=r"Committed revision 15")
+ self.launch("touch anothernewfile")
+ 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")
+ self.launch("touch newfile")
+ 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")
+ self.launch("touch newfile")
+ self.launch("svn add newfile")
+ self.launch("svn commit -m 'Adding newfile'", match=r"Committed revision 15")
+ self.launch("touch anothernewfile")
+ 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)
@@ -184,6 +184,10 @@
     print >> sys.stderr, "%s: %s" % (NAME, s)
     sys.exit(1)
 
+def warn(s):
+ """Print a warning message to stdout."""
+ print >> sys.stdout, "%s: %s" % (NAME, s)
+
 def report(s):
     """Subroutine to output progress message, unless in quiet mode."""
     if opts["verbose"]:
@@ -1208,7 +1212,83 @@
         f.close()
         report('wrote commit message to "%s"' % opts["commit-file"])
 
+def action_rollback(branch_dir, branch_props):
+ """Rollback previously integrated revisions."""
 
+ # Check branch directory is ready for being modified
+ check_dir_clean(branch_dir)
+
+ # Make sure the revision arguments are present
+ if not opts["revision"]:
+ error("The '-r' option is mandatory for rollback")
+
+ # 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 dest 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:
+ warn("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 +1694,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 <djames@collab.net>
           Daniel Rall <dlr@collab.net>

* 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'.

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Tue May 16 20:23:39 2006

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