Rev 3073: Speed up annotate on packs in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Tue Dec 4 00:42:51 GMT 2007


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 3073
revision-id:pqm at pqm.ubuntu.com-20071204004243-cgss0sl9yf0ayepc
parent: pqm at pqm.ubuntu.com-20071203222135-gjk2xshgdfgxje6m
parent: abentley at panoramicfeedback.com-20071203213807-inz8pur6ejnc1ax5
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Tue 2007-12-04 00:42:43 +0000
message:
  Speed up annotate on packs
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/merge.py                merge.py-20050513021216-953b65a438527106
  bzrlib/tests/test_merge.py     testmerge.py-20050905070950-c1b5aa49ff911024
  bzrlib/tests/test_versionedfile.py test_versionedfile.py-20060222045249-db45c9ed14a1c2e5
  bzrlib/tree.py                 tree.py-20050309040759-9d5f2496be663e77
  bzrlib/versionedfile.py        versionedfile.py-20060222045106-5039c71ee3b65490
    ------------------------------------------------------------
    revno: 3062.1.14
    revision-id:abentley at panoramicfeedback.com-20071203213807-inz8pur6ejnc1ax5
    parent: abentley at panoramicfeedback.com-20071203213336-tpv1ftv3kxpp8x08
    committer: Aaron Bentley <abentley at panoramicfeedback.com>
    branch nick: fast-plan-merge
    timestamp: Mon 2007-12-03 16:38:07 -0500
    message:
      Use topo_sorted=False with get_ancestry
    modified:
      bzrlib/merge.py                merge.py-20050513021216-953b65a438527106
      bzrlib/tests/test_versionedfile.py test_versionedfile.py-20060222045249-db45c9ed14a1c2e5
      bzrlib/versionedfile.py        versionedfile.py-20060222045106-5039c71ee3b65490
    ------------------------------------------------------------
    revno: 3062.1.13
    revision-id:abentley at panoramicfeedback.com-20071203213336-tpv1ftv3kxpp8x08
    parent: aaron.bentley at utoronto.ca-20071203003633-pb9662ni2fwad2g0
    committer: Aaron Bentley <abentley at panoramicfeedback.com>
    branch nick: fast-plan-merge
    timestamp: Mon 2007-12-03 16:33:36 -0500
    message:
      Make _PlanMerge an implementation detail of _PlanMergeVersionedFile
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
      bzrlib/merge.py                merge.py-20050513021216-953b65a438527106
      bzrlib/tests/test_merge.py     testmerge.py-20050905070950-c1b5aa49ff911024
      bzrlib/tree.py                 tree.py-20050309040759-9d5f2496be663e77
      bzrlib/versionedfile.py        versionedfile.py-20060222045106-5039c71ee3b65490
    ------------------------------------------------------------
    revno: 3062.1.12
    revision-id:aaron.bentley at utoronto.ca-20071203003633-pb9662ni2fwad2g0
    parent: aaron.bentley at utoronto.ca-20071203003609-m2vo52shpr7zbjkf
    committer: Aaron Bentley <aaron.bentley at utoronto.ca>
    branch nick: fast-plan-merge
    timestamp: Sun 2007-12-02 19:36:33 -0500
    message:
      Implement simple text cache
    modified:
      bzrlib/merge.py                merge.py-20050513021216-953b65a438527106
    ------------------------------------------------------------
    revno: 3062.1.11
    revision-id:aaron.bentley at utoronto.ca-20071203003609-m2vo52shpr7zbjkf
    parent: aaron.bentley at utoronto.ca-20071203001650-nfj8jveb1wk65sh8
    committer: Aaron Bentley <aaron.bentley at utoronto.ca>
    branch nick: fast-plan-merge
    timestamp: Sun 2007-12-02 19:36:09 -0500
    message:
      Update references
    modified:
      bzrlib/tree.py                 tree.py-20050309040759-9d5f2496be663e77
    ------------------------------------------------------------
    revno: 3062.1.10
    revision-id:aaron.bentley at utoronto.ca-20071203001650-nfj8jveb1wk65sh8
    parent: aaron.bentley at utoronto.ca-20071202235203-e1c69qbqegre7nfj
    committer: Aaron Bentley <aaron.bentley at utoronto.ca>
    branch nick: fast-plan-merge
    timestamp: Sun 2007-12-02 19:16:50 -0500
    message:
      Update NEWS
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
    ------------------------------------------------------------
    revno: 3062.1.9
    revision-id:aaron.bentley at utoronto.ca-20071202235203-e1c69qbqegre7nfj
    parent: aaron.bentley at utoronto.ca-20071202182312-1red2zttrs87iamc
    committer: Aaron Bentley <aaron.bentley at utoronto.ca>
    branch nick: fast-plan-merge
    timestamp: Sun 2007-12-02 18:52:03 -0500
    message:
      Move PlanMerge into merge and _PlanMergeVersionedFile into versionedfile
    removed:
      bzrlib/plan_merge.py           plan_merge.py-20071201150758-662g2gkq8nlz483v-1
      bzrlib/tests/test_plan_merge.py test_plan_merge.py-20071201150758-662g2gkq8nlz483v-2
    modified:
      bzrlib/merge.py                merge.py-20050513021216-953b65a438527106
      bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
      bzrlib/tests/test_merge.py     testmerge.py-20050905070950-c1b5aa49ff911024
      bzrlib/tests/test_versionedfile.py test_versionedfile.py-20060222045249-db45c9ed14a1c2e5
      bzrlib/versionedfile.py        versionedfile.py-20060222045106-5039c71ee3b65490
    ------------------------------------------------------------
    revno: 3062.1.8
    revision-id:aaron.bentley at utoronto.ca-20071202182312-1red2zttrs87iamc
    parent: aaron.bentley at utoronto.ca-20071202180356-fzo74355tuzq3r6m
    committer: Aaron Bentley <aaron.bentley at utoronto.ca>
    branch nick: fast-plan-merge
    timestamp: Sun 2007-12-02 13:23:12 -0500
    message:
      Clean up names and add documentation
    modified:
      bzrlib/plan_merge.py           plan_merge.py-20071201150758-662g2gkq8nlz483v-1
      bzrlib/tests/test_plan_merge.py test_plan_merge.py-20071201150758-662g2gkq8nlz483v-2
      bzrlib/tree.py                 tree.py-20050309040759-9d5f2496be663e77
    ------------------------------------------------------------
    revno: 3062.1.7
    revision-id:aaron.bentley at utoronto.ca-20071202180356-fzo74355tuzq3r6m
    parent: aaron.bentley at utoronto.ca-20071202175848-xvxy2xv8i9rxkxss
    committer: Aaron Bentley <aaron.bentley at utoronto.ca>
    branch nick: fast-plan-merge
    timestamp: Sun 2007-12-02 13:03:56 -0500
    message:
      Update NEWS entry
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
    ------------------------------------------------------------
    revno: 3062.1.6
    revision-id:aaron.bentley at utoronto.ca-20071202175848-xvxy2xv8i9rxkxss
    parent: aaron.bentley at utoronto.ca-20071202162059-3ag1cy5qx26ours9
    committer: Aaron Bentley <aaron.bentley at utoronto.ca>
    branch nick: fast-plan-merge
    timestamp: Sun 2007-12-02 12:58:48 -0500
    message:
      PlanMergeVersionedfile now has multiple Versionedfile fallbacks
    modified:
      bzrlib/plan_merge.py           plan_merge.py-20071201150758-662g2gkq8nlz483v-1
      bzrlib/tests/test_plan_merge.py test_plan_merge.py-20071201150758-662g2gkq8nlz483v-2
      bzrlib/tree.py                 tree.py-20050309040759-9d5f2496be663e77
    ------------------------------------------------------------
    revno: 3062.1.5
    revision-id:aaron.bentley at utoronto.ca-20071202162059-3ag1cy5qx26ours9
    parent: aaron.bentley at utoronto.ca-20071202155634-sxcukjjy6b02tg7u
    committer: Aaron Bentley <aaron.bentley at utoronto.ca>
    branch nick: fast-plan-merge
    timestamp: Sun 2007-12-02 11:20:59 -0500
    message:
      Style cleanup
    modified:
      bzrlib/plan_merge.py           plan_merge.py-20071201150758-662g2gkq8nlz483v-1
    ------------------------------------------------------------
    revno: 3062.1.4
    revision-id:aaron.bentley at utoronto.ca-20071202155634-sxcukjjy6b02tg7u
    parent: aaron.bentley at utoronto.ca-20071202154909-tu7o1l5eqmu45vna
    committer: Aaron Bentley <aaron.bentley at utoronto.ca>
    branch nick: fast-plan-merge
    timestamp: Sun 2007-12-02 10:56:34 -0500
    message:
      Add GPL+copyright notices
    modified:
      bzrlib/plan_merge.py           plan_merge.py-20071201150758-662g2gkq8nlz483v-1
      bzrlib/tests/test_plan_merge.py test_plan_merge.py-20071201150758-662g2gkq8nlz483v-2
    ------------------------------------------------------------
    revno: 3062.1.3
    revision-id:aaron.bentley at utoronto.ca-20071202154909-tu7o1l5eqmu45vna
    parent: aaron.bentley at utoronto.ca-20071201160031-p5u6ed26r675t58f
    committer: Aaron Bentley <aaron.bentley at utoronto.ca>
    branch nick: fast-plan-merge
    timestamp: Sun 2007-12-02 10:49:09 -0500
    message:
      Correctly determine file revisions
    modified:
      bzrlib/tree.py                 tree.py-20050309040759-9d5f2496be663e77
    ------------------------------------------------------------
    revno: 3062.1.2
    revision-id:aaron.bentley at utoronto.ca-20071201160031-p5u6ed26r675t58f
    parent: aaron.bentley at utoronto.ca-20071201150801-5o6hhw3qe9sle4ol
    committer: Aaron Bentley <aaron.bentley at utoronto.ca>
    branch nick: fast-plan-merge
    timestamp: Sat 2007-12-01 11:00:31 -0500
    message:
      Use fake versionedfiles for planning the merge
    modified:
      bzrlib/plan_merge.py           plan_merge.py-20071201150758-662g2gkq8nlz483v-1
      bzrlib/tests/test_plan_merge.py test_plan_merge.py-20071201150758-662g2gkq8nlz483v-2
    ------------------------------------------------------------
    revno: 3062.1.1
    revision-id:aaron.bentley at utoronto.ca-20071201150801-5o6hhw3qe9sle4ol
    parent: pqm at pqm.ubuntu.com-20071130233349-86c0lwztw5vt2r17
    committer: Aaron Bentley <aaron.bentley at utoronto.ca>
    branch nick: fast-plan-merge
    timestamp: Sat 2007-12-01 10:08:01 -0500
    message:
      Get plan-merge working on versionedfile
    added:
      bzrlib/plan_merge.py           plan_merge.py-20071201150758-662g2gkq8nlz483v-1
      bzrlib/tests/test_plan_merge.py test_plan_merge.py-20071201150758-662g2gkq8nlz483v-2
    modified:
      bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
