Rev 3471: start factoring the reannotation code into a policy class, which can then be subclassed. in http://bzr.arbash-meinel.com/branches/bzr/1.6-dev/annotation
John Arbash Meinel
john at arbash-meinel.com
Wed Jun 4 23:38:00 BST 2008
At http://bzr.arbash-meinel.com/branches/bzr/1.6-dev/annotation
------------------------------------------------------------
revno: 3471
revision-id: john at arbash-meinel.com-20080604223747-vf0mbkj5goxkyc3w
parent: pqm at pqm.ubuntu.com-20080604174215-d0m8mjs939ek9ed7
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: annotation
timestamp: Wed 2008-06-04 17:37:47 -0500
message:
start factoring the reannotation code into a policy class, which can then be subclassed.
modified:
bzrlib/annotate.py annotate.py-20050922133147-7c60541d2614f022
bzrlib/tests/test_annotate.py test_annotate.py-20061213215015-sttc9agsxomls7q0-1
-------------- next part --------------
=== modified file 'bzrlib/annotate.py'
--- a/bzrlib/annotate.py 2008-04-24 07:22:53 +0000
+++ b/bzrlib/annotate.py 2008-06-04 22:37:47 +0000
@@ -37,6 +37,102 @@
from bzrlib.config import extract_email_address
+class AnnotationPolicy(object):
+ """This class represents a specific method for performing annotations.
+
+ Mostly, this class determines how to resolve ambiguous lines. (Lines which
+ both sides claim different last-modified revisions.)
+ """
+
+ def __init__(self):
+ self._sequence_matcher = patiencediff.PatienceSequenceMatcher
+
+ def _get_matching_blocks(self, old, new):
+ matcher = self._sequence_matcher(None, old, new)
+ return matcher.get_matching_blocks()
+
+ def process_unmatched_lines(self, output_lines, plain_child_lines,
+ annotated_child_lines, start_child, end_child,
+ annotated_right_lines, start_right, end_right,
+ heads_provider, revision_id):
+ """Find lines in plain right_lines that match plain_child_lines
+
+ :param output_lines: Append final annotated lines to this list
+ :param plain_child_lines: The unannotated new lines for the child text
+ :param annotated_child_lines: Lines for the child text which have been
+ annotated from other parents
+ :param start_child: Position in plain_child_lines and
+ annotated_child_lines to start the match searching
+ :param end_child: Last position in plain_child_lines and
+ annotated_child_lines to search for a match
+ :param annotated_right_lines: The annotated lines for the whole text
+ for the right parent
+ :param start_right: Position in annotated_right_lines to start the
+ match
+ :param end_right: Last position in annotated_right_lines to search for
+ a match
+ :param heads_provider: When parents disagree on the lineage of a line,
+ we need to check if one side supersedes the other
+ :param revision_id: The label to give if a line should be labeled 'tip'
+ """
+ output_extend = output_lines.extend
+ output_append = output_lines.append
+ # We need to see if any of the unannotated lines match
+ plain_right_subset = [l for a,l in
+ annotated_right_lines[start_right:end_right]]
+ plain_child_subset = plain_child_lines[start_child:end_child]
+ match_blocks = self._get_matching_blocks(plain_right_subset,
+ plain_child_subset)
+
+ last_child_idx = 0
+
+ self._heads_provider = heads_provider
+
+ for right_idx, child_idx, match_len in match_blocks:
+ # All the lines that don't match are just passed along
+ if child_idx > last_child_idx:
+ output_extend(annotated_child_lines[
+ start_child + last_child_idx:start_child + child_idx])
+ for offset in xrange(match_len):
+ left = annotated_child_lines[start_child+child_idx+offset]
+ right = annotated_right_lines[start_right+right_idx+offset]
+ if left[0] == right[0]:
+ # The annotations match, just return the left one
+ output_append(left)
+ elif left[0] == revision_id:
+ # The left parent marked this as unmatched, so let the
+ # right parent claim it
+ output_append(right)
+ else:
+ # Left and Right both claim this line
+ output_append(self._resolve_ambiguous(revision_id, left,
+ right))
+ last_child_idx = child_idx + match_len
+
+ def _resolve_ambiguous(self, revision_id, left, right):
+ """We have an ambiguous line between two texts.
+
+ Resolve this somehow.
+ """
+ if self._heads_provider is not None:
+ heads = self._heads_provider.heads((left[0], right[0]))
+ if len(heads) == 1:
+ return (iter(heads).next(), left[1])
+ else:
+ # Both claim different origins
+ # We know that revision_id is the head for
+ # left and right, so cache it
+ self._heads_provider.cache(
+ (revision_id, left[0]),
+ (revision_id,))
+ self._heads_provider.cache(
+ (revision_id, right[0]),
+ (revision_id,))
+ return (revision_id, left[1])
+ # Without a heads provider, we just always pick 'tip'
+ return (revision_id, left[1])
+
+
def annotate_file(branch, rev_id, file_id, verbose=False, full=False,
to_file=None, show_ids=False):
"""Annotate file_id at revision rev_id in branch.
@@ -229,74 +325,6 @@
return matcher.get_matching_blocks()
-def _find_matching_unannotated_lines(output_lines, plain_child_lines,
- child_lines, start_child, end_child,
- right_lines, start_right, end_right,
- heads_provider, revision_id):
- """Find lines in plain_right_lines that match the existing lines.
-
- :param output_lines: Append final annotated lines to this list
- :param plain_child_lines: The unannotated new lines for the child text
- :param child_lines: Lines for the child text which have been annotated
- for the left parent
- :param start_child: Position in plain_child_lines and child_lines to start the
- match searching
- :param end_child: Last position in plain_child_lines and child_lines to search
- for a match
- :param right_lines: The annotated lines for the whole text for the right
- parent
- :param start_right: Position in right_lines to start the match
- :param end_right: Last position in right_lines to search for a match
- :param heads_provider: When parents disagree on the lineage of a line, we
- need to check if one side supersedes the other
- :param revision_id: The label to give if a line should be labeled 'tip'
- """
- output_extend = output_lines.extend
- output_append = output_lines.append
- # We need to see if any of the unannotated lines match
- plain_right_subset = [l for a,l in right_lines[start_right:end_right]]
- plain_child_subset = plain_child_lines[start_child:end_child]
- match_blocks = _get_matching_blocks(plain_right_subset, plain_child_subset)
-
- last_child_idx = 0
-
- for right_idx, child_idx, match_len in match_blocks:
- # All the lines that don't match are just passed along
- if child_idx > last_child_idx:
- output_extend(child_lines[start_child + last_child_idx
- :start_child + child_idx])
- for offset in xrange(match_len):
- left = child_lines[start_child+child_idx+offset]
- right = right_lines[start_right+right_idx+offset]
- if left[0] == right[0]:
- # The annotations match, just return the left one
- output_append(left)
- elif left[0] == revision_id:
- # The left parent marked this as unmatched, so let the
- # right parent claim it
- output_append(right)
- else:
- # Left and Right both claim this line
- if heads_provider is None:
- output_append((revision_id, left[1]))
- else:
- heads = heads_provider.heads((left[0], right[0]))
- if len(heads) == 1:
- output_append((iter(heads).next(), left[1]))
- else:
- # Both claim different origins
- output_append((revision_id, left[1]))
- # We know that revision_id is the head for
- # left and right, so cache it
- heads_provider.cache(
- (revision_id, left[0]),
- (revision_id,))
- heads_provider.cache(
- (revision_id, right[0]),
- (revision_id,))
- last_child_idx = child_idx + match_len
-
-
def _reannotate_annotated(right_parent_lines, new_lines, new_revision_id,
annotated_lines, heads_provider):
"""Update the annotations for a node based on another parent.
@@ -320,6 +348,8 @@
lines_extend = lines.extend
last_right_idx = 0 # The line just after the last match from the right side
last_left_idx = 0
+
+ policy = AnnotationPolicy()
matching_left_and_right = _get_matching_blocks(right_parent_lines,
annotated_lines)
for right_idx, left_idx, match_len in matching_left_and_right:
@@ -332,13 +362,13 @@
lines_extend(annotated_lines[last_left_idx:left_idx])
else:
# We need to see if any of the unannotated lines match
- _find_matching_unannotated_lines(lines,
- new_lines, annotated_lines,
- last_left_idx, left_idx,
- right_parent_lines,
- last_right_idx, right_idx,
- heads_provider,
- new_revision_id)
+ policy.process_unmatched_lines(lines,
+ new_lines, annotated_lines,
+ last_left_idx, left_idx,
+ right_parent_lines,
+ last_right_idx, right_idx,
+ heads_provider,
+ new_revision_id)
last_right_idx = right_idx + match_len
last_left_idx = left_idx + match_len
# If left and right agree on a range, just push that into the output
=== modified file 'bzrlib/tests/test_annotate.py'
--- a/bzrlib/tests/test_annotate.py 2008-02-18 22:19:41 +0000
+++ b/bzrlib/tests/test_annotate.py 2008-06-04 22:37:47 +0000
@@ -184,7 +184,7 @@
committer="joe at foo.com",
timestamp=1166046000.00, timezone=0)
- tree2 = tree1.bzrdir.clone('tree2').open_workingtree()
+ tree2 = tree1.bzrdir.sprout('tree2').open_workingtree()
self.build_tree_contents([('tree1/a', 'first\nsecond\n')])
tree1.commit('b', rev_id='rev-2',
@@ -235,7 +235,7 @@
tree1, tree2 = self.create_merged_trees()
tree1.unlock()
- tree3 = tree2.bzrdir.clone('tree3').open_workingtree()
+ tree3 = tree2.bzrdir.sprout('tree3').open_workingtree()
tree2.commit('noop', rev_id='rev-1_1_2')
self.assertEqual(0, tree1.merge_from_branch(tree2.branch))
@@ -246,7 +246,7 @@
committer='jerry at foo.com',
timestamp=1166046003.00, timezone=0)
- tree4 = tree3.bzrdir.clone('tree4').open_workingtree()
+ tree4 = tree3.bzrdir.sprout('tree4').open_workingtree()
tree3.commit('noop', rev_id='rev-1_2_2',
committer='jerry at foo.com',
@@ -275,7 +275,7 @@
self.build_tree_contents([('tree1/file', base_text)])
tree1.add(['file'], ['file-id'])
tree1.commit('base', rev_id='rev-base')
- tree2 = tree1.bzrdir.clone('tree2').open_workingtree()
+ tree2 = tree1.bzrdir.sprout('tree2').open_workingtree()
self.build_tree_contents([('tree1/file', a_text),
('tree2/file', b_text)])
More information about the bazaar-commits
mailing list