Rev 6: Add somewhat more complex plan generation function, rebase implementation. in file:///data/jelmer/bzr-rebase/trunk/

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


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

------------------------------------------------------------
revno: 6
revision-id: jelmer at samba.org-20070704170026-k87l51j95qnmgwh1
parent: jelmer at samba.org-20070704142405-9diewhvola010xec
committer: Jelmer Vernooij <jelmer at samba.org>
branch nick: bzr-rebase
timestamp: Wed 2007-07-04 19:00:26 +0200
message:
  Add somewhat more complex plan generation function, rebase implementation. 
modified:
  README                         readme-20070626220000-c62864tuxlldx6uc-1
  __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 'README'
--- a/README	2007-07-04 13:05:30 +0000
+++ b/README	2007-07-04 17:00:26 +0000
@@ -5,7 +5,8 @@
 How it works
 ============
 The plugin will start off by writing a plan for the rebase, which it 
-will write to .bzr/checkout/rebase-state. If the rebase is interrupted and 
+will write to .bzr/checkout/rebase-state. If the rebase is interrupted 
+(conflicts that have to be resolved by the user) and 
 needs to be continued or aborted, it will read this file to see what needs 
 (still) needs to be done.
 

=== modified file '__init__.py'
--- a/__init__.py	2007-07-04 13:05:30 +0000
+++ b/__init__.py	2007-07-04 17:00:26 +0000
@@ -30,7 +30,7 @@
     def run(self, upstream_location, onto=None):
         from rebase import (generate_simple_plan, rebase, 
                             rebase_plan_exists, write_rebase_plan, 
-                            read_rebase_plan)
+                            read_rebase_plan, workingtree_replay)
         upstream = Branch.open(upstream_location)
         wt = WorkingTree.open('.')
         wt.write_lock()
@@ -59,7 +59,7 @@
 
             # Start executing plan
             try:
-                rebase(wt, replace_map)
+                rebase(wt.branch.repository, replace_map, workingtree_replay(wt))
             except Conflict:
                 raise BzrCommandError("A conflict occurred applying a patch. Resolve the conflict and run 'bzr rebase-continue' or run 'bzr rebase-abort'.")
             # Remove plan file
@@ -91,7 +91,7 @@
     
     @display_command
     def run(self):
-        from rebase import read_rebase_plan, rebase_plan_exists
+        from rebase import read_rebase_plan, rebase_plan_exists, workingtree_replay
         wt = WorkingTree.open('.')
         wt.write_lock()
         try:
@@ -103,7 +103,7 @@
 
             try:
                 # Start executing plan from current Branch.last_revision()
-                rebase(wt, replace_map)
+                rebase(wt.branch.repository, replace_map, workingtree_replay(wt))
             except Conflict:
                 raise BzrCommandError("A conflict occurred applying a patch. Resolve the conflict and run 'bzr rebase-continue' or run 'bzr rebase-abort'.")
             # Remove plan file  

=== modified file 'rebase.py'
--- a/rebase.py	2007-07-04 14:24:05 +0000
+++ b/rebase.py	2007-07-04 17:00:26 +0000
@@ -100,31 +100,134 @@
         replace_map[pts[0]] = (pts[1], pts[2:])
     return (last_revision_info, replace_map)
 
-def generate_simple_plan(subject_branch, start_revid, onto_revid):
+
+def regenerate_default_revid(rev):
+    return gen_revision_id(rev.committer, rev.timestamp)
+
+
+def generate_simple_plan(repository, history, start_revid, onto_revid, 
+                         generate_revid=regenerate_default_revid):
     """Create a simple rebase plan that replays history based 
-    on one revision being mapped.
+    on one revision being replayed on top of another.
 
-    :param subject_branch: Branch that will be changed
-    :param start_revid: Revision at which to start replaying
-    :param onto_revid: Revision on top of which to replay
+    :param repository: Repository
+    :param history: Revision history
+    :param start_revid: Id of revision at which to start replaying
+    :param onto_revid: Id of revision on top of which to replay
+    :param generate_revid: Function for generating new revision ids
 
     :return: replace map
     """