=== modified file 'NEWS'
--- a/NEWS	2007-12-03 17:03:10 +0000
+++ b/NEWS	2007-12-04 00:42:43 +0000
@@ -124,6 +124,10 @@
    * Diff does not require an inventory to be generated on dirstate trees.
      (Aaron Bentley, #149254)
 
+   * New annotate merge (--merge-type=weave) implementation is fast on
+     versionedfiles withough cached annotations, e.g. pack-0.92.
+     (Aaron Bentley)
+
   IMPROVEMENTS:
 
    * ``bzr send`` now doesn't require the target e-mail address to be

=== modified file 'bzrlib/merge.py'
--- a/bzrlib/merge.py	2007-11-30 14:52:30 +0000
+++ b/bzrlib/merge.py	2007-12-03 21:38:07 +0000
@@ -1135,3 +1135,120 @@
         for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
             assert text_a == text_b
             yield "unchanged", text_a
+
+
+class _PlanMerge(object):
+    """Plan an annotate merge using on-the-fly annotation"""
+
+    def __init__(self, a_rev, b_rev, vf):
+        """Contructor.
+
+        :param a_rev: Revision-id of one revision to merge
+        :param b_rev: Revision-id of the other revision to merge
+        :param vf: A versionedfile containing both revisions
+        """
+        self.a_rev = a_rev
+        self.b_rev = b_rev
+        self.lines_a = vf.get_lines(a_rev)
+        self.lines_b = vf.get_lines(b_rev)
+        self.vf = vf
+        a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
+        b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
+        self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
+        self._last_lines = None
+        self._last_lines_revision_id = None
+
+    def plan_merge(self):
+        """Generate a 'plan' for merging the two revisions.
+
+        This involves comparing their texts and determining the cause of
+        differences.  If text A has a line and text B does not, then either the
+        line was added to text A, or it was deleted from B.  Once the causes
+        are combined, they are written out in the format described in
+        VersionedFile.plan_merge
+        """
+        blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
+        new_a = self._find_new(self.a_rev)
+        new_b = self._find_new(self.b_rev)
+        last_i = 0
+        last_j = 0
+        a_lines = self.vf.get_lines(self.a_rev)
+        b_lines = self.vf.get_lines(self.b_rev)
+        for i, j, n in blocks:
+            # determine why lines aren't common
+            for a_index in range(last_i, i):
+                if a_index in new_a:
+                    cause = 'new-a'
+                else:
+                    cause = 'killed-b'
+                yield cause, a_lines[a_index]
+            for b_index in range(last_j, j):
+                if b_index in new_b:
+                    cause = 'new-b'
+                else:
+                    cause = 'killed-a'
+                yield cause, b_lines[b_index]
+            # handle common lines
+            for a_index in range(i, i+n):
+                yield 'unchanged', a_lines[a_index]
+            last_i = i+n
+            last_j = j+n
+
+    def _get_matching_blocks(self, left_revision, right_revision):
+        """Return a description of which sections of two revisions match.
+
+        See SequenceMatcher.get_matching_blocks
+        """
+        if self._last_lines_revision_id == left_revision:
+            left_lines = self._last_lines
+        else:
+            left_lines = self.vf.get_lines(left_revision)
+        right_lines = self.vf.get_lines(right_revision)
+        self._last_lines = right_lines
+        self._last_lines_revision_id = right_revision
+        matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
+                                                       right_lines)
+        return matcher.get_matching_blocks()
+
+    def _unique_lines(self, matching_blocks):
+        """Analyse matching_blocks to determine which lines are unique
+
+        :return: a tuple of (unique_left, unique_right), where the values are
+            sets of line numbers of unique lines.
+        """
+        last_i = 0
+        last_j = 0
+        unique_left = []
+        unique_right = []
+        for i, j, n in matching_blocks:
+            unique_left.extend(range(last_i, i))
+            unique_right.extend(range(last_j, j))
+            last_i = i + n
+            last_j = j + n
+        return unique_left, unique_right
+
+    def _find_new(self, version_id):
+        """Determine which lines are new in the ancestry of this version.
+
+        If a lines is present in this version, and not present in any
+        common ancestor, it is considered new.
+        """
+        if version_id not in self.uncommon:
+            return set()
+        parents = self.vf.get_parents(version_id)
+        if len(parents) == 0:
+            return set(range(len(self.vf.get_lines(version_id))))
+        new = None
+        for parent in parents:
+            blocks = self._get_matching_blocks(version_id, parent)
+            result, unused = self._unique_lines(blocks)
+            parent_new = self._find_new(parent)
+            for i, j, n in blocks:
+                for ii, jj in [(i+r, j+r) for r in range(n)]:
+                    if jj in parent_new:
+                        result.append(ii)
+            if new is None:
+                new = set(result)
+            else:
+                new.intersection_update(result)
+        return new

