Rev 2945: (Andrew Bennetts) Reconcile will force file versions with unreferenced parents to be stored as fulltexts (fixes bug 155730). in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Fri Oct 26 08:48:09 BST 2007


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

------------------------------------------------------------
revno: 2945
revision-id: pqm at pqm.ubuntu.com-20071026074806-v0hw6v1dbm6hu9oj
parent: pqm at pqm.ubuntu.com-20071025232834-wcuoh6azwgez5b80
parent: andrew.bennetts at canonical.com-20071026065843-zof5jrpbfic8cmxl
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Fri 2007-10-26 08:48:06 +0100
message:
  (Andrew Bennetts) Reconcile will force file versions with unreferenced parents to be stored as fulltexts (fixes bug 155730).
modified:
  bzrlib/check.py                check.py-20050309040759-f3a679400c06bcc1
  bzrlib/reconcile.py            reweave_inventory.py-20051108164726-1e5e0934febac06e
  bzrlib/repository.py           rev_storage.py-20051111201905-119e9401e46257e3
  bzrlib/tests/repository_implementations/__init__.py __init__.py-20060131092037-9564957a7d4a841b
  bzrlib/tests/repository_implementations/test_check_reconcile.py test_broken.py-20070928125406-62236394w0jpbpd6-2
    ------------------------------------------------------------
    revno: 2927.2.14
    merged: andrew.bennetts at canonical.com-20071026065843-zof5jrpbfic8cmxl
    parent: andrew.bennetts at canonical.com-20071026064809-4f9fi8h5we7ob7wg
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Fri 2007-10-26 16:58:43 +1000
    message:
      Tweaks suggested by review.
    ------------------------------------------------------------
    revno: 2927.2.13
    merged: andrew.bennetts at canonical.com-20071026064809-4f9fi8h5we7ob7wg
    parent: andrew.bennetts at canonical.com-20071026064728-d3vy41n03fb2j8ni
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Fri 2007-10-26 16:48:09 +1000
    message:
      Display number of file versions not referenced by their corresponding inventory (i.e. dangling versions) in bzr check output.
    ------------------------------------------------------------
    revno: 2927.2.12
    merged: andrew.bennetts at canonical.com-20071026064728-d3vy41n03fb2j8ni
    parent: andrew.bennetts at canonical.com-20071026010910-5ibwby7sfga11llr
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Fri 2007-10-26 16:47:28 +1000
    message:
      Add a mutter as a hack give some basic progress indication in .bzr.log for the prepopulation step.
    ------------------------------------------------------------
    revno: 2927.2.11
    merged: andrew.bennetts at canonical.com-20071026010910-5ibwby7sfga11llr
    parent: andrew.bennetts at canonical.com-20071025001405-aroz1xo9327ndwnn
    parent: pqm at pqm.ubuntu.com-20071025232834-wcuoh6azwgez5b80
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Fri 2007-10-26 11:09:10 +1000
    message:
      Merge from bzr.dev.
    ------------------------------------------------------------
    revno: 2927.2.10
    merged: andrew.bennetts at canonical.com-20071025001405-aroz1xo9327ndwnn
    parent: andrew.bennetts at canonical.com-20071024110815-t89wamuahli15fe4
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Thu 2007-10-25 10:14:05 +1000
    message:
      More docstrings, elaborate a comment with an XXX, and remove a little bit of cruft.
    ------------------------------------------------------------
    revno: 2927.2.9
    merged: andrew.bennetts at canonical.com-20071024110815-t89wamuahli15fe4
    parent: andrew.bennetts at canonical.com-20071024110355-x7ikgyc6xrwexko9
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Wed 2007-10-24 21:08:15 +1000
    message:
      Adjust expected 'check' output in test scenarios.  All test_check_reconcile now passes.
    ------------------------------------------------------------
    revno: 2927.2.8
    merged: andrew.bennetts at canonical.com-20071024110355-x7ikgyc6xrwexko9
    parent: andrew.bennetts at canonical.com-20071024105639-8uskiounrird2i43
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Wed 2007-10-24 21:03:55 +1000
    message:
      Remove totally unreferenced file versions.  All reconcile tests passing.
    ------------------------------------------------------------
    revno: 2927.2.7
    merged: andrew.bennetts at canonical.com-20071024105639-8uskiounrird2i43
    parent: andrew.bennetts at canonical.com-20071024105224-ro2zlfg1iqvgs9rc
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Wed 2007-10-24 20:56:39 +1000
    message:
      Condense assertion message.
    ------------------------------------------------------------
    revno: 2927.2.6
    merged: andrew.bennetts at canonical.com-20071024105224-ro2zlfg1iqvgs9rc
    parent: andrew.bennetts at canonical.com-20071024104558-o2rij4yn86abdqh5
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Wed 2007-10-24 20:52:24 +1000
    message:
      Make some more check tests pass.
    ------------------------------------------------------------
    revno: 2927.2.5
    merged: andrew.bennetts at canonical.com-20071024104558-o2rij4yn86abdqh5
    parent: andrew.bennetts at canonical.com-20071024104255-c3shrqmiynjqm9n8
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Wed 2007-10-24 20:45:58 +1000
    message:
      Remove corrected_inventories, add proper description of corrected_fulltexts.
    ------------------------------------------------------------
    revno: 2927.2.4
    merged: andrew.bennetts at canonical.com-20071024104255-c3shrqmiynjqm9n8
    parent: andrew.bennetts at canonical.com-20071024081257-w4gd9pl4dp1h8xtt
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Wed 2007-10-24 20:42:55 +1000
    message:
      Don't create a 'rev3' file version in the test.
    ------------------------------------------------------------
    revno: 2927.2.3
    merged: andrew.bennetts at canonical.com-20071024081257-w4gd9pl4dp1h8xtt
    parent: andrew.bennetts at canonical.com-20071023075622-ikpj0dyoku3y6jsf
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Wed 2007-10-24 18:12:57 +1000
    message:
      Add fulltexts to avoid bug 155730.
    ------------------------------------------------------------
    revno: 2927.2.2
    merged: andrew.bennetts at canonical.com-20071023075622-ikpj0dyoku3y6jsf
    parent: andrew.bennetts at canonical.com-20071023004829-l76zvyp8sglxtgpa
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Tue 2007-10-23 17:56:22 +1000
    message:
      Only try to check versions that actually exist in the versioned file, and do a little more muttering.
    ------------------------------------------------------------
    revno: 2927.2.1
    merged: andrew.bennetts at canonical.com-20071023004829-l76zvyp8sglxtgpa
    parent: pqm at pqm.ubuntu.com-20071022212520-al7xlieh3d7ng370
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: reconcile-remove-unreferenced-file-versions-bug-155730
    timestamp: Tue 2007-10-23 10:48:29 +1000
    message:
      Tidy a scenario's all_versions slightly.
