Rev 7: Add more test, make basic rebase work. in file:///data/jelmer/bzr-rebase/trunk/

Jelmer Vernooij jelmer at samba.org
Thu Jul 12 09:22:37 BST 2007


At file:///data/jelmer/bzr-rebase/trunk/

------------------------------------------------------------
revno: 7
revision-id: jelmer at samba.org-20070704195529-cpcs228123xsidqy
parent: jelmer at samba.org-20070704170026-k87l51j95qnmgwh1
committer: Jelmer Vernooij <jelmer at samba.org>
branch nick: bzr-rebase
timestamp: Wed 2007-07-04 21:55:29 +0200
message:
  Add more test, make basic rebase work.
modified:
  __init__.py                    __init__.py-20070626215909-fi0s39bkwxn4gcto-1
  rebase.py                      rebase.py-20070626221123-ellanmf93nw8z9r1-1
  test_rebase.py                 test_rebase.py-20070626221123-ellanmf93nw8z9r1-2
=== modified file '__init__.py'
--- a/__init__.py	2007-07-04 17:00:26 +0000
+++ b/__init__.py	2007-07-04 19:55:29 +0000
@@ -14,53 +14,69 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-from bzrlib.branch import Branch
 from bzrlib.commands import Command, Option, display_command, register_command
-from bzrlib.errors import BzrCommandError
-from bzrlib.workingtree import WorkingTree
+from bzrlib.errors import BzrCommandError, UnrelatedBranches
 
 class cmd_rebase(Command):
     """Re-base a branch.
 
     """
-    takes_args = ['upstream_location']
-    takes_options = [Option('onto', help='Different revision to replay onto')]
+    takes_args = ['upstream_location?']
+    takes_options = ['revision', Option('onto', help='Different revision to replay onto')]
     
     @display_command
-    def run(self, upstream_location, onto=None):
+    def run(self, upstream_location=None, onto=None, revision=None):
+        from bzrlib.branch import Branch
+        from bzrlib.revisionspec import RevisionSpec
+        from bzrlib.workingtree import WorkingTree
         from rebase import (generate_simple_plan, rebase, 
                             rebase_plan_exists, write_rebase_plan, 
-                            read_rebase_plan, workingtree_replay)
+                            read_rebase_plan, workingtree_replay, MergeConflicted, remove_rebase_plan)
+        wt = WorkingTree.open('.')
+        wt.lock_write()
+        if upstream_location is None:
+            upstream_location = wt.branch.get_parent()
         upstream = Branch.open(upstream_location)
-        wt = WorkingTree.open('.')
-        wt.write_lock()
+        upstream_repository = upstream.repository
+        upstream_revision = upstream.last_revision()
         try:
             # Abort if there already is a plan file
             if rebase_plan_exists(wt):
                 raise BzrCommandError("A rebase operation was interrupted. Continue using 'bzr rebase-continue' or abort using 'bzr rebase-abort'")
 
             # Pull required revisions
-            wt.branch.repository.fetch(upstream.repository, 
-                                       upstream.last_revision())
+            wt.branch.repository.fetch(upstream_repository, 
+                                       upstream_revision)
             if onto is None:
                 onto = upstream.last_revision()
-
-            wt.branch.repository.fetch(upstream.repository, onto)
+            else:
+                onto = RevisionSpec.from_string(onto)
+
+            wt.branch.repository.fetch(upstream_repository, onto)
+
+            start_revid = None
+            revhistory = wt.branch.revision_history()
+            revhistory.reverse()
+            for revid in revhistory:
+                if revid in upstream.revision_history():
+                    start_revid = wt.branch.get_rev_id(wt.branch.revision_id_to_revno(revid)+1)
+                    break
+
+            if start_revid is None:
+                raise UnrelatedBranches()
 
             # Create plan
             replace_map = generate_simple_plan(
-                    wt.branch, upstream.last_revision(), onto)
+                    wt.branch.repository, 
+                    wt.branch.revision_history(), start_revid, onto)
 
             # Write plan file
             write_rebase_plan(wt, replace_map)
 
