~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: 2006-04-27 01:14:33 UTC
  • mfrom: (1686.1.1 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060427011433-95634ee1da8a2049
Merge in faster joins from weave to knit.

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
 
20
__all__ = ['reconcile', 'Reconciler', 'RepoReconciler']
 
21
 
 
22
 
 
23
import bzrlib.branch
 
24
import bzrlib.errors as errors
 
25
import bzrlib.progress
24
26
from bzrlib.trace import mutter
25
27
from bzrlib.tsort import TopoSorter
26
 
 
27
 
 
28
 
def reconcile(dir, other=None):
 
28
import bzrlib.ui as ui
 
29
 
 
30
 
 
31
def reconcile(dir):
29
32
    """Reconcile the data in dir.
30
33
 
31
34
    Currently this is limited to a inventory 'reweave'.
34
37
 
35
38
    Directly using Reconciler is recommended for library users that
36
39
    desire fine grained control or analysis of the found issues.
37
 
 
38
 
    :param other: another bzrdir to reconcile against.
39
40
    """
40
 
    reconciler = Reconciler(dir, other=other)
 
41
    reconciler = Reconciler(dir)
41
42
    reconciler.reconcile()
42
43
 
43
44
 
44
45
class Reconciler(object):
45
46
    """Reconcilers are used to reconcile existing data."""
46
47
 
47
 
    def __init__(self, dir, other=None):
48
 
        """Create a Reconciler."""
 
48
    def __init__(self, dir):
49
49
        self.bzrdir = dir
50
50
 
51
51
    def reconcile(self):
68
68
        self.repo = self.bzrdir.find_repository()
69
69
        self.pb.note('Reconciling repository %s',
70
70
                     self.repo.bzrdir.root_transport.base)
71
 
        repo_reconciler = self.repo.reconcile(thorough=True)
 
71
        repo_reconciler = RepoReconciler(self.repo)
 
72
        repo_reconciler.reconcile()
72
73
        self.inconsistent_parents = repo_reconciler.inconsistent_parents
73
74
        self.garbage_inventories = repo_reconciler.garbage_inventories
74
75
        self.pb.note('Reconciliation complete.')
80
81
    Currently this consists of an inventory reweave with revision cross-checks.
81
82
    """
82
83
 
83
 
    def __init__(self, repo, other=None, thorough=False):
84
 
        """Construct a RepoReconciler.
85
 
 
86
 
        :param thorough: perform a thorough check which may take longer but
87
 
                         will correct non-data loss issues such as incorrect
88
 
                         cached data.
89
 
        """
90
 
        self.garbage_inventories = 0
91
 
        self.inconsistent_parents = 0
 
84
    def __init__(self, repo):
92
85
        self.repo = repo
93
 
        self.thorough = thorough
94
86
 
95
87
    def reconcile(self):
96
88
        """Perform reconciliation.
116
108
        self._reweave_inventory()
117
109
 
118
110
    def _reweave_inventory(self):
119
 
        """Regenerate the inventory weave for the repository from scratch.
120
 
        
121
 
        This is a smart function: it will only do the reweave if doing it 
122
 
        will correct data issues. The self.thorough flag controls whether
123
 
        only data-loss causing issues (!self.thorough) or all issues
124
 
        (self.thorough) are treated as requiring the reweave.
125
 
        """
126
 
        # local because needing to know about WeaveFile is a wart we want to hide
 
111
        """Regenerate the inventory weave for the repository from scratch."""
 
112
        # local because its really a wart we want to hide
127
113
        from bzrlib.weave import WeaveFile, Weave
128
114
        transaction = self.repo.get_transaction()
129
115
        self.pb.update('Reading inventory data.')
141
127
            # put a revision into the graph.
142
128
            self._graph_revision(rev_id)
143
129
        self._check_garbage_inventories()
144
 
        # if there are no inconsistent_parents and 
145
 
        # (no garbage inventories or we are not doing a thorough check)
146
 
        if (not self.inconsistent_parents and 
147
 
            (not self.garbage_inventories or not self.thorough)):
 
130
        if not self.inconsistent_parents and not self.garbage_inventories:
148
131
            self.pb.note('Inventory ok.')
149
132
            return
150
133
        self.pb.update('Backing up inventory...', 0, 0)
205
188
            else:
206
189
                mutter('found ghost %s', parent)
207
190
        self._rev_graph[rev_id] = parents   
208
 
        if self._parents_are_inconsistent(rev_id, parents):
 
191
        if set(self.inventory.get_parents(rev_id)) != set(parents):
209
192
            self.inconsistent_parents += 1
210
193
            mutter('Inconsistent inventory parents: id {%s} '
211
194
                   'inventory claims %r, '
216
199
                   set(parents),
217
200
                   set(rev.parent_ids).difference(set(parents)))
218
201
 
219
 
    def _parents_are_inconsistent(self, rev_id, parents):
220
 
        """Return True if the parents list of rev_id does not match the weave.
221
 
 
222
 
        This detects inconsistencies based on the self.thorough value:
223
 
        if thorough is on, the first parent value is checked as well as ghost
224
 
        differences.
225
 
        Otherwise only the ghost differences are evaluated.
226
 
        """
227
 
        weave_parents = self.inventory.get_parents(rev_id)
228
 
        weave_missing_old_ghosts = set(weave_parents) != set(parents)
229
 
        first_parent_is_wrong = (
230
 
            len(weave_parents) and len(parents) and
231
 
            parents[0] != weave_parents[0])
232
 
        if self.thorough:
233
 
            return weave_missing_old_ghosts or first_parent_is_wrong
234
 
        else:
235
 
            return weave_missing_old_ghosts
236
 
 
237
202
    def _check_garbage_inventories(self):
238
203
        """Check for garbage inventories which we cannot trust
239
204
 
240
205
        We cant trust them because their pre-requisite file data may not
241
206
        be present - all we know is that their revision was not installed.
242
207
        """
243
 
        if not self.thorough:
244
 
            return
245
208
        inventories = set(self.inventory.versions())
246
209
        revisions = set(self._rev_graph.keys())
247
210
        garbage = inventories.difference(revisions)
274
237
 
275
238
    def _reconcile_steps(self):
276
239
        """Perform the steps to reconcile this repository."""
277
 
        if self.thorough:
278
 
            self._load_indexes()
279
 
            # knits never suffer this
280
 
            self._gc_inventory()
 
240
        self._load_indexes()
 
241
        # knits never suffer this
 
242
        self.inconsistent_parents = 0
 
243
        self._gc_inventory()
281
244
 
282
245
    def _load_indexes(self):
283
246
        """Load indexes for the reconciliation."""
325
288
        self.inventory = None
326
289
        self.pb.note('Inventory regenerated.')
327
290
 
 
291
    def _reinsert_revisions(self):
 
292
        """Correct the revision history for revisions in the revision knit."""
 
293
        # the total set of revisions to process
 
294
        self.pending = set(self.revisions.versions())
 
295
 
 
296
        # mapping from revision_id to parents
 
297
        self._rev_graph = {}
 
298
        # errors that we detect
 
299
        self.inconsistent_parents = 0
 
300
        # we need the revision id of each revision and its available parents list
 
301
        self._setup_steps(len(self.pending))
 
302
        for rev_id in self.pending:
 
303
            # put a revision into the graph.
 
304
            self._graph_revision(rev_id)
 
305
 
 
306
        if not self.inconsistent_parents:
 
307
            self.pb.note('Revision history accurate.')
 
308
            return
 
309
        self._setup_steps(len(self._rev_graph))
 
310
        for rev_id, parents in self._rev_graph.items():
 
311
            if parents != self.revisions.get_parents(rev_id):
 
312
                self.revisions.fix_parents(rev_id, parents)
 
313
            self._reweave_step('Fixing parents')
 
314
        self.pb.note('Ancestry corrected.')
 
315
 
 
316
    def _graph_revision(self, rev_id):
 
317
        """Load a revision into the revision graph."""
 
318
        # pick a random revision
 
319
        # analyse revision id rev_id and put it in the stack.
 
320
        self._reweave_step('loading revisions')
 
321
        rev = self.repo._revision_store.get_revision(rev_id, self.transaction)
 
322
        assert rev.revision_id == rev_id
 
323
        parents = []
 
324
        for parent in rev.parent_ids:
 
325
            if self.revisions.has_version(parent):
 
326
                parents.append(parent)
 
327
            else:
 
328
                mutter('found ghost %s', parent)
 
329
        self._rev_graph[rev_id] = parents   
 
330
        if set(self.inventory.get_parents(rev_id)) != set(parents):
 
331
            self.inconsistent_parents += 1
 
332
            mutter('Inconsistent inventory parents: id {%s} '
 
333
                   'inventory claims %r, '
 
334
                   'available parents are %r, '
 
335
                   'unavailable parents are %r',
 
336
                   rev_id, 
 
337
                   set(self.inventory.get_parents(rev_id)),
 
338
                   set(parents),
 
339
                   set(rev.parent_ids).difference(set(parents)))
 
340
 
328
341
    def _check_garbage_inventories(self):
329
342
        """Check for garbage inventories which we cannot trust
330
343