=== modified file 'bzrlib/check.py'
--- a/bzrlib/check.py	2007-10-17 09:39:41 +0000
+++ b/bzrlib/check.py	2007-10-26 06:48:09 +0000
@@ -60,6 +60,7 @@
             self.repository)
         self.unreferenced_ancestors = set()
         self.inconsistent_parents = []
+        self.dangling_versions = set()
 
     def check(self):
         self.repository.lock_read()
@@ -156,6 +157,9 @@
                         '       %s has wrong parents in index: '
                         '%r should be %r',
                         revision_id, index_parents, actual_parents)
+        if self.dangling_versions:
+            note('%6d file versions are not referenced by their inventory',
+                 len(self.dangling_versions))
 
     def check_one_rev(self, rev_id):
         """Check one revision.
@@ -214,14 +218,18 @@
             weave_checker = self.repository.get_versioned_file_checker(
                 self.planned_revisions, self.revision_versions)
             result = weave_checker.check_file_version_parents(w, weave_id)
-
-            for revision_id, (weave_parents,correct_parents) in result.items():
+            bad_parents, dangling_versions = result
+            bad_parents = bad_parents.items()
+            for revision_id, (weave_parents,correct_parents) in bad_parents:
                 self.inconsistent_parents.append(
                     (revision_id, weave_id, weave_parents, correct_parents))
+                if weave_parents is None:
+                    weave_parents = []
                 unreferenced_parents = set(weave_parents)-set(correct_parents)
                 for unreferenced_parent in unreferenced_parents:
                     self.unreferenced_ancestors.add(
                         (weave_id, unreferenced_parent))
+            self.dangling_versions.update(dangling_versions)
             self.checked_weaves[weave_id] = True
 
     def _check_revision_tree(self, rev_id):

=== modified file 'bzrlib/reconcile.py'
--- a/bzrlib/reconcile.py	2007-10-24 05:33:57 +0000
+++ b/bzrlib/reconcile.py	2007-10-26 06:58:43 +0000
@@ -376,21 +376,53 @@
         transaction = self.repo.get_transaction()
         revision_versions = repository._RevisionTextVersionCache(self.repo)
         versions = self.revisions.versions()
+        mutter('Prepopulating revision text cache with %d revisions',
+                len(versions))
         revision_versions.prepopulate_revs(versions)
+        used_file_versions = revision_versions.used_file_versions()
         for num, file_id in enumerate(self.repo.weave_store):
             self.pb.update('Fixing text parents', num,
                            len(self.repo.weave_store))
             vf = self.repo.weave_store.get_weave(file_id, transaction)
             vf_checker = self.repo.get_versioned_file_checker(
-                versions, revision_versions)
-            versions_with_bad_parents = vf_checker.check_file_version_parents(
-                vf, file_id)
-            if len(versions_with_bad_parents) == 0:
+                vf.versions(), revision_versions)
+            versions_with_bad_parents, dangling_file_versions = \
+                vf_checker.check_file_version_parents(vf, file_id)
+            if (len(versions_with_bad_parents) == 0 and
+                len(dangling_file_versions) == 0):
                 continue
-            self._fix_text_parent(file_id, vf, versions_with_bad_parents)
+            full_text_versions = set()
+            unused_versions = set()
+            for dangling_version in dangling_file_versions:
+                version = dangling_version[1]
+                if dangling_version in used_file_versions:
+                    # This version *is* used by some revision, even though it
+                    # isn't used by its own revision!  We make sure any
+                    # revision referencing it is stored as a fulltext
+                    # This avoids bug 155730: it means that clients looking at
+                    # inventories to determine the versions to fetch will not
+                    # miss a required version.  (So clients can assume that if
+                    # they have a complete revision graph, and fetch all file
+                    # versions named by those revisions inventories, then they
+                    # will not have any missing parents for 'delta' knit
+                    # records.)
+                    # XXX: A better, but more difficult and slower fix would be
+                    # to rewrite the inventories referencing this version.
+                    full_text_versions.add(version)
+                else:
+                    # This version is totally unreferenced.  It should be
+                    # removed.
+                    unused_versions.add(version)
+            self._fix_text_parent(file_id, vf, versions_with_bad_parents,
+                full_text_versions, unused_versions)
 
-    def _fix_text_parent(self, file_id, vf, versions_with_bad_parents):
+    def _fix_text_parent(self, file_id, vf, versions_with_bad_parents,
+            full_text_versions, unused_versions):
         """Fix bad versionedfile entries in a single versioned file."""
+        mutter('fixing text parent: %r (%d versions)', file_id,
+                len(versions_with_bad_parents))
+        mutter('(%d need to be full texts, %d are unused)',
+                len(full_text_versions), len(unused_versions))
         new_vf = self.repo.weave_store.get_empty('temp:%s' % file_id,
             self.transaction)
         new_parents = {}
@@ -401,8 +433,16 @@
                 parents = vf.get_parents(version)
             new_parents[version] = parents
         for version in TopoSorter(new_parents.items()).iter_topo_order():
-            new_vf.add_lines(version, new_parents[version],
-                             vf.get_lines(version))
+            if version in unused_versions:
+                continue
+            lines = vf.get_lines(version)
+            parents = new_parents[version]
+            if parents and (parents[0] in full_text_versions):
+                # Force this record to be a fulltext, not a delta.
+                new_vf._add(version, lines, parents, False,
+                    None, None, None, False)
+            else:
+                new_vf.add_lines(version, parents, lines)
         self.repo.weave_store.copy(new_vf, file_id, self.transaction)
         self.repo.weave_store.delete('temp:%s' % file_id, self.transaction)
 

=== modified file 'bzrlib/repository.py'
--- a/bzrlib/repository.py	2007-10-25 06:17:57 +0000
+++ b/bzrlib/repository.py	2007-10-26 06:58:43 +0000
@@ -2658,6 +2658,7 @@
         # XXX: this loop is very similar to
         # bzrlib.fetch.Inter1and2Helper.iter_rev_trees.
         while revs:
+            mutter('%d revisions left to prepopulate', len(revs))
             for tree in self.repository.revision_trees(revs[:100]):
                 if tree.inventory.revision_id is None:
                     tree.inventory.revision_id = tree.get_revision_id()
@@ -2672,6 +2673,19 @@
             self.revision_parents[revision_id] = parents
             return parents
 
+    def used_file_versions(self):
+        """Return a set of (revision_id, file_id) pairs for each file version
+        referenced by any inventory cached by this _RevisionTextVersionCache.
+
+        If the entire repository has been cached, this can be used to find all
+        file versions that are actually referenced by inventories.  Thus any
+        other file version is completely unused and can be removed safely.
+        """
+        result = set()
+        for inventory_summary in self.revision_versions.itervalues():
+            result.update(inventory_summary.items())
+        return result
+
 
 class VersionedFileChecker(object):
 
@@ -2681,6 +2695,9 @@
         self.repository = repository
     
     def calculate_file_version_parents(self, revision_id, file_id):
+        """Calculate the correct parents for a file version according to
+        the inventories.
+        """
         text_revision = self.revision_versions.get_text_version(
             file_id, revision_id)
         if text_revision is None:
@@ -2703,7 +2720,20 @@
         return tuple(new_parents)
 
     def check_file_version_parents(self, weave, file_id):
-        result = {}
+        """Check the parents stored in a versioned file are correct.
+
+        It also detects file versions that are not referenced by their
+        corresponding revision's inventory.
+
+        :returns: A tuple of (wrong_parents, dangling_file_versions).
+            wrong_parents is a dict mapping {revision_id: (stored_parents,
+            correct_parents)} for each revision_id where the stored parents
+            are not correct.  dangling_file_versions is a set of (file_id,
+            revision_id) tuples for versions that are present in this versioned
+            file, but not used by the corresponding inventory.
+        """
+        wrong_parents = {}
+        dangling_file_versions = set()
         for num, revision_id in enumerate(self.planned_revisions):
             correct_parents = self.calculate_file_version_parents(
                 revision_id, file_id)
@@ -2711,7 +2741,14 @@
                 continue
             text_revision = self.revision_versions.get_text_version(
                 file_id, revision_id)
-            knit_parents = tuple(weave.get_parents(text_revision))
+            try:
+                knit_parents = tuple(weave.get_parents(revision_id))
+            except errors.RevisionNotPresent:
+                knit_parents = None
+            if text_revision != revision_id:
+                # This file version is not referenced by its corresponding
+                # inventory!
+                dangling_file_versions.add((file_id, revision_id))
             if correct_parents != knit_parents:
-                result[revision_id] = (knit_parents, correct_parents)
-        return result
+                wrong_parents[revision_id] = (knit_parents, correct_parents)
+        return wrong_parents, dangling_file_versions

=== modified file 'bzrlib/tests/repository_implementations/__init__.py'
--- a/bzrlib/tests/repository_implementations/__init__.py	2007-10-17 09:39:41 +0000
+++ b/bzrlib/tests/repository_implementations/__init__.py	2007-10-26 06:58:43 +0000
@@ -108,9 +108,9 @@
     A subclass needs to define the following methods:
         :populate_repository: a method to use to populate a repository with
             sample revisions, inventories and file versions.
-        :all_versions: all the versions in repository.  run_test verifies
-            that the text of each of these versions of the file is unchanged
-            by the reconcile.
+        :all_versions_after_reconcile: all the versions in repository after
+            reconcile.  run_test verifies that the text of each of these
+            versions of the file is unchanged by the reconcile.
         :populated_parents: a list of (parents list, revision).  Each version
             of the file is verified to have the given parents before running
             the reconcile.  i.e. this is used to assert that the repo from the
@@ -119,20 +119,30 @@
             of the file is verified to have the given parents after the
             reconcile.  i.e. this is used to assert that reconcile made the
             changes we expect it to make.
+    
+    A subclass may define the following optional method as well:
+        :corrected_fulltexts: a list of file versions that should be stored as
+            fulltexts (not deltas) after reconcile.  run_test will verify that
+            this occurs.
     """
 
     def __init__(self, test_case):
         self.test_case = test_case
 
     def make_one_file_inventory(self, repo, revision, parents,
-                                inv_revision=None, root_revision=None):
+                                inv_revision=None, root_revision=None,
+                                file_contents=None, make_file_version=True):
         return self.test_case.make_one_file_inventory(
             repo, revision, parents, inv_revision=inv_revision,
-            root_revision=root_revision)
+            root_revision=root_revision, file_contents=file_contents,
+            make_file_version=make_file_version)
 
     def add_revision(self, repo, revision_id, inv, parent_ids):
         return self.test_case.add_revision(repo, revision_id, inv, parent_ids)
 
