[MERGE][0.92] Fix bug 155730: force file versions with unreferenced parents to be stored as fulltexts.

Andrew Bennetts andrew at canonical.com
Thu Oct 25 01:29:54 BST 2007


This bundle fixes bug 155730, which is marked critical because it makes it hard
for affected repositories (like bzr.dev!) to be converted to the just-merged
pack format.

The problem is that not only can a repository with a revision 'revX' contain a
file version for 'revX' that the inventory for 'revX' doesn't reference, but
that other file versions might somehow list 'revX' as a parent and be recorded
as a delta of 'revX'.  This breaks code that assumes that if you have all the
knit records of every file version mentioned in every inventory in a history,
thta you'll have enough data to reconstruct any version.  This sort of confused
repository breaks that assumption, so clients making that assumption will end up
with a delta record for a file version that is a delta against a version not in
this repository.

The best fix would be to remove the 'revX' file version, and rewrite any
inventory using it to use a version already referenced.  This is a bit
complicated and would be very slow without a fair bit of work, so I haven't
implemented that fix.

Instead this fix works around the problem by forcing any file version with a
parent that is not referenced to be recorded as a fulltext.  This leaves the
per-file graph as before, which is a bit weird, but the best we can do without
rewriting inventories.  And it should fix the repository just enough to fix bug
155730.

A side-effect of this work is that it is now trivial to remove totally
unreferenced file versions (versions that no inventory in the repository use) as
part of reconcile, so I have done so.

-Andrew.

-------------- next part --------------
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: andrew.bennetts at canonical.com-20071025001405-\
#   aroz1xo9327ndwnn
# target_branch: http://bazaar-vcs.org/bzr/bzr.dev
# testament_sha1: 1997c961754f4fe0fdf43b0e88263f6401d45d25
# timestamp: 2007-10-25 10:14:23 +1000
# source_branch: http://people.ubuntu.com/~andrew/bzr/reconcile-\
#   remove-unreferenced-file-versions-bug-155730
# base_revision_id: pqm at pqm.ubuntu.com-20071022212520-al7xlieh3d7ng370
# 
# Begin patch
=== modified file 'bzrlib/check.py'
--- bzrlib/check.py	2007-10-12 03:06:52 +0000
+++ bzrlib/check.py	2007-10-24 10:52:24 +0000
@@ -214,10 +214,13 @@
             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(

=== modified file 'bzrlib/reconcile.py'
--- bzrlib/reconcile.py	2007-10-15 09:10:30 +0000
+++ bzrlib/reconcile.py	2007-10-25 00:14:05 +0000
@@ -373,21 +373,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)
+            full_text_versions = set()
+            unused_versions = set()
+            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)
+            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 = {}
@@ -398,8 +430,16 @@
                 parents = vf.get_parents(version)
             new_parents[version] = parents
         for version in topo_sort(new_parents.items()):
-            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'
--- bzrlib/repository.py	2007-10-19 17:00:10 +0000
+++ bzrlib/repository.py	2007-10-25 00:14:05 +0000
@@ -2535,6 +2535,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):
 
@@ -2544,6 +2557,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:
@@ -2566,7 +2582,20 @@
         return 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_version).
+            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_version is a set of revision_ids
+            for versions that are present in this versioned file, but not used
+            by the corresponding inventory.
+        """
+        wrong_parents = {}
+        dangling_file_version = set()
         for num, revision_id in enumerate(self.planned_revisions):
             correct_parents = self.calculate_file_version_parents(
                 revision_id, file_id)
@@ -2574,7 +2603,14 @@
                 continue
             text_revision = self.revision_versions.get_text_version(
                 file_id, revision_id)
-            knit_parents = weave.get_parents(text_revision)
+            try:
+                knit_parents = 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_version.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_version

=== modified file 'bzrlib/tests/repository_implementations/__init__.py'
--- bzrlib/tests/repository_implementations/__init__.py	2007-10-15 05:23:29 +0000
+++ bzrlib/tests/repository_implementations/__init__.py	2007-10-25 00:14:05 +0000
@@ -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.
@@ -272,16 +282,21 @@
     """
 
     def all_versions(self):
-        return ['rev1a', 'rev2', 'rev4', 'rev2b', 'rev4', 'rev2c', 'rev5']
+        return ['rev1a', 'rev2c', 'rev4', 'rev5']
 
     def populated_parents(self):
         return [
+            (['rev1a'], 'rev2'),
+            (['rev1a'], 'rev2b'),
             (['rev2'], 'rev3'),
             (['rev2'], 'rev4'),
             (['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,7 +309,11 @@
 
     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'\] "
@@ -310,7 +329,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 +381,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(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
@@ -415,7 +503,7 @@
     """
 
     def all_versions(self):
-        return ['basis', 'modified-something-else', 'current']
+        return ['basis', 'current']
 
     def populated_parents(self):
         return [
@@ -426,14 +514,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', [])
@@ -520,6 +611,7 @@
     TooManyParentsScenario,
     ClaimedFileParentDidNotModifyFileScenario,
     IncorrectlyOrderedParentsScenario,
+    UnreferencedFileParentsFromNoOpMergeScenario,
     ]
     
 

=== modified file 'bzrlib/tests/repository_implementations/test_check_reconcile.py'
--- bzrlib/tests/repository_implementations/test_check_reconcile.py	2007-10-03 10:54:07 +0000
+++ bzrlib/tests/repository_implementations/test_check_reconcile.py	2007-10-24 10:56:39 +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 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.
@@ -143,6 +157,12 @@
             vf_shas,
             self.shas_for_versions_of_file(repo, scenario.all_versions()))
 
+        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."""
         scenario = self.scenario_class(self)

# Begin bundle
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
pK1KIgAuoUHhvHr/H/i7kinChIG+fp0o


More information about the bazaar mailing list