=== modified file 'bzrlib/tests/test_merge.py'
--- a/bzrlib/tests/test_merge.py	2007-09-14 09:51:38 +0000
+++ b/bzrlib/tests/test_merge.py	2007-12-03 21:33:36 +0000
@@ -20,17 +20,19 @@
 from bzrlib import (
     conflicts,
     errors,
+    knit,
     merge as _mod_merge,
     option,
     progress,
+    versionedfile,
     )
 from bzrlib.branch import Branch
 from bzrlib.conflicts import ConflictList, TextConflict
 from bzrlib.errors import UnrelatedBranches, NoCommits, BzrCommandError
-from bzrlib.merge import transform_tree, merge_inner
+from bzrlib.merge import transform_tree, merge_inner, _PlanMerge
 from bzrlib.osutils import pathjoin, file_kind
 from bzrlib.revision import common_ancestor
-from bzrlib.tests import TestCaseWithTransport
+from bzrlib.tests import TestCaseWithTransport, TestCaseWithMemoryTransport
 from bzrlib.trace import (enable_test_log, disable_test_log)
 from bzrlib.workingtree import WorkingTree
 
@@ -285,3 +287,84 @@
         merger.merge_type = _mod_merge.Merge3Merger
         merger.do_merge()
         self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
+
+
+class TestPlanMerge(TestCaseWithMemoryTransport):
+
+    def setUp(self):
+        TestCaseWithMemoryTransport.setUp(self)
+        self.vf = knit.KnitVersionedFile('root', self.get_transport(),
+                                         create=True)
+        self.plan_merge_vf = versionedfile._PlanMergeVersionedFile('root',
+                                                                   [self.vf])
+
+    def add_version(self, version_id, parents, text):
+        self.vf.add_lines(version_id, parents, [c+'\n' for c in text])
+
+    def add_uncommitted_version(self, version_id, parents, text):
+        self.plan_merge_vf.add_lines(version_id, parents,
+                                     [c+'\n' for c in text])
+
+    def setup_plan_merge(self):
+        self.add_version('A', [], 'abc')
+        self.add_version('B', ['A'], 'acehg')
+        self.add_version('C', ['A'], 'fabg')
+        return _PlanMerge('B', 'C', self.plan_merge_vf)
+
+    def setup_plan_merge_uncommitted(self):
+        self.add_version('A', [], 'abc')
+        self.add_uncommitted_version('B:', ['A'], 'acehg')
+        self.add_uncommitted_version('C:', ['A'], 'fabg')
+        return _PlanMerge('B:', 'C:', self.plan_merge_vf)
+
+    def test_unique_lines(self):
+        plan = self.setup_plan_merge()
+        self.assertEqual(plan._unique_lines(
+            plan._get_matching_blocks('B', 'C')),
+            ([1, 2, 3], [0, 2]))
+
+    def test_find_new(self):
+        plan = self.setup_plan_merge()
+        self.assertEqual(set([2, 3, 4]), plan._find_new('B'))
+        self.assertEqual(set([0, 3]), plan._find_new('C'))
+
+    def test_find_new2(self):
+        self.add_version('A', [], 'abc')
+        self.add_version('B', ['A'], 'abcde')
+        self.add_version('C', ['A'], 'abcefg')
+        self.add_version('D', ['A', 'B', 'C'], 'abcdegh')
+        my_plan = _PlanMerge('B', 'D', self.plan_merge_vf)
+        self.assertEqual(set([5, 6]), my_plan._find_new('D'))
+        self.assertEqual(set(), my_plan._find_new('A'))
+
+    def test_find_new_no_ancestors(self):
+        self.add_version('A', [], 'abc')
+        self.add_version('B', [], 'xyz')
+        my_plan = _PlanMerge('A', 'B', self.vf)
+        self.assertEqual(set([0, 1, 2]), my_plan._find_new('A'))
+
+    def test_plan_merge(self):
+        self.setup_plan_merge()
+        plan = self.plan_merge_vf.plan_merge('B', 'C')
+        self.assertEqual([
+                          ('new-b', 'f\n'),
+                          ('unchanged', 'a\n'),
+                          ('killed-b', 'c\n'),
+                          ('new-a', 'e\n'),
+                          ('new-a', 'h\n'),
+                          ('killed-a', 'b\n'),
+                          ('unchanged', 'g\n')],
+                         list(plan))
+
+    def test_plan_merge_uncommitted_files(self):
+        self.setup_plan_merge_uncommitted()
+        plan = self.plan_merge_vf.plan_merge('B:', 'C:')
+        self.assertEqual([
+                          ('new-b', 'f\n'),
+                          ('unchanged', 'a\n'),
+                          ('killed-b', 'c\n'),
+                          ('new-a', 'e\n'),
+                          ('new-a', 'h\n'),
+                          ('killed-a', 'b\n'),
+                          ('unchanged', 'g\n')],
+                         list(plan))

