Rev 3479: Split things into a registry of annotation policies, and start in http://bzr.arbash-meinel.com/branches/bzr/1.6-dev/annotation

John Arbash Meinel john at arbash-meinel.com
Fri Jun 6 03:30:10 BST 2008


At http://bzr.arbash-meinel.com/branches/bzr/1.6-dev/annotation

------------------------------------------------------------
revno: 3479
revision-id: john at arbash-meinel.com-20080606022951-uqtcqi14fvgtxld9
parent: john at arbash-meinel.com-20080605224745-wnih42sf8rqwbmdc
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: annotation
timestamp: Thu 2008-06-05 21:29:51 -0500
message:
  Split things into a registry of annotation policies, and start
  refactoring the tests to handle different policies.
-------------- next part --------------
=== modified file 'bzrlib/annotate.py'
--- a/bzrlib/annotate.py	2008-06-05 22:47:45 +0000
+++ b/bzrlib/annotate.py	2008-06-06 02:29:51 +0000
@@ -63,13 +63,13 @@
         matcher = self._sequence_matcher(None, old, new)
         return matcher.get_matching_blocks()
 
-    def reannotate(self, parents_lines, new_lines, new_revision_id,
-                   _left_matching_blocks=None):
+    def reannotate(self, annotated_parents_lines, plain_child_lines,
+                   child_revision_id, left_matching_blocks=None):
         """Annotate the lines given parent annotations.
         
-        :param parents_lines: List of annotated lines for all parents
-        :param new_lines: The un-annotated new lines
-        :param new_revision_id: The revision-id to associate with new lines
+        :param annotated_parents_lines: List of annotated lines for all parents
+        :param plain_child_lines: The un-annotated new lines
+        :param child_revision_id: The revision-id to associate with new lines
             (will often be CURRENT_REVISION)
         :param left_matching_blocks: a hint about which areas are common
             between the text and its left-hand-parent.  The format is
@@ -77,24 +77,27 @@
             (start_left, start_right, length_of_match).
         :return: A list of annotated lines.
         """
-        if len(parents_lines) == 0:
-            lines = [(new_revision_id, line) for line in new_lines]
-        elif len(parents_lines) == 1:
-            lines = self._reannotate_one(parents_lines[0], new_lines,
-                        new_revision_id, _left_matching_blocks)
-        elif len(parents_lines) == 2:
-            left = self._reannotate_one(parents_lines[0], new_lines,
-                        new_revision_id, _left_matching_blocks)
-            lines = self._reannotate_annotated(parents_lines[1], new_lines,
-                                          new_revision_id, left)
+        if len(annotated_parents_lines) == 0:
+            lines = [(child_revision_id, line) for line in plain_child_lines]
+        elif len(annotated_parents_lines) == 1:
+            lines = self._reannotate_one(annotated_parents_lines[0],
+                        plain_child_lines, child_revision_id,
+                        left_matching_blocks)
+        elif len(annotated_parents_lines) == 2:
+            left = self._reannotate_one(annotated_parents_lines[0],
+                        plain_child_lines, child_revision_id,
+                        left_matching_blocks)
+            lines = self._reannotate_annotated(annotated_parents_lines[1],
+                            plain_child_lines, child_revision_id, left)
         else:
             # Annotate the child lines versus each parent, and then match up the
             # lines one-by-one
-            reannotations = [self._reannotate_one(parents_lines[0], new_lines,
-                                 new_revision_id, _left_matching_blocks)]
-            reannotations.extend(self._reannotate_one(p, new_lines,
-                                                     new_revision_id)
-                                 for p in parents_lines[1:])
+            reannotations = [self._reannotate_one(annotated_parents_lines[0],
+                                plain_child_lines, child_revision_id,
+                                left_matching_blocks)]
+            reannotations.extend(self._reannotate_one(p, plain_child_lines,
+                                                     child_revision_id)
+                                 for p in annotated_parents_lines[1:])
             lines = []
             for annos in zip(*reannotations):
                 origins = set(a for a, l in annos)
@@ -103,12 +106,12 @@
                     lines.append(annos[0])
                 else:
                     line = annos[0][1]
-                    if len(origins) == 2 and new_revision_id in origins:
-                        origins.remove(new_revision_id)
+                    if len(origins) == 2 and child_revision_id in origins:
+                        origins.remove(child_revision_id)
                     if len(origins) == 1:
                         lines.append((origins.pop(), line))
                     else:
-                        lines.append((new_revision_id, line))
+                        lines.append((child_revision_id, line))
         return lines
 
     def _reannotate_one(self, annotated_parent_lines, plain_child_lines,

=== modified file 'bzrlib/tests/test_annotate.py'
--- a/bzrlib/tests/test_annotate.py	2008-06-05 22:47:45 +0000
+++ b/bzrlib/tests/test_annotate.py	2008-06-06 02:29:51 +0000
@@ -23,6 +23,8 @@
     annotate,
     conflicts,
     errors,
+    graph,
+    revision,
     tests,
     trace,
     )
