~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/reconcile.py

Update test support, and remove deprecated functions pullable_revisions and get_intervening_revisions.

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']
 
20
__all__ = ['reconcile', 'Reconciler', 'RepoReconciler', 'KnitReconciler']
21
21
 
22
22
 
23
23
import bzrlib.branch
28
28
import bzrlib.ui as ui
29
29
 
30
30
 
31
 
def reconcile(dir):
 
31
def reconcile(dir, other=None):
32
32
    """Reconcile the data in dir.
33
33
 
34
34
    Currently this is limited to a inventory 'reweave'.
37
37
 
38
38
    Directly using Reconciler is recommended for library users that
39
39
    desire fine grained control or analysis of the found issues.
 
40
 
 
41
    :param other: another bzrdir to reconcile against.
40
42
    """
41
 
    reconciler = Reconciler(dir)
 
43
    reconciler = Reconciler(dir, other=other)
42
44
    reconciler.reconcile()
43
45
 
44
46
 
45
47
class Reconciler(object):
46
48
    """Reconcilers are used to reconcile existing data."""
47
49
 
48
 
    def __init__(self, dir):
 
50
    def __init__(self, dir, other=None):
 
51
        """Create a Reconciler."""
49
52
        self.bzrdir = dir
50
53
 
51
54
    def reconcile(self):
68
71
        self.repo = self.bzrdir.find_repository()
69
72
        self.pb.note('Reconciling repository %s',
70
73
                     self.repo.bzrdir.root_transport.base)
71
 
        repo_reconciler = RepoReconciler(self.repo)
72
 
        repo_reconciler.reconcile()
 
74
        repo_reconciler = self.repo.reconcile(thorough=True)
73
75
        self.inconsistent_parents = repo_reconciler.inconsistent_parents
74
76
        self.garbage_inventories = repo_reconciler.garbage_inventories
75
77
        self.pb.note('Reconciliation complete.')
81
83
    Currently this consists of an inventory reweave with revision cross-checks.
82
84
    """
83
85
 
84
 
    def __init__(self, repo):
 
86
    def __init__(self, repo, other=None, thorough=False):
 
87
        """Construct a RepoReconciler.
 
88
 
 
89
        :param thorough: perform a thorough check which may take longer but
 
90
                         will correct non-data loss issues such as incorrect
 
91
                         cached data.
 
92
        """
 
93
        self.garbage_inventories = 0
 
94
        self.inconsistent_parents = 0
85
95
        self.repo = repo
 
96
        self.thorough = thorough
86
97
 
87
98
    def reconcile(self):
88
99
        """Perform reconciliation.
108
119
        self._reweave_inventory()
109
120
 
110
121
    def _reweave_inventory(self):
111
 
        """Regenerate the inventory weave for the repository from scratch."""
112
 
        # local because its really a wart we want to hide
 
122
        """Regenerate the inventory weave for the repository from scratch.
 
123
        
 
124
        This is a smart function: it will only do the reweave if doing it 
 
125
        will correct data issues. The self.thorough flag controls whether
 
126
        only data-loss causing issues (!self.thorough) or all issues
 
127
        (self.thorough) are treated as requiring the reweave.
 
128
        """
 
129
        # local because needing to know about WeaveFile is a wart we want to hide
113
130
        from bzrlib.weave import WeaveFile, Weave
114
131
        transaction = self.repo.get_transaction()
115
132
        self.pb.update('Reading inventory data.')
127
144
            # put a revision into the graph.
128
145
            self._graph_revision(rev_id)
129
146
        self._check_garbage_inventories()
130
 
        if not self.inconsistent_parents and not self.garbage_inventories:
 
147
        # if there are no inconsistent_parents and 
 
148
        # (no garbage inventories or we are not doing a thorough check)
 
149
        if (not self.inconsistent_parents and 
 
150
            (not self.garbage_inventories or not self.thorough)):
131
151
            self.pb.note('Inventory ok.')
132
152
            return
133
153
        self.pb.update('Backing up inventory...', 0, 0)
188
208
            else:
189
209
                mutter('found ghost %s', parent)
190
210
        self._rev_graph[rev_id] = parents   
191
 
        if set(self.inventory.get_parents(rev_id)) != set(parents):
 
211
        if self._parents_are_inconsistent(rev_id, parents):
192
212
            self.inconsistent_parents += 1
193
213
            mutter('Inconsistent inventory parents: id {%s} '
194
214
                   'inventory claims %r, '
199
219
                   set(parents),
200
220
                   set(rev.parent_ids).difference(set(parents)))
201
221
 
 
222
    def _parents_are_inconsistent(self, rev_id, parents):
 
223
        """Return True if the parents list of rev_id does not match the weave.
 
224
 
 
225
        This detect inconsistences based on the self.thorough value:
 
226
        if thorough is on, the first parent value is checked as well as ghost
 
227
        differences.
 
228
        Otherwise only the ghost differences are evaluated.
 
229
        """
 
230
        weave_parents = self.inventory.get_parents(rev_id)
 
231
        weave_missing_old_ghosts = set(weave_parents) != set(parents)
 
232
        first_parent_is_wrong = (
 
233
            len(weave_parents) and len(parents) and
 
234
            parents[0] != weave_parents[0])
 
235
        if self.thorough:
 
236
            return weave_missing_old_ghosts or first_parent_is_wrong
 
237
        else:
 
238
            return weave_missing_old_ghosts
 
239
 
202
240
    def _check_garbage_inventories(self):
203
241
        """Check for garbage inventories which we cannot trust
204
242
 
205
243
        We cant trust them because their pre-requisite file data may not
206
244
        be present - all we know is that their revision was not installed.
207
245
        """
 
246
        if not self.thorough:
 
247
            return
208
248
        inventories = set(self.inventory.versions())
209
249
        revisions = set(self._rev_graph.keys())
210
250
        garbage = inventories.difference(revisions)
237
277
 
238
278
    def _reconcile_steps(self):
239
279
        """Perform the steps to reconcile this repository."""
240
 
        self._load_indexes()
241
 
        # knits never suffer this
242
 
        self.inconsistent_parents = 0
243
 
        self._gc_inventory()
 
280
        if self.thorough:
 
281
            self._load_indexes()
 
282
            # knits never suffer this
 
283
            self._gc_inventory()
244
284
 
245
285
    def _load_indexes(self):
246
286
        """Load indexes for the reconciliation."""
288
328
        self.inventory = None
289
329
        self.pb.note('Inventory regenerated.')
290
330
 
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
 
 
341
331
    def _check_garbage_inventories(self):
342
332
        """Check for garbage inventories which we cannot trust
343
333