~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/reconcile.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-01-03 18:09:01 UTC
  • mfrom: (3159.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20080103180901-w987y1ftqoh02qbm
(vila) Fix #179368 by keeping the current range hint on
        ShortReadvErrors

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
"""Reconcilers are able to fix some potential data errors in a branch."""
18
18
 
19
19
 
20
 
__all__ = ['reconcile', 'Reconciler', 'RepoReconciler', 'KnitReconciler']
21
 
 
22
 
 
23
 
from bzrlib import ui
24
 
from bzrlib.trace import mutter
 
20
__all__ = [
 
21
    'KnitReconciler',
 
22
    'PackReconciler',
 
23
    'reconcile',
 
24
    'Reconciler',
 
25
    'RepoReconciler',
 
26
    ]
 
27
 
 
28
 
 
29
from bzrlib import (
 
30
    errors,
 
31
    ui,
 
32
    repository,
 
33
    repofmt,
 
34
    )
 
35
from bzrlib.trace import mutter, note
25
36
from bzrlib.tsort import TopoSorter
26
37
 
27
38
 
68
79
        self.repo = self.bzrdir.find_repository()
69
80
        self.pb.note('Reconciling repository %s',
70
81
                     self.repo.bzrdir.root_transport.base)
 
82
        self.pb.update("Reconciling repository", 0, 1)
71
83
        repo_reconciler = self.repo.reconcile(thorough=True)
72
84
        self.inconsistent_parents = repo_reconciler.inconsistent_parents
73
85
        self.garbage_inventories = repo_reconciler.garbage_inventories
74
 
        self.pb.note('Reconciliation complete.')
 
86
        if repo_reconciler.aborted:
 
87
            self.pb.note(
 
88
                'Reconcile aborted: revision index has inconsistent parents.')
 
89
            self.pb.note(
 
90
                'Run "bzr check" for more details.')
 
91
        else:
 
92
            self.pb.note('Reconciliation complete.')
75
93
 
76
94
 
77
95
class RepoReconciler(object):
78
96
    """Reconciler that reconciles a repository.
79
97
 
 
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.
 
102
 
80
103
    Currently this consists of an inventory reweave with revision cross-checks.
81
104
    """
82
105
 
89
112
        """
90
113
        self.garbage_inventories = 0
91
114
        self.inconsistent_parents = 0
 
115
        self.aborted = False
92
116
        self.repo = repo
93
117
        self.thorough = thorough
94
118
 
170
194
                # This is done to avoid a revision_count * time-to-write additional overhead on 
171
195
                # reconcile.
172
196
                new_inventory_vf._check_write_ok()
173
 
                Weave._add_lines(new_inventory_vf, rev_id, parents, self.inventory.get_lines(rev_id),
174
 
                                 None)
 
197
                Weave._add_lines(new_inventory_vf, rev_id, parents,
 
198
                    self.inventory.get_lines(rev_id), None, None, None, False, True)
175
199
            else:
176
200
                new_inventory_vf.add_lines(rev_id, parents, self.inventory.get_lines(rev_id))
177
201
 
267
291
class KnitReconciler(RepoReconciler):
268
292
    """Reconciler that reconciles a knit format repository.
269
293
 
270
 
    This will detect garbage inventories and remove them.
271
 
 
272
 
    Inconsistent parentage is checked for in the revision weave.
 
294
    This will detect garbage inventories and remove them in thorough mode.
273
295
    """
274
296
 
275
297
    def _reconcile_steps(self):
276
298
        """Perform the steps to reconcile this repository."""
277
299
        if self.thorough:
278
 
            self._load_indexes()
 
300
            try:
 
301
                self._load_indexes()
 
302
            except errors.BzrCheckError:
 
303
                self.aborted = True
 
304
                return
279
305
            # knits never suffer this
280
306
            self._gc_inventory()
 
307
            self._fix_text_parents()
281
308
 
282
309
    def _load_indexes(self):
283
310
        """Load indexes for the reconciliation."""
285
312
        self.pb.update('Reading indexes.', 0, 2)
286
313
        self.inventory = self.repo.get_inventory_weave()
287
314
        self.pb.update('Reading indexes.', 1, 2)
 
315
        self.repo._check_for_inconsistent_revision_parents()
288
316
        self.revisions = self.repo._revision_store.get_revision_file(self.transaction)
289
317
        self.pb.update('Reading indexes.', 2, 2)
290
318
 
337
365
        self.garbage_inventories = len(garbage)
338
366
        for revision_id in garbage:
339
367
            mutter('Garbage inventory {%s} found.', revision_id)
 
368
 
 
369
    def _fix_text_parents(self):
 
370
        """Fix bad versionedfile parent entries.
 
371
 
 
372
        It is possible for the parents entry in a versionedfile entry to be
 
373
        inconsistent with the values in the revision and inventory.
 
374
 
 
375
        This method finds entries with such inconsistencies, corrects their
 
376
        parent lists, and replaces the versionedfile with a corrected version.
 
377
        """
 
378
        transaction = self.repo.get_transaction()
 
379
        versions = self.revisions.versions()
 
380
        mutter('Prepopulating revision text cache with %d revisions',
 
381
                len(versions))
 
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):
 
394
                continue
 
395
            full_text_versions = set()
 
396
            self._fix_text_parent(file_id, vf, versions_with_bad_parents,
 
397
                full_text_versions, unused_versions)
 
398
 
 
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,
 
407
            self.transaction)
 
408
        new_parents = {}
 
409
        for version in vf.versions():
 
410
            if version in unused_versions:
 
411
                continue
 
412
            elif version in versions_with_bad_parents:
 
413
                parents = versions_with_bad_parents[version][1]
 
414
            else:
 
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)
 
420
            return
 
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)
 
428
            else:
 
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)
 
432
 
 
433
 
 
434
class PackReconciler(RepoReconciler):
 
435
    """Reconciler that reconciles a pack based repository.
 
436
 
 
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.
 
440
 
 
441
    In future this may be a good place to hook in annotation cache checking,
 
442
    index recreation etc.
 
443
    """
 
444
 
 
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
 
451
 
 
452
    def _reconcile_steps(self):
 
453
        """Perform the steps to reconcile this repository."""
 
454
        if not self.thorough:
 
455
            return
 
456
        collection = self.repo._pack_collection
 
457
        collection.ensure_loaded()
 
458
        collection.lock_names()
 
459
        try:
 
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)
 
470
            else:
 
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()))
 
475
        finally:
 
476
            collection._unlock_names()
 
477
 
 
478
    def _discard_and_save(self, packs):
 
479
        """Discard some packs from the repository.
 
480
 
 
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.
 
485
 
 
486
        :param packs: The packs to discard.
 
487
        """
 
488
        for pack in packs:
 
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)