=== modified file 'bzrlib/tests/test_versionedfile.py'
--- a/bzrlib/tests/test_versionedfile.py	2007-11-09 17:50:31 +0000
+++ b/bzrlib/tests/test_versionedfile.py	2007-12-03 21:38:07 +0000
@@ -797,6 +797,68 @@
         return self._factory
 
 
+class TestPlanMergeVersionedFile(TestCaseWithMemoryTransport):
+
+    def setUp(self):
+        TestCaseWithMemoryTransport.setUp(self)
+        self.vf1 = KnitVersionedFile('root', self.get_transport(), create=True)
+        self.vf2 = KnitVersionedFile('root', self.get_transport(), create=True)
+        self.plan_merge_vf = versionedfile._PlanMergeVersionedFile('root',
+            [self.vf1, self.vf2])
+
+    def test_add_lines(self):
+        self.plan_merge_vf.add_lines('a:', [], [])
+        self.assertRaises(ValueError, self.plan_merge_vf.add_lines, 'a', [],
+                          [])
+        self.assertRaises(ValueError, self.plan_merge_vf.add_lines, 'a:', None,
+                          [])
+        self.assertRaises(ValueError, self.plan_merge_vf.add_lines, 'a:', [],
+                          None)
+
+    def test_ancestry(self):
+        self.vf1.add_lines('A', [], [])
+        self.vf1.add_lines('B', ['A'], [])
+        self.plan_merge_vf.add_lines('C:', ['B'], [])
+        self.plan_merge_vf.add_lines('D:', ['C:'], [])
+        self.assertEqual(set(['A', 'B', 'C:', 'D:']),
+            self.plan_merge_vf.get_ancestry('D:', topo_sorted=False))
+
+    def setup_abcde(self):
+        self.vf1.add_lines('A', [], ['a'])
+        self.vf1.add_lines('B', ['A'], ['b'])
+        self.vf2.add_lines('C', [], ['c'])
+        self.vf2.add_lines('D', ['C'], ['d'])
+        self.plan_merge_vf.add_lines('E:', ['B', 'D'], ['e'])
+
+    def test_ancestry_uses_all_versionedfiles(self):
+        self.setup_abcde()
+        self.assertEqual(set(['A', 'B', 'C', 'D', 'E:']),
+            self.plan_merge_vf.get_ancestry('E:', topo_sorted=False))
+
+    def test_ancestry_raises_revision_not_present(self):
+        error = self.assertRaises(errors.RevisionNotPresent,
+                                  self.plan_merge_vf.get_ancestry, 'E:', False)
+        self.assertContainsRe(str(error), '{E:} not present in "root"')
+
+    def test_get_parents(self):
+        self.setup_abcde()
+        self.assertEqual(['A'], self.plan_merge_vf.get_parents('B'))
+        self.assertEqual(['C'], self.plan_merge_vf.get_parents('D'))
+        self.assertEqual(['B', 'D'], self.plan_merge_vf.get_parents('E:'))
+        error = self.assertRaises(errors.RevisionNotPresent,
+                                  self.plan_merge_vf.get_parents, 'F')
+        self.assertContainsRe(str(error), '{F} not present in "root"')
+
+    def test_get_lines(self):
+        self.setup_abcde()
+        self.assertEqual(['a'], self.plan_merge_vf.get_lines('A'))
+        self.assertEqual(['c'], self.plan_merge_vf.get_lines('C'))
+        self.assertEqual(['e'], self.plan_merge_vf.get_lines('E:'))
+        error = self.assertRaises(errors.RevisionNotPresent,
+                                  self.plan_merge_vf.get_lines, 'F')
+        self.assertContainsRe(str(error), '{F} not present in "root"')
+
+
 class InterString(versionedfile.InterVersionedFile):
     """An inter-versionedfile optimised code path for strings.
 

=== modified file 'bzrlib/tree.py'
--- a/bzrlib/tree.py	2007-11-21 13:21:38 +0000
+++ b/bzrlib/tree.py	2007-12-03 21:33:36 +0000
@@ -299,14 +299,40 @@
         uncommitted changes in the other tree, they will be assigned to the
         'other:' pseudo-revision.
         """
