Rev 4711: Add the criss-cross flip-flop 'bug' for weave merge. in http://bazaar.launchpad.net/~jameinel/bzr/2.0-40412-show-base-weave

John Arbash Meinel john at arbash-meinel.com
Fri Dec 4 01:56:41 GMT 2009


At http://bazaar.launchpad.net/~jameinel/bzr/2.0-40412-show-base-weave

------------------------------------------------------------
revno: 4711
revision-id: john at arbash-meinel.com-20091204015626-s3igiwbj5qiirwoa
parent: john at arbash-meinel.com-20091204005909-r5htm5irxu7n44di
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: 2.0-40412-show-base-weave
timestamp: Thu 2009-12-03 19:56:26 -0600
message:
  Add the criss-cross flip-flop 'bug' for weave merge.
  
  This is the reason lca merge was created. However, also show where lca
  merge fails when there is a more complex ancestry. Since it thinks that
  a missing line is a source of flip-flop errors, when really it is just
  a different ancestry.
-------------- next part --------------
=== modified file 'bzrlib/tests/test_merge.py'
--- a/bzrlib/tests/test_merge.py	2009-09-15 11:22:27 +0000
+++ b/bzrlib/tests/test_merge.py	2009-12-04 01:56:26 +0000
@@ -38,6 +38,7 @@
 from bzrlib.workingtree import WorkingTree
 from bzrlib.transform import TreeTransform
 
+
 class TestMerge(TestCaseWithTransport):
     """Test appending more than one revision"""
 
@@ -524,6 +525,12 @@
         self.add_uncommitted_version(('root', 'C:'), [('root', 'A')], 'fabg')
         return _PlanMerge('B:', 'C:', self.plan_merge_vf, ('root',))
 
+    def test_base_from_plan(self):
+        self.setup_plan_merge()
+        plan = self.plan_merge_vf.plan_merge('B', 'C')
+        pwm = versionedfile.PlanWeaveMerge(plan)
+        self.assertEqual(['a\n', 'b\n', 'c\n'], pwm.base_from_plan())
+
     def test_unique_lines(self):
         plan = self.setup_plan_merge()
         self.assertEqual(plan._unique_lines(
@@ -827,6 +834,111 @@
                           ('unchanged', 'f\n'),
                           ('unchanged', 'g\n')],
                          list(plan))