-    replace_map = {}
-
-    need_rewrite = []
-
-    for revid in need_rewrite:
-        replace_map[revid] = gen_revision_id()
-    # TODO
-
-def rebase(wt, replace_map):
+    assert start_revid in history
+    assert repository.has_revision(start_revid)
+    assert repository.has_revision(onto_revid)
+    replace_map = {}
+    i = history.index(start_revid)
+    new_parent = onto_revid
+    for oldrevid in history[i:]: 
+        rev = repository.get_revision(oldrevid)
+        parents = rev.parent_ids
+        assert len(parents) == 0 or \
+                parents[0] == history[history.index(oldrevid)-1]
+        parents[0] = new_parent
+        newrevid = generate_revid(rev)
+        assert newrevid != oldrevid
+        replace_map[oldrevid] = (newrevid, parents)
+        new_parent = newrevid
+    return replace_map
+
+
+def generate_transpose_plan(repository, graph, renames, 
+        generate_revid=regenerate_default_revid):
+    """Create a rebase plan that replaces the bottom of 
+    a revision graph.
+
+    :param repository: Repository
+    :param graph: Revision graph in which to operate
+    :param renames: Renames of revision
+    :param generate_revid: Function for creating new revision ids
+    """
+    replace_map = {}
+    todo = []
+    for r in renames:
+        replace_map[r] = (renames[r], 
+                          repository.revision_parents(renames[r]))
+        todo.append(r)
+
+    def find_revision_children(revid):
+        for x in graph: 
+            if revid in graph[x]: 
+                yield x
+
+    while len(todo) > 0:
+        r = todo.pop()
+        # Find children of r in graph
+        children = list(find_revision_children(r))
+        # Add entry for them in replace_map
+        for c in children:
+            rev = repository.get_revision(c)
+            if replace_map.has_key(c):
+                parents = replace_map[c][1]
+            else:
+                parents = rev.parent_ids
+            # replace r in parents with replace_map[r][0]
+            parents[parents.index(r)] = replace_map[r][0]
+            replace_map[c] = (generate_revid(rev), parents)
+            assert replace_map[c][0] != rev.revision_id
+        # Add them to todo[]
+        todo.extend(children)
+
+    return replace_map
+
+
+def rebase(repository, replace_map, replay_fn):
     """Rebase a working tree according to the specified map.
 
-    :param wt: Working tree to rebase
+    :param repository: Repository that contains the revisions
     :param replace_map: Dictionary with revisions to (optionally) rewrite
+    :param merge_fn: Function for replaying a revision
     """
-    #TODO
+    todo = []
+    for revid in replace_map:
+        if not repository.has_revision(replace_map[revid][0]):
+            todo.append(revid)
+    dependencies = {}
+
+    # Figure out the dependencies
+    for revid in todo:
+        possible = True
+        for p in replace_map[revid][1]:
+            if repository.has_revision(p):
+                continue
+            possible = False
+            if not dependencies.has_key(p):
+                dependencies[p] = []
+            dependencies[p].append(revid)
+
+    pb = ui.ui_factory.nested_progress_bar()
+    i = 0
+    try:
+        while len(todo) > 0:
+            pb.update('rebase revisions', i, len(replace_map))
+            i += 1
+            revid = todo.pop()
+            (newrevid, newparents) = replace_map[revid]
+            if not all(map(repository.has_revision, newparents)):
+                # Not all parents present yet, avoid for now
+                continue
+            if repository.has_revision(newrevid):
+                # Was already converted, no need to worry about it again
+                continue
+            replay_fn(repository, revid, newrevid, newparents)
+            assert repository.has_revision(newrevid)
+            assert repository.revision_parents(newrevid) == newparents
+            if dependencies.has_key(newrevid):
+                todo.extend(dependencies[newrevid])
+                del dependencies[newrevid]
+    finally:
+        pb.finished()
+        
+    assert all(map(repository.has_revision, [replace_map[r][0] for r in replace_map]))
      
 
 # Change the parent of a revision
@@ -212,3 +315,12 @@
     return builder.commit(oldrev.message)
 
 
+def workingtree_replay(wt):
+    """Returns a function that can replay revisions in wt.
+
+    :param wt: Working tree in which to do the replays.
+    """
+    def replay(repository, oldrevid, newrevid, newparents):
+        # TODO
+        pass
+    return replay

