71
79
self.repo = self.bzrdir.find_repository()
72
80
self.pb.note('Reconciling repository %s',
73
81
self.repo.bzrdir.root_transport.base)
82
self.pb.update("Reconciling repository", 0, 1)
74
83
repo_reconciler = self.repo.reconcile(thorough=True)
75
84
self.inconsistent_parents = repo_reconciler.inconsistent_parents
76
85
self.garbage_inventories = repo_reconciler.garbage_inventories
77
self.pb.note('Reconciliation complete.')
86
if repo_reconciler.aborted:
88
'Reconcile aborted: revision index has inconsistent parents.')
90
'Run "bzr check" for more details.')
92
self.pb.note('Reconciliation complete.')
80
95
class RepoReconciler(object):
81
96
"""Reconciler that reconciles a repository.
98
The goal of repository reconciliation is to make any derived data
99
consistent with the core data committed by a user. This can involve
100
reindexing, or removing unreferenced data if that can interfere with
101
queries in a given repository.
83
103
Currently this consists of an inventory reweave with revision cross-checks.
340
365
self.garbage_inventories = len(garbage)
341
366
for revision_id in garbage:
342
367
mutter('Garbage inventory {%s} found.', revision_id)
369
def _fix_text_parents(self):
370
"""Fix bad versionedfile parent entries.
372
It is possible for the parents entry in a versionedfile entry to be
373
inconsistent with the values in the revision and inventory.
375
This method finds entries with such inconsistencies, corrects their
376
parent lists, and replaces the versionedfile with a corrected version.
378
transaction = self.repo.get_transaction()
379
versions = self.revisions.versions()
380
mutter('Prepopulating revision text cache with %d revisions',
382
vf_checker = self.repo._get_versioned_file_checker()
383
# List all weaves before altering, to avoid race conditions when we
384
# delete unused weaves.
385
weaves = list(enumerate(self.repo.weave_store))
386
for num, file_id in weaves:
387
self.pb.update('Fixing text parents', num,
388
len(self.repo.weave_store))
389
vf = self.repo.weave_store.get_weave(file_id, transaction)
390
versions_with_bad_parents, unused_versions = \
391
vf_checker.check_file_version_parents(vf, file_id)
392
if (len(versions_with_bad_parents) == 0 and
393
len(unused_versions) == 0):
395
full_text_versions = set()
396
self._fix_text_parent(file_id, vf, versions_with_bad_parents,
397
full_text_versions, unused_versions)
399
def _fix_text_parent(self, file_id, vf, versions_with_bad_parents,
400
full_text_versions, unused_versions):
401
"""Fix bad versionedfile entries in a single versioned file."""
402
mutter('fixing text parent: %r (%d versions)', file_id,
403
len(versions_with_bad_parents))
404
mutter('(%d need to be full texts, %d are unused)',
405
len(full_text_versions), len(unused_versions))
406
new_vf = self.repo.weave_store.get_empty('temp:%s' % file_id,
409
for version in vf.versions():
410
if version in unused_versions:
412
elif version in versions_with_bad_parents:
413
parents = versions_with_bad_parents[version][1]
415
parents = vf.get_parents(version)
416
new_parents[version] = parents
417
if not len(new_parents):
418
# No used versions, remove the VF.
419
self.repo.weave_store.delete(file_id, self.transaction)
421
for version in TopoSorter(new_parents.items()).iter_topo_order():
422
lines = vf.get_lines(version)
423
parents = new_parents[version]
424
if parents and (parents[0] in full_text_versions):
425
# Force this record to be a fulltext, not a delta.
426
new_vf._add(version, lines, parents, False,
427
None, None, None, False)
429
new_vf.add_lines(version, parents, lines)
430
self.repo.weave_store.copy(new_vf, file_id, self.transaction)
431
self.repo.weave_store.delete('temp:%s' % file_id, self.transaction)
434
class PackReconciler(RepoReconciler):
435
"""Reconciler that reconciles a pack based repository.
437
Garbage inventories do not affect ancestry queries, and removal is
438
considerably more expensive as there is no separate versioned file for
439
them, so they are not cleaned. In short it is currently a no-op.
441
In future this may be a good place to hook in annotation cache checking,
442
index recreation etc.
445
# XXX: The index corruption that _fix_text_parents performs is needed for
446
# packs, but not yet implemented. The basic approach is to:
447
# - lock the names list
448
# - perform a customised pack() that regenerates data as needed
449
# - unlock the names list
450
# https://bugs.edge.launchpad.net/bzr/+bug/154173
452
def _reconcile_steps(self):
453
"""Perform the steps to reconcile this repository."""
454
if not self.thorough:
456
collection = self.repo._pack_collection
457
collection.ensure_loaded()
458
collection.lock_names()
460
packs = collection.all_packs()
461
all_revisions = self.repo.all_revision_ids()
462
total_inventories = len(list(
463
collection.inventory_index.combined_index.iter_all_entries()))
464
if len(all_revisions):
465
self._packer = repofmt.pack_repo.ReconcilePacker(
466
collection, packs, ".reconcile", all_revisions)
467
new_pack = self._packer.pack(pb=self.pb)
468
if new_pack is not None:
469
self._discard_and_save(packs)
471
# only make a new pack when there is data to copy.
472
self._discard_and_save(packs)
473
self.garbage_inventories = total_inventories - len(list(
474
collection.inventory_index.combined_index.iter_all_entries()))
476
collection._unlock_names()
478
def _discard_and_save(self, packs):
479
"""Discard some packs from the repository.
481
This removes them from the memory index, saves the in-memory index
482
which makes the newly reconciled pack visible and hides the packs to be
483
discarded, and finally renames the packs being discarded into the
484
obsolete packs directory.
486
:param packs: The packs to discard.
489
self.repo._pack_collection._remove_pack_from_memory(pack)
490
self.repo._pack_collection._save_pack_names()
491
self.repo._pack_collection._obsolete_packs(packs)