+    def corrected_fulltexts(self):
+        return []
+
 
 class UndamagedRepositoryScenario(BrokenRepoScenario):
     """A scenario where the repository has no damage.
@@ -140,7 +150,7 @@
     It has a single revision, 'rev1a', with a single file.
     """
 
-    def all_versions(self):
+    def all_versions_after_reconcile(self):
         return ('rev1a', )
 
     def populated_parents(self):
@@ -168,7 +178,7 @@
     'rev2', preserving 'rev1a' as a parent.
     """
 
-    def all_versions(self):
+    def all_versions_after_reconcile(self):
         return ('rev1a', 'rev1b', 'rev2')
 
     def populated_parents(self):
@@ -218,7 +228,7 @@
     inaccessbile (i.e. remove 'rev1c' from the parents of a-file's rev3).
     """
 
-    def all_versions(self):
+    def all_versions_after_reconcile(self):
         return ('rev2', 'rev3')
 
     def populated_parents(self):
@@ -271,17 +281,22 @@
     inventory.
     """
 
-    def all_versions(self):
-        return ('rev1a', 'rev2', 'rev4', 'rev2b', 'rev4', 'rev2c', 'rev5')
+    def all_versions_after_reconcile(self):
+        return ('rev1a', 'rev2c', 'rev4', 'rev5')
 
     def populated_parents(self):
-        return (
+        return [
+            (('rev1a',), 'rev2'),
+            (('rev1a',), 'rev2b'),
             (('rev2',), 'rev3'),
             (('rev2',), 'rev4'),
-            (('rev2', 'rev2c'), 'rev5'))
+            (('rev2', 'rev2c'), 'rev5')]
 
     def corrected_parents(self):
         return (
+            # rev2 and rev2b have been removed.
+            (None, 'rev2'),
+            (None, 'rev2b'),
             # rev3's accessible parent inventories all have rev1a as the last
             # modifier.
             (('rev1a',), 'rev3'),
@@ -294,13 +309,18 @@
 
     def check_regexes(self):
         return [
-            "3 inconsistent parents",
+            "5 inconsistent parents",
+            r"a-file-id version rev2 has parents \('rev1a',\) "
+            r"but should have \(\)",
+            r"a-file-id version rev2b has parents \('rev1a',\) "
+            r"but should have \(\)",
             r"a-file-id version rev3 has parents \('rev2',\) "
             r"but should have \('rev1a',\)",
             r"a-file-id version rev5 has parents \('rev2', 'rev2c'\) "
             r"but should have \('rev2c',\)",
             r"a-file-id version rev4 has parents \('rev2',\) "
             r"but should have \('rev1a',\)",
+            "2 file versions are not referenced by their inventory",
             ]
 
     def populate_repository(self, repo):
@@ -310,7 +330,8 @@
         self.add_revision(repo, 'rev1a', inv, [])
 
         # make rev2, with a-file.
-        # a-file is unmodified from rev1a.
+        # a-file is unmodified from rev1a, and an unreferenced rev2 file
+        # version is present in the repository.
         self.make_one_file_inventory(
             repo, 'rev2', ['rev1a'], inv_revision='rev1a')
         self.add_revision(repo, 'rev2', inv, ['rev1a'])
@@ -361,6 +382,74 @@
         self.add_revision(repo, 'rev5', inv, ['rev2', 'rev2c'])
 
 
+class UnreferencedFileParentsFromNoOpMergeScenario(BrokenRepoScenario):
+    """
+    rev1a and rev1b with identical contents
+    rev2 revision has parents of [rev1a, rev1b]
+    There is a a-file:rev2 file version, not referenced by the inventory.
+    """
+
+    def all_versions_after_reconcile(self):
+        return ('rev1a', 'rev1b', 'rev2', 'rev4')
+
+    def populated_parents(self):
+        return (
+            ((), 'rev1a'),
+            ((), 'rev1b'),
+            (('rev1a', 'rev1b'), 'rev2'),
+            (None, 'rev3'),
+            (('rev2',), 'rev4'),
+            )
+
+    def corrected_parents(self):
+        return (
+            ((), 'rev1a'),
+            ((), 'rev1b'),
+            ((), 'rev2'),
+            (None, 'rev3'),
+            (('rev2',), 'rev4'),
+            )
+
+    def corrected_fulltexts(self):
+        return ['rev4']
+
+    def check_regexes(self):
+        return []
+
+    def populate_repository(self, repo):
+        # make rev1a: A well-formed revision, containing 'a-file'
+        inv1a = self.make_one_file_inventory(
+            repo, 'rev1a', [], root_revision='rev1a')
+        self.add_revision(repo, 'rev1a', inv1a, [])
+
+        # make rev1b: A well-formed revision, containing 'a-file'
+        # rev1b of a-file has the exact same contents as rev1a.
+        file_contents = repo.revision_tree('rev1a').get_file_text('a-file-id')
+        inv = self.make_one_file_inventory(
+            repo, 'rev1b', [], root_revision='rev1b',
+            file_contents=file_contents)
+        self.add_revision(repo, 'rev1b', inv, [])
+
+        # make rev2, a merge of rev1a and rev1b, with a-file.
+        # a-file is unmodified from rev1a and rev1b, but a new version is
+        # wrongly present anyway.
+        inv = self.make_one_file_inventory(
+            repo, 'rev2', ['rev1a', 'rev1b'], inv_revision='rev1a',
+            file_contents=file_contents)
+        self.add_revision(repo, 'rev2', inv, ['rev1a', 'rev1b'])
+
+        # rev3: a-file unchanged from rev2, but wrongly referencing rev2 of the
+        # file in its inventory.
+        inv = self.make_one_file_inventory(
+            repo, 'rev3', ['rev2'], inv_revision='rev2',
+            file_contents=file_contents, make_file_version=False)
+        self.add_revision(repo, 'rev3', inv, ['rev2'])
+
+        # rev4: a modification of a-file on top of rev3.
+        inv = self.make_one_file_inventory(repo, 'rev4', ['rev2'])
+        self.add_revision(repo, 'rev4', inv, ['rev3'])
+
+
 class TooManyParentsScenario(BrokenRepoScenario):
     """A scenario where 'broken-revision' of 'a-file' claims to have parents
     ['good-parent', 'bad-parent'].  However 'bad-parent' is in the ancestry of