-        from bzrlib import merge
-        annotated_a = list(self.annotate_iter(file_id,
-                                              _mod_revision.CURRENT_REVISION))
-        annotated_b = list(other.annotate_iter(file_id, 'other:'))
-        ancestors_a = self._get_ancestors(_mod_revision.CURRENT_REVISION)
-        ancestors_b = other._get_ancestors('other:')
-        return merge._plan_annotate_merge(annotated_a, annotated_b,
-                                          ancestors_a, ancestors_b)
+        from bzrlib import merge, versionedfile
+        vf = versionedfile._PlanMergeVersionedFile(file_id)
+        last_revision_a = self._get_file_revision(file_id, vf, 'this:')
+        last_revision_b = other._get_file_revision(file_id, vf, 'other:')
+        return vf.plan_merge(last_revision_a, last_revision_b)
+
+    def _get_file_revision(self, file_id, vf, tree_revision):
+        def file_revision(revision_tree):
+            revision_tree.lock_read()
+            try:
+                return revision_tree.inventory[file_id].revision
+            finally:
+                revision_tree.unlock()
+
+        def iter_parent_trees():
+            for revision_id in self.get_parent_ids():
+                try:
+                    yield self.revision_tree(revision_id)
+                except:
+                    yield self.repository.revision_tree(revision_id)
+
+        if getattr(self, '_get_weave', None) is None:
+            last_revision = tree_revision
+            parent_revisions = [file_revision(t) for t in iter_parent_trees()]
+            vf.add_lines(last_revision, parent_revisions,
+                         self.get_file(file_id).readlines())
+            repo = self.branch.repository
+            transaction = repo.get_transaction()
+            base_vf = repo.weave_store.get_weave(file_id, transaction)
+        else:
+            last_revision = file_revision(self)
+            base_vf = self._get_weave(file_id)
+        vf.fallback_versionedfiles.append(base_vf)
+        return last_revision
 
     inventory = property(_get_inventory,
                          doc="Inventory of this Tree")

