Rev 5021: (gerard) Update performs two merges in a more logical order but stop in file:///home/pqm/archives/thelove/bzr/%2Btrunk/
Canonical.com Patch Queue Manager
pqm at pqm.ubuntu.com
Wed Feb 10 16:25:31 GMT 2010
At file:///home/pqm/archives/thelove/bzr/%2Btrunk/
------------------------------------------------------------
revno: 5021 [merge]
revision-id: pqm at pqm.ubuntu.com-20100210162528-00g29u0ex6vzv914
parent: pqm at pqm.ubuntu.com-20100210091016-2asq6wznqcb8e97s
parent: v.ladeuil+lp at free.fr-20100210154603-k4no1gvfuqpzrw7p
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Wed 2010-02-10 16:25:28 +0000
message:
(gerard) Update performs two merges in a more logical order but stop
on conflicts
modified:
NEWS NEWS-20050323055033-4e00b5db738777ff
bzrlib/tests/blackbox/test_commit.py test_commit.py-20060212094538-ae88fc861d969db0
bzrlib/tests/blackbox/test_update.py test_update.py-20060212125639-c4dad1a5c56d5919
bzrlib/tests/per_workingtree/test_workingtree.py test_workingtree.py-20060203003124-817757d3e31444fb
bzrlib/workingtree.py workingtree.py-20050511021032-29b6ec0a681e02e3
=== modified file 'NEWS'
--- a/NEWS 2010-02-10 00:28:39 +0000
+++ b/NEWS 2010-02-10 15:46:03 +0000
@@ -77,6 +77,10 @@
the same value to avoid confusing ``make`` and other date-based build
systems. (Robert Collins, #515631)
+* ``bzr update`` performs the two merges in a more logical order and will stop
+ when it encounters conflicts.
+ (Gerard Krol, #113809)
+
Improvements
************
=== modified file 'bzrlib/tests/blackbox/test_commit.py'
--- a/bzrlib/tests/blackbox/test_commit.py 2010-01-25 17:48:22 +0000
+++ b/bzrlib/tests/blackbox/test_commit.py 2010-02-10 15:46:03 +0000
@@ -343,22 +343,31 @@
trunk = self.make_branch_and_tree('trunk')
u1 = trunk.branch.create_checkout('u1')
- self.build_tree_contents([('u1/hosts', 'initial contents')])
+ self.build_tree_contents([('u1/hosts', 'initial contents\n')])
u1.add('hosts')
self.run_bzr('commit -m add-hosts u1')
u2 = trunk.branch.create_checkout('u2')
- self.build_tree_contents([('u2/hosts', 'altered in u2')])
+ self.build_tree_contents([('u2/hosts', 'altered in u2\n')])
self.run_bzr('commit -m checkin-from-u2 u2')
# make an offline commits
- self.build_tree_contents([('u1/hosts', 'first offline change in u1')])
+ self.build_tree_contents([('u1/hosts', 'first offline change in u1\n')])
self.run_bzr('commit -m checkin-offline --local u1')
# now try to pull in online work from u2, and then commit our offline
# work as a merge
# retcode 1 as we expect a text conflict
self.run_bzr('update u1', retcode=1)
+ self.assertFileEqual('''\
+<<<<<<< TREE
+first offline change in u1
+=======
+altered in u2
+>>>>>>> MERGE-SOURCE
+''',
+ 'u1/hosts')
+
self.run_bzr('resolved u1/hosts')
# add a text change here to represent resolving the merge conflicts in
# favour of a new version of the file not identical to either the u1
=== modified file 'bzrlib/tests/blackbox/test_update.py'
--- a/bzrlib/tests/blackbox/test_update.py 2009-12-23 06:31:19 +0000
+++ b/bzrlib/tests/blackbox/test_update.py 2010-02-10 13:39:38 +0000
@@ -171,9 +171,9 @@
# get all three files and a pending merge.
out, err = self.run_bzr('update checkout')
self.assertEqual('', out)
- self.assertEqualDiff("""+N file
+ self.assertEqualDiff("""+N file_b
All changes applied successfully.
-+N file_b
++N file
All changes applied successfully.
Updated to revision 1 of branch %s
Your local commits will now show as pending merges with 'bzr status', and can be committed with 'bzr commit'.
@@ -242,9 +242,6 @@
self.run_bzr('update checkout')
def test_update_dash_r(self):
- # Test that 'bzr update' works correctly when you have
- # an update in the master tree, and a lightweight checkout
- # which has merged another branch
master = self.make_branch_and_tree('master')
os.chdir('master')
self.build_tree(['./file1'])
@@ -266,9 +263,6 @@
self.assertEquals(['m1'], master.get_parent_ids())
def test_update_dash_r_outside_history(self):
- # Test that 'bzr update' works correctly when you have
- # an update in the master tree, and a lightweight checkout
- # which has merged another branch
master = self.make_branch_and_tree('master')
self.build_tree(['master/file1'])
master.add(['file1'])
@@ -315,3 +309,61 @@
2>All changes applied successfully.
2>Updated to revision 2 of branch .../master
''')
+
+ def test_update_checkout_prevent_double_merge(self):
+ """"Launchpad bug 113809 in bzr "update performs two merges"
+ https://launchpad.net/bugs/113809"""
+ master = self.make_branch_and_tree('master')
+ self.build_tree_contents([('master/file', 'initial contents\n')])
+ master.add(['file'])
+ master.commit('one', rev_id='m1')
+
+ checkout = master.branch.create_checkout('checkout')
+ lightweight = checkout.branch.create_checkout('lightweight',
+ lightweight=True)
+
+ # time to create a mess
+ # add a commit to the master
+ self.build_tree_contents([('master/file', 'master\n')])
+ master.commit('two', rev_id='m2')
+ self.build_tree_contents([('master/file', 'master local changes\n')])
+
+ # local commit on the checkout
+ self.build_tree_contents([('checkout/file', 'checkout\n')])
+ checkout.commit('tree', rev_id='c2', local=True)
+ self.build_tree_contents([('checkout/file',
+ 'checkout local changes\n')])
+
+ # lightweight
+ self.build_tree_contents([('lightweight/file',
+ 'lightweight local changes\n')])
+
+ # now update (and get conflicts)
+ out, err = self.run_bzr('update lightweight', retcode=1)
+ self.assertEqual('', out)
+ self.assertFileEqual('''\
+<<<<<<< TREE
+lightweight local changes
+=======
+checkout
+>>>>>>> MERGE-SOURCE
+''',
+ 'lightweight/file')
+
+ # resolve it
+ self.build_tree_contents([('lightweight/file',
+ 'lightweight+checkout\n')])
+ self.run_bzr('resolve lightweight/file')
+
+ # check we get the second conflict
+ out, err = self.run_bzr('update lightweight', retcode=1)
+ self.assertEqual('', out)
+ self.assertFileEqual('''\
+<<<<<<< TREE
+lightweight+checkout
+=======
+master
+>>>>>>> MERGE-SOURCE
+''',
+ 'lightweight/file')
+
=== modified file 'bzrlib/tests/per_workingtree/test_workingtree.py'
--- a/bzrlib/tests/per_workingtree/test_workingtree.py 2010-01-13 23:06:42 +0000
+++ b/bzrlib/tests/per_workingtree/test_workingtree.py 2010-02-10 15:39:30 +0000
@@ -23,6 +23,7 @@
from bzrlib import (
branch,
+ branchbuilder,
bzrdir,
errors,
osutils,
@@ -949,6 +950,112 @@
os.link = real_os_link
+class TestWorkingTreeUpdate(TestCaseWithWorkingTree):
+
+ def make_diverged_master_branch(self):
+ """
+ B: wt.branch.last_revision()
+ M: wt.branch.get_master_branch().last_revision()
+ W: wt.last_revision()
+
+
+ 1
+ |\
+ B-2 3
+ | |
+ 4 5-M
+ |
+ W
+ """
+ builder = branchbuilder.BranchBuilder(
+ self.get_transport(),
+ format=self.workingtree_format._matchingbzrdir)
+ builder.start_series()
+ # mainline
+ builder.build_snapshot(
+ '1', None,
+ [('add', ('', 'root-id', 'directory', '')),
+ ('add', ('file1', 'file1-id', 'file', 'file1 content\n'))])
+ # branch
+ builder.build_snapshot('2', ['1'], [])
+ builder.build_snapshot(
+ '4', ['2'],
+ [('add', ('file4', 'file4-id', 'file', 'file4 content\n'))])
+ # master
+ builder.build_snapshot('3', ['1'], [])
+ builder.build_snapshot(
+ '5', ['3'],
+ [('add', ('file5', 'file5-id', 'file', 'file5 content\n'))])
+ builder.finish_series()
+ return builder, builder._branch.last_revision()
+
+ def make_checkout_and_master(self, builder, wt_path, master_path, wt_revid,
+ master_revid=None, branch_revid=None):
+ """Build a lightweight checkout and its master branch."""
+ if master_revid is None:
+ master_revid = wt_revid
+ if branch_revid is None:
+ branch_revid = master_revid
+ final_branch = builder.get_branch()
+ # The master branch
+ master = final_branch.bzrdir.sprout(master_path,
+ master_revid).open_branch()
+ # The checkout
+ wt = self.make_branch_and_tree(wt_path)
+ wt.pull(final_branch, stop_revision=wt_revid)
+ wt.branch.pull(final_branch, stop_revision=branch_revid, overwrite=True)
+ try:
+ wt.branch.bind(master)
+ except errors.UpgradeRequired:
+ raise TestNotApplicable(
+ "Can't bind %s" % wt.branch._format.__class__)
+ return wt, master
+
+ def test_update_remove_commit(self):
+ """Update should remove revisions when the branch has removed
+ some commits.
+
+ We want to revert 4, so that strating with the
+ make_diverged_master_branch() graph the final result should be
+ equivalent to:
+
+ 1
+ |\
+ 3 2
+ | |\
+ MB-5 | 4
+ |/
+ W
+
+ And the changes in 4 have been removed from the WT.
+ """
+ builder, tip = self.make_diverged_master_branch()
+ wt, master = self.make_checkout_and_master(
+ builder, 'checkout', 'master', '4',
+ master_revid=tip, branch_revid='2')
+ # First update the branch
+ old_tip = wt.branch.update()
+ self.assertEqual('2', old_tip)
+ # No conflicts should occur
+ self.assertEqual(0, wt.update(old_tip=old_tip))
+ # We are in sync with the master
+ self.assertEqual(tip, wt.branch.last_revision())
+ # We have the right parents ready to be committed
+ self.assertEqual(['5', '2'], wt.get_parent_ids())
+
+ def test_update_revision(self):
+ builder, tip = self.make_diverged_master_branch()
+ wt, master = self.make_checkout_and_master(
+ builder, 'checkout', 'master', '4',
+ master_revid=tip, branch_revid='2')
+ self.assertEqual(0, wt.update(revision='1'))
+ self.assertEqual('1', wt.last_revision())
+ self.assertEqual(tip, wt.branch.last_revision())
+ self.failUnlessExists('checkout/file1')
+ self.failIfExists('checkout/file4')
+ self.failIfExists('checkout/file5')
+
+
class TestIllegalPaths(TestCaseWithWorkingTree):
def test_bad_fs_path(self):
=== modified file 'bzrlib/workingtree.py'
--- a/bzrlib/workingtree.py 2010-02-09 19:04:02 +0000
+++ b/bzrlib/workingtree.py 2010-02-10 15:46:03 +0000
@@ -2250,7 +2250,7 @@
# We MUST save it even if an error occurs, because otherwise the users
# local work is unreferenced and will appear to have been lost.
#
- result = 0
+ nb_conflicts = 0
try:
last_rev = self.get_parent_ids()[0]
except IndexError:
@@ -2260,26 +2260,48 @@
else:
if revision not in self.branch.revision_history():
raise errors.NoSuchRevision(self.branch, revision)
+
+ old_tip = old_tip or _mod_revision.NULL_REVISION
+
+ if not _mod_revision.is_null(old_tip) and old_tip != last_rev:
+ # the branch we are bound to was updated
+ # merge those changes in first
+ base_tree = self.basis_tree()
+ other_tree = self.branch.repository.revision_tree(old_tip)
+ nb_conflicts = merge.merge_inner(self.branch, other_tree,
+ base_tree, this_tree=self,
+ change_reporter=change_reporter)
+ if nb_conflicts:
+ self.add_parent_tree((old_tip, other_tree))
+ trace.note('Rerun update after fixing the conflicts.')
+ return nb_conflicts
+
if last_rev != _mod_revision.ensure_null(revision):
- # merge tree state up to specified revision.
+ # the working tree is up to date with the branch
+ # we can merge the specified revision from master
+ to_tree = self.branch.repository.revision_tree(revision)
+ to_root_id = to_tree.get_root_id()
+
basis = self.basis_tree()
basis.lock_read()
try:
- to_tree = self.branch.repository.revision_tree(revision)
- to_root_id = to_tree.get_root_id()
if (basis.inventory.root is None
or basis.inventory.root.file_id != to_root_id):
self.set_root_id(to_root_id)
self.flush()
- result += merge.merge_inner(
- self.branch,
- to_tree,
- basis,
- this_tree=self,
- change_reporter=change_reporter)
- self.set_last_revision(revision)
finally:
basis.unlock()
+
+ # determine the branch point
+ graph = self.branch.repository.get_graph()
+ base_rev_id = graph.find_unique_lca(self.branch.last_revision(),
+ last_rev)
+ base_tree = self.branch.repository.revision_tree(base_rev_id)
+
+ nb_conflicts = merge.merge_inner(self.branch, to_tree, base_tree,
+ this_tree=self,
+ change_reporter=change_reporter)
+ self.set_last_revision(revision)
# TODO - dedup parents list with things merged by pull ?
# reuse the tree we've updated to to set the basis:
parent_trees = [(revision, to_tree)]
@@ -2292,42 +2314,12 @@
for parent in merges:
parent_trees.append(
(parent, self.branch.repository.revision_tree(parent)))
- if (old_tip is not None and not _mod_revision.is_null(old_tip)):
+ if not _mod_revision.is_null(old_tip):
parent_trees.append(
(old_tip, self.branch.repository.revision_tree(old_tip)))
self.set_parent_trees(parent_trees)
last_rev = parent_trees[0][0]
- else:
- # the working tree had the same last-revision as the master
- # branch did. We may still have pivot local work from the local
- # branch into old_tip:
- if (old_tip is not None and not _mod_revision.is_null(old_tip)):
- self.add_parent_tree_id(old_tip)
- if (old_tip is not None and not _mod_revision.is_null(old_tip)
- and old_tip != last_rev):
- # our last revision was not the prior branch last revision
- # and we have converted that last revision to a pending merge.
- # base is somewhere between the branch tip now
- # and the now pending merge
-
- # Since we just modified the working tree and inventory, flush out
- # the current state, before we modify it again.
- # TODO: jam 20070214 WorkingTree3 doesn't require this, dirstate
- # requires it only because TreeTransform directly munges the
- # inventory and calls tree._write_inventory(). Ultimately we
- # should be able to remove this extra flush.
- self.flush()
- graph = self.branch.repository.get_graph()
- base_rev_id = graph.find_unique_lca(revision, old_tip)
- base_tree = self.branch.repository.revision_tree(base_rev_id)
- other_tree = self.branch.repository.revision_tree(old_tip)
- result += merge.merge_inner(
- self.branch,
- other_tree,
- base_tree,
- this_tree=self,
- change_reporter=change_reporter)
- return result
+ return nb_conflicts
def _write_hashcache_if_dirty(self):
"""Write out the hashcache if it is dirty."""
More information about the bazaar-commits
mailing list