=== modified file 'test_rebase.py'
--- a/test_rebase.py	2007-07-04 14:24:05 +0000
+++ b/test_rebase.py	2007-07-04 17:00:26 +0000
@@ -15,10 +15,12 @@
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 from bzrlib.errors import UnknownFormatError
+from bzrlib.revision import NULL_REVISION
 from bzrlib.tests import TestCase, TestCaseWithTransport
 
 from rebase import (marshall_rebase_plan, unmarshall_rebase_plan, 
-                    change_revision_parent) 
+                    change_revision_parent, generate_simple_plan,
+                    generate_transpose_plan) 
 
 
 class RebasePlanReadWriterTests(TestCase):
@@ -65,3 +67,81 @@
         self.assertEqual("bla4", newrev)
         self.assertTrue(wt.branch.repository.has_revision(newrev))
         self.assertEqual(["bloe"], wt.branch.repository.revision_parents(newrev))
+
+
+class PlanCreatorTests(TestCaseWithTransport):
+    def test_simple_plan_creator(self):
+        wt = self.make_branch_and_tree('.')
+        b = wt.branch
+        file('hello', 'w').write('hello world')
+        wt.add('hello')
+        wt.commit(message='add hello', rev_id="bla")
+        file('hello', 'w').write('world')
+        wt.commit(message='change hello', rev_id="bloe")
+        wt.set_last_revision("bla")
+        b.set_revision_history(["bla"])
+        file('hello', 'w').write('world')
+        wt.commit(message='change hello', rev_id="bla2")
+
+        self.assertEquals({'bla2': ('newbla2', ["bloe"])}, 
+                generate_simple_plan(b.repository, b.revision_history(), "bla2", "bloe", 
+                    lambda y: "new"+y.revision_id))
+     
+    def test_simple_plan_creator_extra_history(self):
+        wt = self.make_branch_and_tree('.')
+        b = wt.branch
+        file('hello', 'w').write('hello world')
+        wt.add('hello')
+        wt.commit(message='add hello', rev_id="bla")
+        file('hello', 'w').write('world')
+        wt.commit(message='change hello', rev_id="bloe")
+        wt.set_last_revision("bla")
+        b.set_revision_history(["bla"])
+        file('hello', 'w').write('world')
+        wt.commit(message='change hello', rev_id="bla2")
+        file('hello', 'w').write('universe')
+        wt.commit(message='change hello again', rev_id="bla3")
+
+        self.assertEquals({'bla2': ('newbla2', ["bloe"]), 'bla3': ('newbla3', ['newbla2'])}, 
+                generate_simple_plan(b.repository, b.revision_history(), "bla2", "bloe", 
+                    lambda y: "new"+y.revision_id))
+ 
+
+    def test_generate_transpose_plan(self):
+        wt = self.make_branch_and_tree('.')
+        b = wt.branch
+        file('hello', 'w').write('hello world')
+        wt.add('hello')
+        wt.commit(message='add hello', rev_id="bla")
+        file('hello', 'w').write('world')
+        wt.commit(message='change hello', rev_id="bloe")
+        wt.set_last_revision("bla")
+        b.set_revision_history(["bla"])
+        file('hello', 'w').write('world')
+        wt.commit(message='change hello', rev_id="bla2")
+        file('hello', 'w').write('universe')
+        wt.commit(message='change hello again', rev_id="bla3")
+        wt.set_last_revision("bla")
+        b.set_revision_history(["bla"])
+        file('hello', 'w').write('somebar')
+        wt.commit(message='change hello yet again', rev_id="blie")
+        wt.set_last_revision(NULL_REVISION)
+        b.set_revision_history([])
+        wt.add('hello')
+        wt.commit(message='add hello', rev_id="lala")
+
+        self.assertEquals({
+                'bla': ('lala', []),
+                'blie': ('newblie', ['lala']),
+            },
+                generate_transpose_plan(b.repository, b.repository.get_revision_graph("blie"), 
+                {"bla": "lala"}, lambda y: "new"+y.revision_id))
+        self.assertEquals({
+                'bla': ('lala', []),
+                'bla2': ('newbla2', ['lala']),
+                'bla3': ('newbla3', ['newbla2']),
+                'blie': ('newblie', ['lala']),
+                'bloe': ('newbloe', ['lala'])},
+                generate_transpose_plan(b.repository, b.repository.get_revision_graph(), 
+                {"bla": "lala"}, lambda y: "new"+y.revision_id))
+




More information about the bazaar-commits mailing list