-            # Set last-revision back to start revision
-            wt.set_last_revision(onto)
-
             # Start executing plan
             try:
                 rebase(wt.branch.repository, replace_map, workingtree_replay(wt))
-            except Conflict:
+            except MergeConflicted:
                 raise BzrCommandError("A conflict occurred applying a patch. Resolve the conflict and run 'bzr rebase-continue' or run 'bzr rebase-abort'.")
             # Remove plan file
             remove_rebase_plan(wt)
@@ -74,12 +90,16 @@
     
     @display_command
     def run(self):
-        from rebase import read_rebase_plan
+        from rebase import read_rebase_plan, remove_rebase_plan
+        from bzrlib.workingtree import WorkingTree
         wt = WorkingTree.open('.')
-        wt.write_lock()
+        wt.lock_write()
         try:
             # Read plan file and set last revision
-            wt.set_last_revision_info(read_rebase_plan(wt)[0])
+            last_rev_info = read_rebase_plan(wt)[0]
+            wt.branch.set_last_revision_info(last_rev_info[0], last_rev_info[1])
+            wt.set_last_revision(last_rev_info[1])
+            remove_rebase_plan(wt)
         finally:
             wt.unlock()
 
@@ -91,9 +111,10 @@
     
     @display_command
     def run(self):
-        from rebase import read_rebase_plan, rebase_plan_exists, workingtree_replay
+        from rebase import read_rebase_plan, rebase_plan_exists, workingtree_replay, MergeConflicted, rebase, remove_rebase_plan
+        from bzrlib.workingtree import WorkingTree
         wt = WorkingTree.open('.')
-        wt.write_lock()
+        wt.lock_write()
         try:
             # Abort if there are any conflicts
             if len(wt.conflicts()) != 0:
@@ -104,7 +125,7 @@
             try:
                 # Start executing plan from current Branch.last_revision()
                 rebase(wt.branch.repository, replace_map, workingtree_replay(wt))
-            except Conflict:
+            except MergeConflicted:
                 raise BzrCommandError("A conflict occurred applying a patch. Resolve the conflict and run 'bzr rebase-continue' or run 'bzr rebase-abort'.")
             # Remove plan file  
             remove_rebase_plan(wt)

=== modified file 'rebase.py'
--- a/rebase.py	2007-07-04 17:00:26 +0000
+++ b/rebase.py	2007-07-04 19:55:29 +0000
@@ -15,7 +15,7 @@
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 from bzrlib.config import Config
-from bzrlib.errors import UnknownFormatError
+from bzrlib.errors import UnknownFormatError, NoSuchFile, BzrError
 from bzrlib.generate_ids import gen_revision_id
 from bzrlib.trace import mutter
 import bzrlib.ui as ui
@@ -29,7 +29,10 @@
     :param wt: Working tree for which to check.
     :return: boolean
     """
-    return wt._control_files.get(REBASE_PLAN_FILENAME).read() != ''
+    try:
+        return wt._control_files.get(REBASE_PLAN_FILENAME).read() != ''
+    except NoSuchFile:
+        return False
 
 
 def read_rebase_plan(wt):
@@ -40,7 +43,7 @@
     """
     text = wt._control_files.get(REBASE_PLAN_FILENAME).read()
     if text == '':
-        raise BzrError("No rebase plan exists")
+        raise NoSuchFile(REBASE_PLAN_FILENAME)
     return unmarshall_rebase_plan(text)
 
 
@@ -50,8 +53,8 @@
     :param wt: Working Tree for which to write the plan.
     :param replace_map: Replace map (old revid -> (new revid, new parents))
     """
-    wt._control_files.put(REBASE_PLAN_FILENAME, 
-            marshall_rebase_plan(wt.last_revision_info(), replace_map))
+    wt._control_files.put_utf8(REBASE_PLAN_FILENAME, 
+            marshall_rebase_plan(wt.branch.last_revision_info(), replace_map))
 
 
 def remove_rebase_plan(wt):
@@ -59,7 +62,7 @@
 
     :param wt: Working Tree for which to remove the plan.
     """
