~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/check.py

  • Committer: Robert Collins
  • Date: 2009-05-12 05:34:15 UTC
  • mto: This revision was merged to the branch mainline in revision 4593.
  • Revision ID: robertc@robertcollins.net-20090512053415-20rkucg2p211zdui
Check revisions as we cross check the revision index, rather than in a separate pass.

Show diffs side-by-side

added added

removed removed

Lines of Context:
68
68
        self.repository = repository
69
69
        self.checked_text_cnt = 0
70
70
        self.checked_rev_cnt = 0
71
 
        self.ghosts = []
 
71
        self.ghosts = set()
72
72
        self.repeated_text_cnt = 0
73
73
        self.missing_parent_links = {}
74
74
        self.missing_inventory_sha_cnt = 0
82
82
        self.text_key_references = {}
83
83
        self.check_repo = check_repo
84
84
        self.other_results = []
 
85
        # Plain text lines to include in the report
 
86
        self._report_items = []
 
87
        # Sha1 expectations; may be large and need spilling to disk.
 
88
        # key->(sha1, first-referer)
 
89
        self.expected_sha1 = {}
85
90
        # Ancestors map for all of revisions being checked; while large helper
86
91
        # functions we call would create it anyway, so better to have once and
87
92
        # keep.
107
112
                    self.progress.update('checking revision', revno,
108
113
                                         len(self.planned_revisions))
109
114
                    revno += 1
110
 
                    self.check_one_rev(rev_id)
 
115
                    self._check_revision_tree(rev_id)
111
116
                # check_weaves is done after the revision scan so that
112
117
                # revision index is known to be valid.
113
118
                self.check_weaves()
156
161
            self.progress.finished()
157
162
            self.repository.unlock()
158
163
 
 
164
    def check_revisions(self, revisions_iterator):
 
165
        """Check revision objects by decorating a generator.
 
166
 
 
167
        :param revisions_iterator: An iterator of(revid, Revision-or-None).
 
168
        :return: A generator of the contents of revisions_iterator.
 
169
        """
 
170
        self.planned_revisions = set()
 
171
        for revid, revision in revisions_iterator:
 
172
            yield revid, revision
 
173
            self._check_one_rev(revid, revision)
 
174
 
159
175
    def check_revision_graph(self):
 
176
        revision_iterator = self.repository._iter_revisions(None)
 
177
        revision_iterator = self.check_revisions(revision_iterator)
 
178
        # We read the all revisions here:
 
179
        # - doing this allows later code to depend on the revision index.
 
180
        # - we can fill out existence flags at this point
 
181
        # - we can read the revision inventory sha at this point
 
182
        # - we can check properties and serialisers etc.
160
183
        if not self.repository.revision_graph_can_have_wrong_parents():
161
 
            # This check is not necessary.
 
184
            # The check against the index isn't needed.
162
185
            self.revs_with_bad_parents_in_index = None
163
 
            return
164
 
        bad_revisions = self.repository._find_inconsistent_revision_parents()
165
 
        self.revs_with_bad_parents_in_index = list(bad_revisions)
 
186
            for thing in revision_iterator:
 
187
                pass
 
188
        else:
 
189
            bad_revisions = self.repository._find_inconsistent_revision_parents(
 
190
                revision_iterator)
 
191
            self.revs_with_bad_parents_in_index = list(bad_revisions)
166
192
 
167
193
    def plan_revisions(self):
168
194
        repository = self.repository
188
214
        note('%6d file-ids', len(self.checked_weaves))
189
215
        note('%6d unique file texts', self.checked_text_cnt)
190
216
        note('%6d repeated file texts', self.repeated_text_cnt)
191
 
        note('%6d unreferenced text versions',
192
 
             len(self.unreferenced_versions))
 
217
        if verbose:
 
218
            note('%6d unreferenced text versions',
 
219
                len(self.unreferenced_versions))
 
220
        if verbose and len(self.unreferenced_versions):
 
221
                for file_id, revision_id in self.unreferenced_versions:
 
222
                    log_error('unreferenced version: {%s} in %s', revision_id,
 
223
                        file_id)
193
224
        if self.missing_inventory_sha_cnt:
194
225
            note('%6d revisions are missing inventory_sha1',
195
226
                 self.missing_inventory_sha_cnt)
209
240
                    note('      %s should be in the ancestry for:', link)
210
241
                    for linker in linkers:
211
242
                        note('       * %s', linker)
212
 
            if verbose:
213
 
                for file_id, revision_id in self.unreferenced_versions:
