Rev 3077: Implement a detection and workaround for when find_ancestry gives bad results. in http://bzr.arbash-meinel.com/branches/bzr/0.93-dev/status_after_merge_172657

John Arbash Meinel john at arbash-meinel.com
Fri Dec 7 21:22:49 GMT 2007


At http://bzr.arbash-meinel.com/branches/bzr/0.93-dev/status_after_merge_172657

------------------------------------------------------------
revno: 3077
revision-id:john at arbash-meinel.com-20071207212220-qnh95jk65lc2f0xt
parent: john at arbash-meinel.com-20071204195523-3tong8h1fwvx0xgy
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: status_after_merge_172657
timestamp: Fri 2007-12-07 15:22:20 -0600
message:
  Implement a detection and workaround for when find_ancestry gives bad results.
modified:
  bzrlib/status.py               status.py-20050505062338-431bfa63ec9b19e6
  bzrlib/tests/test_status.py    test_status.py-20060516190614-fbf6432e4a6e8aa5
-------------- next part --------------
=== modified file 'bzrlib/status.py'
--- a/bzrlib/status.py	2007-12-04 19:55:23 +0000
+++ b/bzrlib/status.py	2007-12-07 21:22:20 +0000
@@ -19,6 +19,7 @@
 from bzrlib import (
     delta as _mod_delta,
     osutils,
+    trace,
     tree,
     tsort,
     revision as _mod_revision,
@@ -157,11 +158,40 @@
         to_file.write('pending merges:\n')
     ignore = set([None, last_revision, _mod_revision.NULL_REVISION])
     graph = branch.repository.get_graph()
+    last_ancestry = None
     for merge in pending:
         # Find all of the revisions in the merge source, which are not in the
         # last committed revision.
         # We don't care about last_extra
+        # XXX: find_difference is known incomplete when there are history
+        #      shortcuts. This generally manifests itself by having
+        #      NULL_REVISION show up in the "uncommon" revisions for one
+        #      side.
         last_extra, merge_extra = graph.find_difference(last_revision, merge)
+        if (_mod_revision.NULL_REVISION in last_extra
+            or _mod_revision.NULL_REVISION in merge_extra):
+            # Fall back to the slow method. Consider warning the user rather
+            # than just degrading performance silently
+            trace.mutter('History shortcut detected, using slow algorithm for'
+                         ' pending revision detection.'
+                         ' revision ids:\n  %s\n  %s', last_revision, merge)
+            if last_ancestry is None:
+                try:
+                    last_ancestry = set(branch.repository.get_ancestry(
+                                        last_revision, topo_sorted=False))
+                except errors.NoSuchRevision: # ghost last-revision
+                    last_ancestry = set([last_revision])
+                # repo.get_ancestry always includes None in the list, for some
+                # historical reason
+                last_ancestry.discard(None)
+            try:
+                merge_extra = set(branch.repository.get_ancestry(merge,
+                                        topo_sorted=False))
+            except errors.NoSuchRevision: # Ghost merge
+                merge_extra = set([merge])
+            merge_extra.difference_update(last_ancestry)
+            merge_extra.discard(None)
+
         # Now that we have the revisions, we need to sort them to get a proper
         # listing. We want to sort in reverse topological order (which
         # MergeSorter gives us). MergeSorter requires that there are no
@@ -169,11 +199,11 @@
         # present nodes.
         merge_extra.discard(_mod_revision.NULL_REVISION)
         merged_graph = {}
-        for merge, parents in zip(merge_extra, graph.get_parents(merge_extra)):
+        for extra, parents in zip(merge_extra, graph.get_parents(merge_extra)):
             if parents is None: # The revision does not exist in the repository
-                merged_graph[merge] = []
+                merged_graph[extra] = []
             else:
-                merged_graph[merge] = [p for p in parents if p in merge_extra]
+                merged_graph[extra] = [p for p in parents if p in merge_extra]
         sorter = tsort.MergeSorter(merged_graph, merge)
         # Get a handle to all of the revisions we will need
         width = osutils.terminal_width()
@@ -191,7 +221,9 @@
         else:
             rev_id_iterator = sorter.iter_topo_order()
             num, first, depth, eom = rev_id_iterator.next()
-            assert first == merge
+            assert first == merge, ("The sorted tip revision is incorrect\n"
+                                    "expected: %s\nactual:   %s\n"
+                                    % (merge, first))
             m_revision = revisions[merge]
             if short:
                 prefix = 'P  '

=== modified file 'bzrlib/tests/test_status.py'
--- a/bzrlib/tests/test_status.py	2007-12-04 19:55:23 +0000
+++ b/bzrlib/tests/test_status.py	2007-12-07 21:22:20 +0000
@@ -43,31 +43,71 @@
         self.assertContainsRe(output.getvalue(), 'empty commit')
 
     def test_multiple_pending(self):
+        """multiple pending merges shouldn't repeat revisions"""
         config.GlobalConfig().set_user_option('email', 'Joe Foo <joe at foo.com>')
-        tree = self.make_branch_and_tree('a')
-        tree.commit('commit 1', timestamp=1196796819, timezone=0)
-        tree2 = tree.bzrdir.clone('b').open_workingtree()
-        tree.commit('commit 2', timestamp=1196796819, timezone=0)
-        tree2.commit('commit 2b', timestamp=1196796819, timezone=0)
-        tree3 = tree.bzrdir.clone('c').open_workingtree()
-        tree2.commit('commit 3b', timestamp=1196796819, timezone=0)
-        tree3.commit('commit 3c', timestamp=1196796819, timezone=0)
-        tree.merge_from_branch(tree2.branch)
-        tree.merge_from_branch(tree3.branch)
+        tree_a = self.make_branch_and_tree('a')
+        date_2007_12_04 = 1196790000 # A fixed timestamp
+        tree_a.commit('commit 1a', timestamp=date_2007_12_04, timezone=0)
+        tree_b = tree_a.bzrdir.clone('b').open_workingtree()
+        tree_a.commit('commit 2a', timestamp=date_2007_12_04, timezone=0)
+        tree_b.commit('commit 2b', timestamp=date_2007_12_04, timezone=0)
+        tree_c = tree_a.bzrdir.clone('c').open_workingtree()
+        tree_b.commit('commit 3b', timestamp=date_2007_12_04, timezone=0)
+        tree_c.commit('commit 3c', timestamp=date_2007_12_04, timezone=0)
+        tree_a.merge_from_branch(tree_b.branch)
+        tree_a.merge_from_branch(tree_c.branch)
         output = StringIO()
-        tree.lock_read()
+        tree_a.lock_read()
         try:
-            show_pending_merges(tree, output)
+            show_pending_merges(tree_a, output)
         finally:
-            tree.unlock()
+            tree_a.unlock()
         # Even though 2b is merged by 3c also, it should only be displayed
-        # the first time it shows u.
+        # the first time it appears.
         self.assertEqual('pending merges:\n'
                          '  Joe Foo 2007-12-04 commit 3b\n'
                          '    Joe Foo 2007-12-04 commit 2b\n'
                          '  Joe Foo 2007-12-04 commit 3c\n',
                          output.getvalue())
 
+    def test_history_shortcut(self):
+        """Fallback correctly when a history shortcut is detected."""
+        # See tests/test_graph.py for a picture of this ancestry
+        config.GlobalConfig().set_user_option('email', 'Joe Foo <joe at foo.com>')
+        date_2007_12_04 = 1196790000 # A fixed timestamp
+        tree1 = self.make_branch_and_tree('tree1')
+        tree1.commit('a', rev_id='rev-a')
+        tree2 = tree1.bzrdir.sprout('tree2').open_workingtree()
+        tree1.commit('b', rev_id='rev-b')
+        tree1.commit('c', rev_id='rev-c')
+        tree1.commit('d', rev_id='rev-d')
+        tree2.merge_from_branch(tree1.branch)
+        tree2.commit('f', rev_id='rev-f', timestamp=date_2007_12_04)
+        tree1.commit('e', rev_id='rev-e', timestamp=date_2007_12_04)
+        tree1.merge_from_branch(tree2.branch)
+
+        output = StringIO()
+        tree1.lock_read()
+        try:
+            show_pending_merges(tree1, to_file=output)
+        finally:
+            tree1.unlock()
+        self.assertEqual('pending merges:\n'
+                         '  Joe Foo 2007-12-04 f\n',
+                         output.getvalue())
+
+        # Neither direction should fail
+        tree2.merge_from_branch(tree1.branch)
+        output = StringIO()
+        tree2.lock_read()
+        try:
+            show_pending_merges(tree2, to_file=output)
+        finally:
+            tree2.unlock()
+        self.assertEqual('pending merges:\n'
+                         '  Joe Foo 2007-12-04 e\n',
+                         output.getvalue())
+
     def tests_revision_to_revision(self):
         """doing a status between two revision trees should work."""
         tree = self.make_branch_and_tree('.')



More information about the bazaar-commits mailing list