@@ -368,7 +457,7 @@
     ['good-parent'].
     """
 
-    def all_versions(self):
+    def all_versions_after_reconcile(self):
         return ('bad-parent', 'good-parent', 'broken-revision')
 
     def populated_parents(self):
@@ -414,8 +503,8 @@
     'modified-something-else' is the parent file version.
     """
 
-    def all_versions(self):
-        return ('basis', 'modified-something-else', 'current')
+    def all_versions_after_reconcile(self):
+        return ('basis', 'current')
 
     def populated_parents(self):
         return (
@@ -426,14 +515,17 @@
     def corrected_parents(self):
         return (
             ((), 'basis'),
-            (('basis',), 'modified-something-else'),
+            (None, 'modified-something-else'),
             (('basis',), 'current'))
 
     def check_regexes(self):
         return (
-            '1 inconsistent parents',
+            '2 inconsistent parents',
             r"\* a-file-id version current has parents "
-            r"\('modified-something-else',\) but should have \('basis',\)")
+            r"\('modified-something-else',\) but should have \('basis',\)",
+            r"\* a-file-id version modified-something-else has parents "
+            r"\('basis',\) but should have \(\)",
+            )
 
     def populate_repository(self, repo):
         inv = self.make_one_file_inventory(repo, 'basis', ())
