Rev 4515: Start implementing an Annotator.add_special_text functionality. in http://bazaar.launchpad.net/~jameinel/bzr/1.17-rework-annotate
John Arbash Meinel
john at arbash-meinel.com
Mon Jul 6 19:59:52 BST 2009
At http://bazaar.launchpad.net/~jameinel/bzr/1.17-rework-annotate
------------------------------------------------------------
revno: 4515
revision-id: john at arbash-meinel.com-20090706185924-qlhn1j607117lgdj
parent: john at arbash-meinel.com-20090706185702-gw00fhi18gl1z3bz
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: 1.17-rework-annotate
timestamp: Mon 2009-07-06 13:59:24 -0500
message:
Start implementing an Annotator.add_special_text functionality.
The Python implementation supports it. Basically, it is meant to allow things
like WT and PreviewTree to insert the 'current' content into the graph, so that
we can get local modifications into the annotations.
There is also some work here to get support for texts that are already cached
in the annotator. So that we avoid extracting them, and can shortcut the
history.
-------------- next part --------------
=== modified file 'bzrlib/_annotator_py.py'
--- a/bzrlib/_annotator_py.py 2009-07-02 21:43:05 +0000
+++ b/bzrlib/_annotator_py.py 2009-07-06 18:59:24 +0000
@@ -35,6 +35,7 @@
def __init__(self, vf):
"""Create a new Annotator from a VersionedFile."""
self._vf = vf
+ self._special_keys = set()
self._parent_map = {}
self._text_cache = {}
# Map from key => number of nexts that will be built from this key
@@ -42,27 +43,58 @@
self._annotations_cache = {}
self._heads_provider = None
+ def _update_needed_children(self, key, parent_keys):
+ for parent_key in parent_keys:
+ if parent_key in self._num_needed_children:
+ self._num_needed_children[parent_key] += 1
+ else:
+ self._num_needed_children[parent_key] = 1
+
def _get_needed_keys(self, key):
- graph = _mod_graph.Graph(self._vf)
- parent_map = {}
+ """Determine the texts we need to get from the backing vf.
+
+ :return: (vf_keys_needed, ann_keys_needed)
+ vf_keys_needed These are keys that we need to get from the vf
+ ann_keys_needed Texts which we have in self._text_cache but we
+ don't have annotations for. We need to yield these
+ in the proper order so that we can get proper
+ annotations.
+ """
+ parent_map = self._parent_map
# We need 1 extra copy of the node we will be looking at when we are
# done
self._num_needed_children[key] = 1
- for key, parent_keys in graph.iter_ancestry([key]):
- if parent_keys is None:
- continue
- parent_map[key] = parent_keys
- for parent_key in parent_keys:
- if parent_key in self._num_needed_children:
- self._num_needed_children[parent_key] += 1
+ vf_keys_needed = set()
+ ann_keys_needed = set()
+ needed_keys = set([key])
+ while needed_keys:
+ parent_lookup = []
+ next_parent_map = {}
+ for key in needed_keys:
+ if key in self._parent_map:
+ # We don't need to lookup this key in the vf
+ if key not in self._text_cache:
+ # Extract this text from the vf
+ vf_keys_needed.add(key)
+ elif key not in self._annotations_cache:
+ # We do need to annotate
+ ann_keys_needed.add(key)
+ next_parent_map[key] = self._parent_map[key]
else:
- self._num_needed_children[parent_key] = 1
- self._parent_map.update(parent_map)
- # _heads_provider does some graph caching, so it is only valid while
- # self._parent_map hasn't changed
- self._heads_provider = None
- keys = parent_map.keys()
- return keys
+ parent_lookup.append(key)
+ vf_keys_needed.add(key)
+ needed_keys = set()
+ next_parent_map.update(self._vf.get_parent_map(parent_lookup))
+ for key, parent_keys in next_parent_map.iteritems():
+ self._update_needed_children(key, parent_keys)
+ needed_keys.update([key for key in parent_keys
+ if key not in parent_map])
+ parent_map.update(next_parent_map)
+ # _heads_provider does some graph caching, so it is only valid while
+ # self._parent_map hasn't changed
+ self._heads_provider = None
+ # self._parent_map.update(parent_map)
+ return vf_keys_needed, ann_keys_needed
def _get_needed_texts(self, key, pb=None):
"""Get the texts we need to properly annotate key.
@@ -73,18 +105,24 @@
matcher object we are using. Currently it is always 'lines' but
future improvements may change this to a simple text string.
"""
- keys = self._get_needed_keys(key)
+ keys, ann_keys = self._get_needed_keys(key)
if pb is not None:
pb.update('getting stream', 0, len(keys))
stream = self._vf.get_record_stream(keys, 'topological', True)
for idx, record in enumerate(stream):
if pb is not None:
pb.update('extracting', 0, len(keys))
+ if record.storage_kind == 'absent':
+ raise errors.RevisionNotPresent(record.key, self._vf)
this_key = record.key
lines = osutils.chunks_to_lines(record.get_bytes_as('chunked'))
num_lines = len(lines)
self._text_cache[this_key] = lines
yield this_key, lines, num_lines
+ for key in ann_keys:
+ lines = self._text_cache[key]
+ num_lines = len(lines)
+ yield key, lines, num_lines
def _get_parent_annotations_and_matches(self, key, text, parent_key):
"""Get the list of annotations for the parent, and the matching lines.
@@ -186,6 +224,13 @@
this_annotation, parent)
self._record_annotation(key, parent_keys, annotations)
+ def add_special_text(self, key, parent_keys, text):
+ """Add a specific text to the graph."""
+ self._special_keys.add(key)
+ self._parent_map[key] = parent_keys
+ self._text_cache[key] = osutils.split_lines(text)
+ self._heads_provider = None
+
def annotate(self, key):
"""Return annotated fulltext for the given key."""
pb = ui.ui_factory.nested_progress_bar()
=== modified file 'bzrlib/_annotator_pyx.pyx'
--- a/bzrlib/_annotator_pyx.pyx 2009-07-02 21:43:05 +0000
+++ b/bzrlib/_annotator_pyx.pyx 2009-07-06 18:59:24 +0000
@@ -362,6 +362,9 @@
this_annotation, parent)
self._record_annotation(key, parent_keys, annotations)
+ def add_special_text(self, key, parent_keys, text):
+ """Add a specific text to the graph."""
+
def annotate(self, key):
"""Return annotated fulltext for the given key."""
pb = ui.ui_factory.nested_progress_bar()
=== modified file 'bzrlib/tests/test__annotator.py'
--- a/bzrlib/tests/test__annotator.py 2009-06-20 03:22:59 +0000
+++ b/bzrlib/tests/test__annotator.py 2009-07-06 18:59:24 +0000
@@ -17,9 +17,10 @@
"""Tests for Annotators."""
from bzrlib import (
+ _annotator_py,
errors,
knit,
- _annotator_py,
+ revision,
tests,
)
@@ -77,12 +78,22 @@
# This assumes nothing special happens during __init__, which may be
# valid
self.ann = self.module.Annotator(self.vf)
+ # A 'simple|content|'
+ # |
+ # B 'simple|new content|'
self.vf.add_lines(self.fa_key, [], ['simple\n', 'content\n'])
self.vf.add_lines(self.fb_key, [self.fa_key],
['simple\n', 'new content\n'])
def make_merge_text(self):
self.make_simple_text()
+ # A 'simple|content|'
+ # |\
+ # B | 'simple|new content|'
+ # | |
+ # | C 'simple|from c|content|'
+ # |/
+ # D 'simple|from c|new content|introduced in merge|'
self.vf.add_lines(self.fc_key, [self.fa_key],
['simple\n', 'from c\n', 'content\n'])
self.vf.add_lines(self.fd_key, [self.fb_key, self.fc_key],
@@ -92,6 +103,13 @@
def make_common_merge_text(self):
"""Both sides of the merge will have introduced a line."""
self.make_simple_text()
+ # A 'simple|content|'
+ # |\
+ # B | 'simple|new content|'
+ # | |
+ # | C 'simple|new content|'
+ # |/
+ # D 'simple|new content|'
self.vf.add_lines(self.fc_key, [self.fa_key],
['simple\n', 'new content\n'])
self.vf.add_lines(self.fd_key, [self.fb_key, self.fc_key],
@@ -99,6 +117,17 @@
def make_many_way_common_merge_text(self):
self.make_simple_text()
+ # A-. 'simple|content|'
+ # |\ \
+ # B | | 'simple|new content|'
+ # | | |
+ # | C | 'simple|new content|'
+ # |/ |
+ # D | 'simple|new content|'
+ # | |
+ # | E 'simple|new content|'
+ # | /
+ # F-' 'simple|new content|'
self.vf.add_lines(self.fc_key, [self.fa_key],
['simple\n', 'new content\n'])
self.vf.add_lines(self.fd_key, [self.fb_key, self.fc_key],
@@ -110,6 +139,13 @@
def make_merge_and_restored_text(self):
self.make_simple_text()
+ # A 'simple|content|'
+ # |\
+ # B | 'simple|new content|'
+ # | |
+ # C | 'simple|content|' # reverted to A
+ # \|
+ # D 'simple|content|'
# c reverts back to 'a' for the new content line
self.vf.add_lines(self.fc_key, [self.fb_key],
['simple\n', 'content\n'])
@@ -117,11 +153,12 @@
self.vf.add_lines(self.fd_key, [self.fa_key, self.fc_key],
['simple\n', 'content\n'])
- def assertAnnotateEqual(self, expected_annotation, annotator, key):
- annotation, lines = annotator.annotate(key)
+ def assertAnnotateEqual(self, expected_annotation, key, exp_text=None):
+ annotation, lines = self.ann.annotate(key)
self.assertEqual(expected_annotation, annotation)
- record = self.vf.get_record_stream([key], 'unordered', True).next()
- exp_text = record.get_bytes_as('fulltext')
+ if exp_text is None:
+ record = self.vf.get_record_stream([key], 'unordered', True).next()
+ exp_text = record.get_bytes_as('fulltext')
self.assertEqualDiff(exp_text, ''.join(lines))
def test_annotate_missing(self):
@@ -131,31 +168,30 @@
def test_annotate_simple(self):
self.make_simple_text()
- self.assertAnnotateEqual([(self.fa_key,)]*2, self.ann, self.fa_key)
- self.assertAnnotateEqual([(self.fa_key,), (self.fb_key,)],
- self.ann, self.fb_key)
+ self.assertAnnotateEqual([(self.fa_key,)]*2, self.fa_key)
+ self.assertAnnotateEqual([(self.fa_key,), (self.fb_key,)], self.fb_key)
def test_annotate_merge_text(self):
self.make_merge_text()
self.assertAnnotateEqual([(self.fa_key,), (self.fc_key,),
(self.fb_key,), (self.fd_key,)],
- self.ann, self.fd_key)
+ self.fd_key)
def test_annotate_common_merge_text(self):
self.make_common_merge_text()
self.assertAnnotateEqual([(self.fa_key,), (self.fb_key, self.fc_key)],
- self.ann, self.fd_key)
+ self.fd_key)
def test_annotate_many_way_common_merge_text(self):
self.make_many_way_common_merge_text()
self.assertAnnotateEqual([(self.fa_key,),
(self.fb_key, self.fc_key, self.fe_key)],
- self.ann, self.ff_key)
+ self.ff_key)
def test_annotate_merge_and_restored(self):
self.make_merge_and_restored_text()
self.assertAnnotateEqual([(self.fa_key,), (self.fa_key, self.fc_key)],
- self.ann, self.fd_key)
+ self.fd_key)
def test_annotate_flat_simple(self):
self.make_simple_text()
@@ -190,14 +226,15 @@
def test_needed_keys_simple(self):
self.make_simple_text()
- keys = self.ann._get_needed_keys(self.fb_key)
+ keys, ann_keys = self.ann._get_needed_keys(self.fb_key)
self.assertEqual([self.fa_key, self.fb_key], sorted(keys))
self.assertEqual({self.fa_key: 1, self.fb_key: 1},
self.ann._num_needed_children)
+ self.assertEqual(set(), ann_keys)
def test_needed_keys_many(self):
self.make_many_way_common_merge_text()
- keys = self.ann._get_needed_keys(self.ff_key)
+ keys, ann_keys = self.ann._get_needed_keys(self.ff_key)
self.assertEqual([self.fa_key, self.fb_key, self.fc_key,
self.fd_key, self.fe_key, self.ff_key,
], sorted(keys))
@@ -208,6 +245,19 @@
self.fe_key: 1,
self.ff_key: 1,
}, self.ann._num_needed_children)
+ self.assertEqual(set(), ann_keys)
+
+ def test_needed_keys_with_special_text(self):
+ self.make_many_way_common_merge_text()
+ spec_key = ('f-id', revision.CURRENT_REVISION)
+ spec_text = 'simple\nnew content\nlocally modified\n'
+ self.ann.add_special_text(spec_key, [self.fd_key, self.fe_key],
+ spec_text)
+ keys, ann_keys = self.ann._get_needed_keys(spec_key)
+ self.assertEqual([self.fa_key, self.fb_key, self.fc_key,
+ self.fd_key, self.fe_key,
+ ], sorted(keys))
+ self.assertEqual([spec_key], sorted(ann_keys))
def test_record_annotation_removes_texts(self):
self.make_many_way_common_merge_text()
@@ -250,3 +300,29 @@
self.assertFalse(self.fb_key in self.ann._annotations_cache)
self.assertFalse(self.fc_key in self.ann._text_cache)
self.assertFalse(self.fc_key in self.ann._annotations_cache)
+
+ def test_annotate_special_text(self):
+ # Things like WT and PreviewTree want to annotate an arbitrary text
+ # ('current:') so we need a way to add that to the group of files to be
+ # annotated.
+ self.make_many_way_common_merge_text()
+ # A-. 'simple|content|'
+ # |\ \
+ # B | | 'simple|new content|'
+ # | | |
+ # | C | 'simple|new content|'
+ # |/ |
+ # D | 'simple|new content|'
+ # | |
+ # | E 'simple|new content|'
+ # | /
+ # SPEC 'simple|new content|locally modified|'
+ spec_key = ('f-id', revision.CURRENT_REVISION)
+ spec_text = 'simple\nnew content\nlocally modified\n'
+ self.ann.add_special_text(spec_key, [self.fd_key, self.fe_key],
+ spec_text)
+ self.assertAnnotateEqual([(self.fa_key,),
+ (self.fb_key, self.fc_key, self.fe_key),
+ (spec_key,)
+ ], spec_key,
+ exp_text=spec_text)
More information about the bazaar-commits
mailing list