@@ -112,14 +114,17 @@
 # We always change the fourth line so that the file is properly tracked as
 # being modified in each revision. In reality, this probably would happen over
 # many revisions, and it would be a different line that changes.
+#
+# The actual annotations will vary slightly based on the algorithm. For the
+# default algorithm, the values are as noted.
+#
 # BASE
 #  |\
 #  A B  # line should be annotated as new for A and B
 #  |\|
-#  C D  # line should 'converge' and say D
+#  C D  # line should 'converge' and say B
 #  |/
-#  E    # D should supersede A and stay as D (not become E because C references
-#         A)
+#  E    # B should supersede A and stay as B
 duplicate_base = annotation("""\
 rev-base first
 rev-base second
@@ -470,7 +475,29 @@
                            new_text, 'rev2', blocks)
 
 
-class TestAnnotationPolicy(tests.TestCaseWithTransport):
+class _AnnotationScenario(object):
+    """A class encapsulating results for an annotation use case."""
+
+class TestAnnotationPolicy(tests.TestCase):
+    """Base helper for testing different annotation policies"""
+
+    self._duplicate_base = duplicate_base
+    self._duplicate_A = duplicate_A
+    self._duplicate_B = duplicate_B
+    self._duplicate_C = duplicate_C
+    self._duplicate_D = duplicate_D
+    self._duplicate_E = duplicate_E
+
+    self._revision_graph = {
+        'rev-base':(revision.NULL_REVISION),
+        'rev-A':('rev-base',),
+        'rev-B':('rev-base',),
+        'rev-C':('rev-A',),
+        'rev-D':('rev-B', 'rev-A',),
+        'rev-E':('rev-C', 'rev-D',),
+    }
+
+    self._annotation_policy = None
 
     def create_duplicate_lines_tree(self):
         tree1 = self.make_branch_and_tree('tree1')
@@ -504,8 +531,15 @@
         tree1.commit('E', rev_id='rev-E')
         return tree1
 
-    def assertRepoAnnotate(self, expected, repo, file_id, revision_id):
+    def get_heads_provider(self):
+        parent_provider = graph.DictParentsProvider(self._revision_graph)
+        graph_obj = graph.Graph(parent_provider)
+        return graph.FrozenHeadsCache(graph_obj)
+
+    def assertReannotate(self, expected, annotated_parents_lines,
+                         plain_child_lines, new_id):
         """Assert that the revision is properly annotated."""
+        policy = self._annotation_policy(self.get_heads_provider())
         actual = list(repo.revision_tree(revision_id).annotate_iter(file_id))
         if actual != expected:
             # Create an easier to understand diff when the lines don't actually
@@ -513,15 +547,37 @@
             self.assertEqualDiff(''.join('\t'.join(l) for l in expected),
                                  ''.join('\t'.join(l) for l in actual))
 
-    def test_annotate_duplicate_lines(self):
+    def help_test_annotate_duplicate_lines(self):
         # XXX: Should this be a repository_implementations test?
-        tree1 = self.create_duplicate_lines_tree()
-        repo = tree1.branch.repository
-        repo.lock_read()
-        self.addCleanup(repo.unlock)
-        self.assertRepoAnnotate(duplicate_base, repo, 'file-id', 'rev-base')
-        self.assertRepoAnnotate(duplicate_A, repo, 'file-id', 'rev-A')
-        self.assertRepoAnnotate(duplicate_B, repo, 'file-id', 'rev-B')
-        self.assertRepoAnnotate(duplicate_C, repo, 'file-id', 'rev-C')
-        self.assertRepoAnnotate(duplicate_D, repo, 'file-id', 'rev-D')
-        self.assertRepoAnnotate(duplicate_E, repo, 'file-id', 'rev-E')
+        self.assertAnnotate(self._duplicate_base, [], 
+                            'rev-base')
+        self.assertAnnotate(self._duplicate_A, repo, 'file-id', 'rev-A')
+        self.assertAnnotate(self._duplicate_B, repo, 'file-id', 'rev-B')
+        self.assertAnnotate(self._duplicate_C, repo, 'file-id', 'rev-C')
+        self.assertAnnotate(self._duplicate_D, repo, 'file-id', 'rev-D')
+        self.assertAnnotate(self._duplicate_E, repo, 'file-id', 'rev-E')
+
+
+class TestMergeNodeAnnotationPolicy(TestAnnotationPolicy):
+    # 
+    # BASE
+    #  |\
+    #  A B  # line should be annotated as new for A and B
+    #  |\|
+    #  C D  # line should 'converge' and say D
+    #  |/
+    #  E    # D should supersede A and stay as D (not become E because C
+    #       # references A)
+    self._duplicate_D = annotation("""\
+rev-base first
+rev-D alt-second
+rev-base third
+rev-D fourth-D
+""")
+
+    self._duplicate_E = annotation("""\
+rev-base first
+rev-D alt-second
+rev-base third
+rev-E fourth-E
+""")



More information about the bazaar-commits mailing list