-    wt._control_files.put(REBASE_PLAN_FILENAME, '')
+    wt._control_files.put_utf8(REBASE_PLAN_FILENAME, '')
 
 
 def marshall_rebase_plan(last_rev_info, replace_map):
@@ -230,9 +233,42 @@
     assert all(map(repository.has_revision, [replace_map[r][0] for r in replace_map]))
      
 
-# Change the parent of a revision
-def change_revision_parent(repository, oldrevid, newrevid, new_parents):
-    """Create a copy of a revision with different parents.
+class MapTree:
+    def __init__(self, oldtree, old_parents, new_parents):
+        self.map = {}
+        for (oldp, newp) in zip(old_parents, new_parents):
+            oldinv = repository.get_revision_inventory(oldp)
+            newinv = repository.get_revision_inventory(newp)
+            for path, ie in oldinv.iter_entries():
+                if newinv.has_filename(path):
+                    self.map[ie.file_id] = newinv.path2id(path)
+        self.oldtree = oldtree
+
+    def old_id(self, file_id):
+        for x in self.map:
+            if self.map[x] == file_id:
+                return x
+        return file_id
+
+    def new_id(self, file_id):
+        try:
+            return new_id[file_id]
+        except KeyError:
+            return file_id
+
+    def get_file_sha1(self, file_id, path=None):
+        return self.oldtree.get_file_sha1(file_id=self.old_id(file_id), 
+                                          path=path)
+
+    def get_file(self, file_id):
+        return self.oldtree.get_file(self.old_id(file_id=file_id))
+
+    def is_executable(self, file_id, path=None):
+        return self.oldtree.is_executable(self.old_id(file_id=file_id), 
+                                          path=path)
+
+def replay_snapshot(repository, oldrevid, newrevid, new_parents):
+    """Replay a commit by simply commiting the same snapshot with different parents.
 
     :param repository: Repository in which the revision is present.
     :param oldrevid: Revision id of the revision to copy.
@@ -243,68 +279,35 @@
     mutter('creating copy %r of %r with new parents %r' % (newrevid, oldrevid, new_parents))
     oldrev = repository.get_revision(oldrevid)
 
+    revprops = dict(oldrev.properties)
+    revprops['rebase-of'] = oldrevid
+
     builder = repository.get_commit_builder(branch=None, parents=new_parents, 
                                   config=Config(),
                                   committer=oldrev.committer,
                                   timestamp=oldrev.timestamp,
                                   timezone=oldrev.timezone,
-                                  revprops=oldrev.properties,
+                                  revprops=revprops,
                                   revision_id=newrevid)
 
     # Check what new_ie.file_id should be
     # use old and new parent inventories to generate new_id map
-    old_parents = oldrev.parent_ids
-    new_id = {}
-    for (oldp, newp) in zip(old_parents, new_parents):
-        oldinv = repository.get_revision_inventory(oldp)
-        newinv = repository.get_revision_inventory(newp)
-        for path, ie in oldinv.iter_entries():
-            if newinv.has_filename(path):
-                new_id[ie.file_id] = newinv.path2id(path)
-
-    i = 0
-    class MapTree:
-        def __init__(self, oldtree, map):
-            self.oldtree = oldtree
-            self.map = map
-
-        def old_id(self, file_id):
-            for x in self.map:
-                if self.map[x] == file_id:
-                    return x
-            return file_id
-
-        def get_file_sha1(self, file_id, path=None):
-            return self.oldtree.get_file_sha1(file_id=self.old_id(file_id), 
-                                              path=path)
-
-        def get_file(self, file_id):
-            return self.oldtree.get_file(self.old_id(file_id=file_id))
-
-        def is_executable(self, file_id, path=None):
-            return self.oldtree.is_executable(self.old_id(file_id=file_id), 
-                                              path=path)
-
-    oldtree = MapTree(repository.revision_tree(oldrevid), new_id)
+    oldtree = MapTree(repository.revision_tree(oldrevid), 
+                      oldrev.parent_ids, new_parents)
     oldinv = repository.get_revision_inventory(oldrevid)
     total = len(oldinv)
     pb = ui.ui_factory.nested_progress_bar()
