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