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