~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/reconcile.py

  • Committer: Vincent Ladeuil
  • Date: 2008-01-29 15:16:31 UTC
  • mto: (3206.1.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 3207.
  • Revision ID: v.ladeuil+lp@free.fr-20080129151631-vqjd13tb405mobx6
Fix two more leaking tmp dirs, by reworking TransformPreview lock handling.

* bzrlib/tests/test_transform.py:
(TestTransformMerge): Revert previous patch and cleanly call
preview.finalize now that we can.

* bzrlib/tests/test_merge.py:
(TestMerge.test_make_preview_transform): Catch TransformPreview
leak.

* bzrlib/builtins.py:
(cmd_merge._do_preview): Finalize the TransformPreview or the
limbodir will stay in /tmp.

* bzrlib/transform.py:
(TreeTransformBase.__init__): Create the _deletiondir since it's
reffered to by finalize.
(TreeTransformBase.finalize): Delete the dir only if _deletiondir
is set.
(TreeTransform.__init__): Use a temp var for deletiondir and set
the attribute after the base class __init__ has been called.
(TransformPreview.__init__): Read locks the tree since finalize
wants to unlock it (as suggested by Aaron).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# (C) 2005, 2006 Canonical Limited.
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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
 
import bzrlib.branch
24
 
import bzrlib.errors as errors
25
 
import bzrlib.progress
26
 
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
27
36
from bzrlib.tsort import TopoSorter
28
 
import bzrlib.ui as ui
29
37
 
30
38
 
31
39
def reconcile(dir, other=None):
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:
 
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.')
78
93
 
79
94
 
80
95
class RepoReconciler(object):
81
96
    """Reconciler that reconciles a repository.
82
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
 
83
103
    Currently this consists of an inventory reweave with revision cross-checks.
84
104
    """
85
105
 
92
112
        """
93
113
        self.garbage_inventories = 0
94
114
        self.inconsistent_parents = 0
 
115
        self.aborted = False
95
116
        self.repo = repo
96
117
        self.thorough = thorough
97
118
 
173
194
                # This is done to avoid a revision_count * time-to-write additional overhead on 
174
195
                # reconcile.
175
196
                new_inventory_vf._check_write_ok()
176
 
                Weave._add_lines(new_inventory_vf, rev_id, parents, self.inventory.get_lines(rev_id),
177
 
                                 None)
 
197
                Weave._add_lines(new_inventory_vf, rev_id, parents,
 
198
                    self.inventory.get_lines(rev_id), None, None, None, False, True)
178
199
            else:
179
200
                new_inventory_vf.add_lines(rev_id, parents, self.inventory.get_lines(rev_id))
180
201
 
222
243
    def _parents_are_inconsistent(self, rev_id, parents):
223
244
        """Return True if the parents list of rev_id does not match the weave.
224
245
 
225
 
        This detect inconsistences based on the self.thorough value:
 
246
        This detects inconsistencies based on the self.thorough value:
226
247
        if thorough is on, the first parent value is checked as well as ghost
227
248
        differences.
228
249
        Otherwise only the ghost differences are evaluated.
270
291
class KnitReconciler(RepoReconciler):
271
292
    """Reconciler that reconciles a knit format repository.
272
293
 
273
 
    This will detect garbage inventories and remove them.
274
 
 
275
 
    Inconsistent parentage is checked for in the revision weave.
 
294
    This will detect garbage inventories and remove them in thorough mode.
276
295
    """
277
296
 
278
297
    def _reconcile_steps(self):
279
298
        """Perform the steps to reconcile this repository."""
280
299
        if self.thorough:
281
 
            self._load_indexes()
 
300
            try:
 
301
                self._load_indexes()
 
302
            except errors.BzrCheckError:
 
303
                self.aborted = True
 
304
                return
282
305
            # knits never suffer this
283
306
            self._gc_inventory()
 
307
            self._fix_text_parents()
284
308
 
285
309
    def _load_indexes(self):
286
310
        """Load indexes for the reconciliation."""
288
312
        self.pb.update('Reading indexes.', 0, 2)
289
313
        self.inventory = self.repo.get_inventory_weave()
290
314
        self.pb.update('Reading indexes.', 1, 2)
 
315
        self.repo._check_for_inconsistent_revision_parents()
291
316
        self.revisions = self.repo._revision_store.get_revision_file(self.transaction)
292
317
        self.pb.update('Reading indexes.', 2, 2)
293
318
 
340
365
        self.garbage_inventories = len(garbage)
341
366
        for revision_id in garbage:
342
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)