=== modified file 'bzrlib/versionedfile.py'
--- a/bzrlib/versionedfile.py	2007-11-13 21:39:37 +0000
+++ b/bzrlib/versionedfile.py	2007-12-03 21:38:07 +0000
@@ -492,6 +492,105 @@
         return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
 
 
+class _PlanMergeVersionedFile(object):
+    """A VersionedFile for uncommitted and committed texts.
+
+    It is intended to allow merges to be planned with working tree texts.
+    It implements only the small part of the VersionedFile interface used by
+    PlanMerge.  It falls back to multiple versionedfiles for data not stored in
+    _PlanMergeVersionedFile itself.
+    """
+
+    def __init__(self, file_id, fallback_versionedfiles=None):
+        """Constuctor
+
+        :param file_id: Used when raising exceptions.
+        :param fallback_versionedfiles: If supplied, the set of fallbacks to
+            use.  Otherwise, _PlanMergeVersionedFile.fallback_versionedfiles
+            can be appended to later.
+        """
+        self._file_id = file_id
+        if fallback_versionedfiles is None:
+            self.fallback_versionedfiles = []
+        else:
+            self.fallback_versionedfiles = fallback_versionedfiles
+        self._parents = {}
+        self._lines = {}
+
+    def plan_merge(self, ver_a, ver_b):
+        """See VersionedFile.plan_merge"""
+        from merge import _PlanMerge
+        return _PlanMerge(ver_a, ver_b, self).plan_merge()
+
+    def add_lines(self, version_id, parents, lines):
+        """See VersionedFile.add_lines
+
+        Lines are added locally, not fallback versionedfiles.  Also, ghosts are
+        permitted.  Only reserved ids are permitted.
+        """
+        if not revision.is_reserved_id(version_id):
+            raise ValueError('Only reserved ids may be used')
+        if parents is None:
+            raise ValueError('Parents may not be None')
+        if lines is None:
+            raise ValueError('Lines may not be None')
+        self._parents[version_id] = parents
+        self._lines[version_id] = lines
+
+    def get_lines(self, version_id):
+        """See VersionedFile.get_ancestry"""
+        lines = self._lines.get(version_id)
+        if lines is not None:
+            return lines
+        for versionedfile in self.fallback_versionedfiles:
+            try:
+                return versionedfile.get_lines(version_id)
+            except errors.RevisionNotPresent:
+                continue
+        else:
+            raise errors.RevisionNotPresent(version_id, self._file_id)
+
+    def get_ancestry(self, version_id, topo_sorted=False):
+        """See VersionedFile.get_ancestry.
+
+        Note that this implementation assumes that if a VersionedFile can
+        answer get_ancestry at all, it can give an authoritative answer.  In
+        fact, ghosts can invalidate this assumption.  But it's good enough
+        99% of the time, and far cheaper/simpler.
+
+        Also note that the results of this version are never topologically
+        sorted, and are a set.
+        """
+        if topo_sorted:
+            raise ValueError('This implementation does not provide sorting')
+        parents = self._parents.get(version_id)
+        if parents is None:
+            for vf in self.fallback_versionedfiles:
+                try:
+                    return vf.get_ancestry(version_id, topo_sorted=False)
+                except errors.RevisionNotPresent:
+                    continue
+            else:
+                raise errors.RevisionNotPresent(version_id, self._file_id)
+        ancestry = set([version_id])
+        for parent in parents:
+            ancestry.update(self.get_ancestry(parent, topo_sorted=False))
+        return ancestry
+
+    def get_parents(self, version_id):
+        """See VersionedFile.get_parents"""
+        parents = self._parents.get(version_id)
+        if parents is not None:
+            return parents
+        for versionedfile in self.fallback_versionedfiles:
+            try:
+                return versionedfile.get_parents(version_id)
+            except errors.RevisionNotPresent:
+                continue
+        else:
+            raise errors.RevisionNotPresent(version_id, self._file_id)
+
+
 class PlanWeaveMerge(TextMerge):
     """Weave merge that takes a plan as its input.
     




More information about the bazaar-commits mailing list