@@ -466,7 +558,7 @@
     iteration order is arbitrary, it is also consistent within a single test).
     """
 
-    def all_versions(self):
+    def all_versions_after_reconcile(self):
         return ['parent-1', 'parent-2', 'broken-revision-1-2',
                 'broken-revision-2-1']
 
@@ -520,6 +612,7 @@
     TooManyParentsScenario,
     ClaimedFileParentDidNotModifyFileScenario,
     IncorrectlyOrderedParentsScenario,
+    UnreferencedFileParentsFromNoOpMergeScenario,
     ]
     
 

=== modified file 'bzrlib/tests/repository_implementations/test_check_reconcile.py'
--- a/bzrlib/tests/repository_implementations/test_check_reconcile.py	2007-10-17 09:39:41 +0000
+++ b/bzrlib/tests/repository_implementations/test_check_reconcile.py	2007-10-26 06:58:43 +0000
@@ -65,7 +65,8 @@
         repo.add_revision(revision_id,revision, inv)
 
     def make_one_file_inventory(self, repo, revision, parents,
-                                inv_revision=None, root_revision=None):
+                                inv_revision=None, root_revision=None,
+                                file_contents=None, make_file_version=True):
         """Make an inventory containing a version of a file with ID 'a-file'.
 
         The file's ID will be 'a-file', and its filename will be 'a file name',