-    transact = repository.get_transaction()
+    i = 0
     try:
+        transact = repository.get_transaction()
         for path, ie in oldinv.iter_entries():
             pb.update('upgrading file', i, total)
             i += 1
             new_ie = ie.copy()
             if new_ie.revision == oldrevid:
                 new_ie.revision = None
-            def lookup(file_id):
-                try:
-                    return new_id[file_id]
-                except KeyError:
-                    return file_id
-
-            new_ie.file_id = lookup(new_ie.file_id)
-            new_ie.parent_id = lookup(new_ie.parent_id)
+            new_ie.file_id = oldtree.new_id(new_ie.file_id)
+            new_ie.parent_id = oldtree.new_id(new_ie.parent_id)
             builder.record_entry_contents(new_ie, 
                    map(repository.get_revision_inventory, new_parents), 
                    path, oldtree)
@@ -315,12 +318,55 @@
     return builder.commit(oldrev.message)
 
 
-def workingtree_replay(wt):
+def replay_delta_workingtree(wt, oldrevid, newrevid, newparents, map_ids=False,
+        merge_type=None):
+    """Replay a commit in a working tree, with a different base.
+
+    :param wt: Working tree in which to do the replays.
+    :param oldrevid: Old revision id
+    :param newrevid: New revision id
+    :param newparents: New parent revision ids
+    :param map_ids: Whether to map file ids from the rebased revision using 
+        the old and new parent tree file ids.
+    """
+    repository = wt.branch.repository
+    if merge_type is None:
+        from bzrlib.merge import Merge3Merger
+        merge_type = Merge3Merger
+    oldrev = wt.branch.repository.get_revision(oldrevid)
+    # Make sure there are no conflicts or pending merges/changes 
+    # in the working tree
+    if wt.changes_from(wt.basis_tree()).has_changed():
+        raise BzrError("Working tree has uncommitted changes.")
+    wt.branch.generate_revision_history(newparents[0])
+    wt.set_parent_ids(newparents)
+
+    oldtree = repository.revision_tree(oldrevid)
+    basetree = repository.revision_tree(oldrev.parent_ids[0])
+    if map_ids:
+        oldtree = MapTree(oldtree, oldrev.parent_ids, new_parents)
+        basetree = MapTree(basetree, oldrev.parent_ids, new_parents)
+
+    merge = merge_type(working_tree=wt, this_tree=wt, 
+            base_tree=basetree,
+            other_tree=oldtree)
+
+    # commit
+    revprops = dict(oldrev.properties)
+    revprops['rebase-of'] = oldrevid
+    wt.commit(message=oldrev.message, timestamp=oldrev.timestamp, timezone=oldrev.timezone,
+              revprops=revprops, rev_id=newrevid)
+
+def workingtree_replay(wt, map_ids=False):
     """Returns a function that can replay revisions in wt.
 
     :param wt: Working tree in which to do the replays.
+    :param map_ids: Whether to try to map between file ids (False for path-based merge)
     """
     def replay(repository, oldrevid, newrevid, newparents):
-        # TODO
-        pass
+        assert wt.branch.repository == repository
+        return replay_delta_workingtree(wt, oldrevid, newrevid, newparents)
     return replay
+
+class MergeConflicted(BzrError):
+    _fmt = "Conflict during merge"

=== modified file 'test_rebase.py'
--- a/test_rebase.py	2007-07-04 17:00:26 +0000
+++ b/test_rebase.py	2007-07-04 19:55:29 +0000
@@ -14,13 +14,15 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-from bzrlib.errors import UnknownFormatError
+from bzrlib.errors import UnknownFormatError, NoSuchFile
 from bzrlib.revision import NULL_REVISION
 from bzrlib.tests import TestCase, TestCaseWithTransport
 
 from rebase import (marshall_rebase_plan, unmarshall_rebase_plan, 
-                    change_revision_parent, generate_simple_plan,
-                    generate_transpose_plan) 
+                    replay_snapshot, generate_simple_plan,
+                    generate_transpose_plan, rebase_plan_exists,
+                    REBASE_PLAN_FILENAME, write_rebase_plan,
+                    read_rebase_plan, remove_rebase_plan) 
 
 
 class RebasePlanReadWriterTests(TestCase):