+        plan = self.plan_merge_vf.plan_lca_merge('F', 'G')
+        # This is one of the main differences between plan_merge and
+        # plan_lca_merge. plan_lca_merge generates a conflict for 'x => z',
+        # because 'x' was not present in one of the bases. However, in this
+        # case it is spurious because 'x' does not exist in the global base A.
+        self.assertEqual([
+                          ('unchanged', 'h\n'),
+                          ('unchanged', 'a\n'),
+                          ('conflicted-a', 'x\n'),
+                          ('new-b', 'z\n'),
+                          ('unchanged', 'c\n'),
+                          ('unchanged', 'd\n'),
+                          ('unchanged', 'y\n'),
+                          ('unchanged', 'f\n'),
+                          ('unchanged', 'g\n')],
+                         list(plan))
+
+    def test_criss_cross_flip_flop(self):
+        # This is specificly trying to trigger problems when using limited
+        # ancestry and weaves. The ancestry graph looks like:
+        #       XX      unused ancestor, should not show up in the weave
+        #       |
+        #       A       Unique LCA
+        #      / \  
+        #     B   C     B & C both introduce a new line
+        #     |\ /|  
+        #     | X |  
+        #     |/ \| 
+        #     D   E     B & C are both merged, so both are common ancestors
+        #               In the process of merging, both sides order the new
+        #               lines differently
+        #
+        self.add_rev('root', 'XX', [], 'qrs')
+        self.add_rev('root', 'A', ['XX'], 'abcdef')
+        self.add_rev('root', 'B', ['A'], 'abcdgef')
+        self.add_rev('root', 'C', ['A'], 'abcdhef')
+        self.add_rev('root', 'D', ['B', 'C'], 'abcdghef')
+        self.add_rev('root', 'E', ['C', 'B'], 'abcdhgef')
+        plan = list(self.plan_merge_vf.plan_merge('D', 'E'))
+        self.assertEqual([
+                          ('unchanged', 'a\n'),
+                          ('unchanged', 'b\n'),
+                          ('unchanged', 'c\n'),
+                          ('unchanged', 'd\n'),
+                          ('new-b', 'h\n'),
+                          ('unchanged', 'g\n'),
+                          ('killed-b', 'h\n'),
+                          ('unchanged', 'e\n'),
+                          ('unchanged', 'f\n'),
+                         ], plan)
+        pwm = versionedfile.PlanWeaveMerge(plan)
+        self.assertEqualDiff('\n'.join('abcdghef') + '\n',
+                             ''.join(pwm.base_from_plan()))
+        # Reversing the order reverses the merge plan, and final order of 'hg'
+        # => 'gh'
+        plan = list(self.plan_merge_vf.plan_merge('E', 'D'))
+        self.assertEqual([
+                          ('unchanged', 'a\n'),
+                          ('unchanged', 'b\n'),
+                          ('unchanged', 'c\n'),
+                          ('unchanged', 'd\n'),
+                          ('new-b', 'g\n'),
+                          ('unchanged', 'h\n'),
+                          ('killed-b', 'g\n'),
+                          ('unchanged', 'e\n'),
+                          ('unchanged', 'f\n'),
+                         ], plan)
+        pwm = versionedfile.PlanWeaveMerge(plan)
+        self.assertEqualDiff('\n'.join('abcdhgef') + '\n',
+                             ''.join(pwm.base_from_plan()))
+        # This is where lca differs, in that it (fairly correctly) determines
+        # that there is a conflict because both sides resolved the merge
+        # differently
+        plan = list(self.plan_merge_vf.plan_lca_merge('D', 'E'))
+        self.assertEqual([
+                          ('unchanged', 'a\n'),
+                          ('unchanged', 'b\n'),
+                          ('unchanged', 'c\n'),
+                          ('unchanged', 'd\n'),
+                          ('conflicted-b', 'h\n'),
+                          ('unchanged', 'g\n'),
+                          ('conflicted-a', 'h\n'),
+                          ('unchanged', 'e\n'),
+                          ('unchanged', 'f\n'),
+                         ], plan)
+        pwm = versionedfile.PlanWeaveMerge(plan)
+        self.assertEqualDiff('\n'.join('abcdhghef') + '\n',
+                             ''.join(pwm.base_from_plan()))
+        # Reversing it changes what line is doubled, but still gives a
+        # double-conflict
+        plan = list(self.plan_merge_vf.plan_lca_merge('E', 'D'))
+        self.assertEqual([
+                          ('unchanged', 'a\n'),
+                          ('unchanged', 'b\n'),
+                          ('unchanged', 'c\n'),
+                          ('unchanged', 'd\n'),
+                          ('conflicted-b', 'g\n'),
+                          ('unchanged', 'h\n'),
+                          ('conflicted-a', 'g\n'),
+                          ('unchanged', 'e\n'),
+                          ('unchanged', 'f\n'),
+                         ], plan)
+        pwm = versionedfile.PlanWeaveMerge(plan)
+        self.assertEqualDiff('\n'.join('abcdghgef') + '\n',
+                             ''.join(pwm.base_from_plan()))
 
     def assertRemoveExternalReferences(self, filtered_parent_map,
                                        child_map, tails, parent_map):

=== modified file 'bzrlib/versionedfile.py'
--- a/bzrlib/versionedfile.py	2009-12-04 00:59:09 +0000
+++ b/bzrlib/versionedfile.py	2009-12-04 01:56:26 +0000
@@ -1512,6 +1512,12 @@
             #      2) Exclude them in .BASE, because they aren't in all BASEs.
             #         diff3 then sees 'b' being added by both sides before and
             #         after 'a'. Which gives MbabN (no conflicts)
+            #      Also note that --weave output isn't a great representation,
+            #      as it produces a 'clean' flip-flop. If you merge MabN =>
+            #      MbaN you get MabN, if you reverse it you get the reverse.
+            #      The BASE in both cases is just the current text, with the
+            #      'other' 'b' line shown as killed-in-other. Which is why it
+            #      merges cleanly.
             if state in ('killed-a', 'killed-b', 'killed-both', 'unchanged',
                          'conflicted-a', 'conflicted-b'):
                 # If unchanged, then this line is straight from base. If a or b



More information about the bazaar-commits mailing list