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