@@ -78,6 +79,9 @@
             inventory entry.  Otherwise, this defaults to revision.
         :param root_revision: if not None, the inventory's root.revision will
             be set to this.
+        :param file_contents: if not None, the contents of this file version.
+            Otherwise a unique default (based on revision ID) will be
+            generated.
         """
         inv = Inventory(revision_id=revision)
         if root_revision is not None:
@@ -89,12 +93,14 @@
         else:
             entry.revision = revision
         entry.text_size = 0
-        file_contents = '%sline\n' % entry.revision
+        if file_contents is None:
+            file_contents = '%sline\n' % entry.revision
         entry.text_sha1 = sha.sha(file_contents).hexdigest()
         inv.add(entry)
-        vf = repo.weave_store.get_weave_or_empty(file_id,
-                                                 repo.get_transaction())
-        vf.add_lines(revision, parents, [file_contents])
+        if make_file_version:
+            vf = repo.weave_store.get_weave_or_empty(file_id,
+                                                     repo.get_transaction())
+            vf.add_lines(revision, parents, [file_contents])
         return inv
 
     def require_repo_suffers_text_parent_corruption(self, repo):
@@ -106,14 +112,22 @@
         return tuple(repo.weave_store.get_weave('a-file-id',
             repo.get_transaction()).get_parents(revision_id))
 
+    def assertFileVersionAbsent(self, repo, revision_id):
+        self.assertFalse(repo.weave_store.get_weave('a-file-id',
+            repo.get_transaction()).has_version(revision_id),
+            'File version %s wrongly present.' % (revision_id,))
+
     def assertParentsMatch(self, expected_parents_for_versions, repo,
                            when_description):
         for expected_parents, version in expected_parents_for_versions:
-            found_parents = self.file_parents(repo, version)
-            self.assertEqual(expected_parents, found_parents,
-                "Expected version %s of a-file-id to have parents %s %s "
-                "reconcile, but it has %s instead."
-                % (version, expected_parents, when_description, found_parents))
+            if expected_parents is None:
+                self.assertFileVersionAbsent(repo, version)
+            else:
+                found_parents = self.file_parents(repo, version)
+                self.assertEqual(expected_parents, found_parents,
+                    "%s reconcile %s has parents %s, should have %s."
+                    % (when_description, version, found_parents,
+                       expected_parents))
 
     def shas_for_versions_of_file(self, repo, versions):
         """Get the SHA-1 hashes of the versions of 'a-file' in the repository.
@@ -134,14 +148,22 @@
         repo = self.make_populated_repository(scenario.populate_repository)
         self.require_repo_suffers_text_parent_corruption(repo)
         self.assertParentsMatch(scenario.populated_parents(), repo, 'before')
-        vf_shas = self.shas_for_versions_of_file(repo, scenario.all_versions())
+        vf_shas = self.shas_for_versions_of_file(
+            repo, scenario.all_versions_after_reconcile())
         result = repo.reconcile(thorough=True)
         self.assertParentsMatch(scenario.corrected_parents(), repo, 'after')
         # The contents of the versions in the versionedfile should be the same
         # after the reconcile.
         self.assertEqual(
             vf_shas,
-            self.shas_for_versions_of_file(repo, scenario.all_versions()))
+            self.shas_for_versions_of_file(
+                repo, scenario.all_versions_after_reconcile()))
+
+        for file_version in scenario.corrected_fulltexts():
+            vf = repo.weave_store.get_weave(
+                'a-file-id', repo.get_transaction())
+            self.assertEqual('fulltext', vf._index.get_method(file_version),
+                '%r should be fulltext' % (file_version,))
 
     def test_check_behaviour(self):
         """Populate a repository and check it, and verify the output."""




More information about the bazaar-commits mailing list