@@ -62,11 +64,12 @@
         file('hello', 'w').write('world')
         wt.commit(message='change hello', rev_id="bla2")
         
-        newrev = change_revision_parent(wt.branch.repository, "bla2", "bla4", 
+        newrev = replay_snapshot(wt.branch.repository, "bla2", "bla4", 
                                         ["bloe"])
         self.assertEqual("bla4", newrev)
         self.assertTrue(wt.branch.repository.has_revision(newrev))
         self.assertEqual(["bloe"], wt.branch.repository.revision_parents(newrev))
+        self.assertEqual("bla2", wt.branch.repository.get_revision(newrev).properties["rebase-of"])
 
 
 class PlanCreatorTests(TestCaseWithTransport):
@@ -145,3 +148,60 @@
                 generate_transpose_plan(b.repository, b.repository.get_revision_graph(), 
                 {"bla": "lala"}, lambda y: "new"+y.revision_id))
 
+
+class PlanFileTests(TestCaseWithTransport):
+   def test_rebase_plan_exists_false(self):
+        wt = self.make_branch_and_tree('.')
+        self.assertFalse(rebase_plan_exists(wt))
+
+   def test_rebase_plan_exists_empty(self):
+        wt = self.make_branch_and_tree('.')
+        wt._control_files.put_utf8(REBASE_PLAN_FILENAME, "")
+        self.assertFalse(rebase_plan_exists(wt))
+
+   def test_rebase_plan_exists(self):
+        wt = self.make_branch_and_tree('.')
+        wt._control_files.put_utf8(REBASE_PLAN_FILENAME, "foo")
+        self.assertTrue(rebase_plan_exists(wt))
+
+   def test_remove_rebase_plan(self):
+        wt = self.make_branch_and_tree('.')
+        wt._control_files.put_utf8(REBASE_PLAN_FILENAME, "foo")
+        remove_rebase_plan(wt)
+        self.assertFalse(rebase_plan_exists(wt))
+
+   def test_remove_rebase_plan_twice(self):
+        wt = self.make_branch_and_tree('.')
+        remove_rebase_plan(wt)
+        self.assertFalse(rebase_plan_exists(wt))
+
+   def test_write_rebase_plan(self):
+        wt = self.make_branch_and_tree('.')
+        file('hello', 'w').write('hello world')
+        wt.add('hello')
+        wt.commit(message='add hello', rev_id="bla")
+        write_rebase_plan(wt, 
+                {"oldrev": ("newrev", ["newparent1", "newparent2"])})
+        self.assertEqualDiff("""# Bazaar rebase plan 1
+1 bla
+oldrev newrev newparent1 newparent2
+""", wt._control_files.get(REBASE_PLAN_FILENAME).read())
+
+   def test_read_rebase_plan_nonexistant(self):
+        wt = self.make_branch_and_tree('.')
+        self.assertRaises(NoSuchFile, read_rebase_plan, wt)
+
+   def test_read_rebase_plan_empty(self):
+        wt = self.make_branch_and_tree('.')
+        wt._control_files.put_utf8(REBASE_PLAN_FILENAME, "")
+        self.assertRaises(NoSuchFile, read_rebase_plan, wt)
+        
+   def test_read_rebase_plan(self):
+        wt = self.make_branch_and_tree('.')
+        wt._control_files.put_utf8(REBASE_PLAN_FILENAME, """# Bazaar rebase plan 1
+1 bla
+oldrev newrev newparent1 newparent2
+""")
+        self.assertEquals(((1, "bla"), {"oldrev": ("newrev", ["newparent1", "newparent2"])}),
+                read_rebase_plan(wt))
+




More information about the bazaar-commits mailing list