214
 
                    log_error('unreferenced version: {%s} in %s', revision_id,
215
 
                        file_id)
216
243
        if len(self.inconsistent_parents):
217
244
            note('%6d inconsistent parents', len(self.inconsistent_parents))
218
245
            if verbose:
232
259
                        '       %s has wrong parents in index: '
233
260
                        '%r should be %r',
234
261
                        revision_id, index_parents, actual_parents)
235
 
 
236
 
    def check_one_rev(self, rev_id):
237
 
        """Check one revision.
238
 
 
239
 
        rev_id - the one to check
 
262
        for item in self._report_items:
 
263
            note(item)
 
264
 
 
265
    def _check_one_rev(self, rev_id, rev):
 
266
        """Cross-check one revision.
 
267
 
 
268
        :param rev_id: A revision id to check.
 
269
        :param rev: A revision or None to indicate a missing revision.
240
270
        """
241
 
        rev = self.repository.get_revision(rev_id)
242
 
 
243
271
        if rev.revision_id != rev_id:
244
 
            raise BzrCheckError('wrong internal revision id in revision {%s}'
245
 
                                % rev_id)
246
 
 
 
272
            self._report_items.append(
 
273
                'Mismatched internal revid {%s} and index revid {%s}' % (
 
274
                rev.revision_id, rev_id))
 
275
            rev_id = rev.revision_id
 
276
        # Check this revision tree etc, and count as seen when we encounter a
 
277
        # reference to it.
 
278
        self.planned_revisions.add(rev_id)
 
279
        # It is not a ghost
 
280
        self.ghosts.discard(rev_id)
 
281
        # Count all parents as ghosts if we haven't seen them yet.
247
282
        for parent in rev.parent_ids:
248
283
            if not parent in self.planned_revisions:
249
 
                # rev has a parent we didn't know about.
250
 
                missing_links = self.missing_parent_links.get(parent, [])
251
 
                missing_links.append(rev_id)
252
 
                self.missing_parent_links[parent] = missing_links
253
 
                # list based so somewhat slow,
254
 
                # TODO have a planned_revisions list and set.
255
 
                if self.repository.has_revision(parent):
256
 
                    missing_ancestry = self.repository.get_ancestry(parent)
257
 
                    for missing in missing_ancestry:
258
 
                        if (missing is not None
259
 
                            and missing not in self.planned_revisions):
260
 
                            self.planned_revisions.append(missing)
261
 
                else:
262
 
                    self.ghosts.append(rev_id)
263
 
 
 
284
                self.ghosts.add(parent)
 
285
        
264
286
        self.ancestors[rev_id] = tuple(rev.parent_ids) or (NULL_REVISION,)
 
287
        # If the revision has an inventory sha, we want to cross check it later.
265
288
        if rev.inventory_sha1:
266
 
            # Loopback - this is currently circular logic as the
267
 
            # knit get_inventory_sha1 call returns rev.inventory_sha1.
268
 
            # Repository.py's get_inventory_sha1 should instead return
269
 
            # inventories.get_record_stream([(revid,)]).next().sha1 or
270
 
            # similar.
271
 
            inv_sha1 = self.repository.get_inventory_sha1(rev_id)
272
 
            if inv_sha1 != rev.inventory_sha1:
273
 
                raise BzrCheckError('Inventory sha1 hash doesn\'t match'
274
 
                    ' value in revision {%s}' % rev_id)
275
 
        self._check_revision_tree(rev_id)
 
289
            self.add_sha_check(rev_id, ('inventories', rev_id),
 
290
            rev.inventory_sha1)
276
291
        self.checked_rev_cnt += 1
277
292
 
 
293
    def add_sha_check(self, referer, key, sha1):
 
294
        """Add a reference to a sha1 to be cross checked against a key.
 
295
 
 
296
        :param referer: The referer that expects key to have sha1.
 
297
        :param key: A storage key e.g. ('texts', 'foo@bar-20040504-1234')
 
298
        :param sha1: A hex sha1.
 
299
        """
 
300
        existing = self.expected_sha1.get(key)
 
301
        if existing:
 
302
            if sha1 != existing[0]:
 
303
                self._report_items.append('Multiple expected sha1s for %s. {%s}'
 
304
                    ' expects {%s}, {%s} expects {%s}', (
 
305
                    key, referer, sha1, existing[1], existing[0]))
 
306
        else:
 
307
            self.expected_sha1[key] = (sha1, referer)
 
308
 
278
309
    def check_weaves(self):
279
310
        """Check all the weaves we can get our hands on.
280
311
        """