[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