/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/check.py

  • Committer: Jelmer Vernooij
  • Date: 2017-06-08 23:30:31 UTC
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170608233031-3qavls2o7a1pqllj
Update imports.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
# TODO: Check ancestries are correct for every revision: includes
 
18
# every committed so far, and in a reasonable order.
 
19
 
 
20
# TODO: Also check non-mainline revisions mentioned as parents.
 
21
 
 
22
# TODO: Check for extra files in the control directory.
 
23
 
 
24
# TODO: Check revision, inventory and entry objects have all
 
25
# required fields.
 
26
 
 
27
# TODO: Get every revision in the revision-store even if they're not
 
28
# referenced by history and make sure they're all valid.
 
29
 
17
30
# TODO: Perhaps have a way to record errors other than by raising exceptions;
18
31
# would perhaps be enough to accumulate exception objects in a list without
19
32
# raising them.  If there's more than one exception it'd be good to see them
34
47
  indicating that the revision was found/not found.
35
48
"""
36
49
 
37
 
import contextlib
 
50
from __future__ import absolute_import
38
51
 
39
52
from . import (
40
53
    errors,
 
54
    ui,
41
55
    )
 
56
from .branch import Branch
42
57
from .controldir import ControlDir
 
58
from .revision import NULL_REVISION
 
59
from .sixish import (
 
60
    viewitems,
 
61
    )
43
62
from .trace import note
 
63
from .workingtree import WorkingTree
44
64
from .i18n import gettext
45
65
 
46
 
 
47
66
class Check(object):
48
67
    """Check a repository"""
49
68
 
54
73
        raise NotImplementedError(self.report_results)
55
74
 
56
75
 
57
 
def scan_branch(branch, needed_refs, exit_stack):
 
76
class VersionedFileCheck(Check):
 
77
    """Check a versioned file repository"""
 
78
 
 
79
    # The Check object interacts with InventoryEntry.check, etc.
 
80
 
 
81
    def __init__(self, repository, check_repo=True):
 
82
        self.repository = repository
 
83
        self.checked_rev_cnt = 0
 
84
        self.ghosts = set()
 
85
        self.missing_parent_links = {}
 
86
        self.missing_inventory_sha_cnt = 0
 
87
        self.missing_revision_cnt = 0
 
88
        self.checked_weaves = set()
 
89
        self.unreferenced_versions = set()
 
90
        self.inconsistent_parents = []
 
91
        self.rich_roots = repository.supports_rich_root()
 
92
        self.text_key_references = {}
 
93
        self.check_repo = check_repo
 
94
        self.other_results = []
 
95
        # Plain text lines to include in the report
 
96
        self._report_items = []
 
97
        # Keys we are looking for; may be large and need spilling to disk.
 
98
        # key->(type(revision/inventory/text/signature/map), sha1, first-referer)
 
99
        self.pending_keys = {}
 
100
        # Ancestors map for all of revisions being checked; while large helper
 
101
        # functions we call would create it anyway, so better to have once and
 
102
        # keep.
 
103
        self.ancestors = {}
 
104
 
 
105
    def check(self, callback_refs=None, check_repo=True):
 
106
        if callback_refs is None:
 
107
            callback_refs = {}
 
108
        self.repository.lock_read()
 
109
        self.progress = ui.ui_factory.nested_progress_bar()
 
110
        try:
 
111
            self.progress.update(gettext('check'), 0, 4)
 
112
            if self.check_repo:
 
113
                self.progress.update(gettext('checking revisions'), 0)
 
114
                self.check_revisions()
 
115
                self.progress.update(gettext('checking commit contents'), 1)
 
116
                self.repository._check_inventories(self)
 
117
                self.progress.update(gettext('checking file graphs'), 2)
 
118
                # check_weaves is done after the revision scan so that
 
119
                # revision index is known to be valid.
 
120
                self.check_weaves()
 
121
            self.progress.update(gettext('checking branches and trees'), 3)
 
122
            if callback_refs:
 
123
                repo = self.repository
 
124
                # calculate all refs, and callback the objects requesting them.
 
125
                refs = {}
 
126
                wanting_items = set()
 
127
                # Current crude version calculates everything and calls
 
128
                # everything at once. Doing a queue and popping as things are
 
129
                # satisfied would be cheaper on memory [but few people have
 
130
                # huge numbers of working trees today. TODO: fix before
 
131
                # landing].
 
132
                distances = set()
 
133
                existences = set()
 
134
                for ref, wantlist in viewitems(callback_refs):
 
135
                    wanting_items.update(wantlist)
 
136
                    kind, value = ref
 
137
                    if kind == 'trees':
 
138
                        refs[ref] = repo.revision_tree(value)
 
139
                    elif kind == 'lefthand-distance':
 
140
                        distances.add(value)
 
141
                    elif kind == 'revision-existence':
 
142
                        existences.add(value)
 
143
                    else:
 
144
                        raise AssertionError(
 
145
                            'unknown ref kind for ref %s' % ref)
 
146
                node_distances = repo.get_graph().find_lefthand_distances(distances)
 
147
                for key, distance in viewitems(node_distances):
 
148
                    refs[('lefthand-distance', key)] = distance
 
149
                    if key in existences and distance > 0:
 
150
                        refs[('revision-existence', key)] = True
 
151
                        existences.remove(key)
 
152
                parent_map = repo.get_graph().get_parent_map(existences)
 
153
                for key in parent_map:
 
154
                    refs[('revision-existence', key)] = True
 
155
                    existences.remove(key)
 
156
                for key in existences:
 
157
                    refs[('revision-existence', key)] = False
 
158
                for item in wanting_items:
 
159
                    if isinstance(item, WorkingTree):
 
160
                        item._check(refs)
 
161
                    if isinstance(item, Branch):
 
162
                        self.other_results.append(item.check(refs))
 
163
        finally:
 
164
            self.progress.finished()
 
165
            self.repository.unlock()
 
166
 
 
167
    def _check_revisions(self, revisions_iterator):
 
168
        """Check revision objects by decorating a generator.
 
169
 
 
170
        :param revisions_iterator: An iterator of(revid, Revision-or-None).
 
171
        :return: A generator of the contents of revisions_iterator.
 
172
        """
 
173
        self.planned_revisions = set()
 
174
        for revid, revision in revisions_iterator:
 
175
            yield revid, revision
 
176
            self._check_one_rev(revid, revision)
 
177
        # Flatten the revisions we found to guarantee consistent later
 
178
        # iteration.
 
179
        self.planned_revisions = list(self.planned_revisions)
 
180
        # TODO: extract digital signatures as items to callback on too.
 
181
 
 
182
    def check_revisions(self):
 
183
        """Scan revisions, checking data directly available as we go."""
 
184
        revision_iterator = self.repository._iter_revisions(None)
 
185
        revision_iterator = self._check_revisions(revision_iterator)
 
186
        # We read the all revisions here:
 
187
        # - doing this allows later code to depend on the revision index.
 
188
        # - we can fill out existence flags at this point
 
189
        # - we can read the revision inventory sha at this point
 
190
        # - we can check properties and serialisers etc.
 
191
        if not self.repository._format.revision_graph_can_have_wrong_parents:
 
192
            # The check against the index isn't needed.
 
193
            self.revs_with_bad_parents_in_index = None
 
194
            for thing in revision_iterator:
 
195
                pass
 
196
        else:
 
197
            bad_revisions = self.repository._find_inconsistent_revision_parents(
 
198
                revision_iterator)
 
199
            self.revs_with_bad_parents_in_index = list(bad_revisions)
 
200
 
 
201
    def report_results(self, verbose):
 
202
        if self.check_repo:
 
203
            self._report_repo_results(verbose)
 
204
        for result in self.other_results:
 
205
            result.report_results(verbose)
 
206
 
 
207
    def _report_repo_results(self, verbose):
 
208
        note(gettext('checked repository {0} format {1}').format(
 
209
            self.repository.user_url,
 
210
            self.repository._format))
 
211
        note(gettext('%6d revisions'), self.checked_rev_cnt)
 
212
        note(gettext('%6d file-ids'), len(self.checked_weaves))
 
213
        if verbose:
 
214
            note(gettext('%6d unreferenced text versions'),
 
215
                len(self.unreferenced_versions))
 
216
        if verbose and len(self.unreferenced_versions):
 
217
                for file_id, revision_id in self.unreferenced_versions:
 
218
                    note(gettext('unreferenced version: {{{0}}} in {1}').format(revision_id,
 
219
                        file_id))
 
220
        if self.missing_inventory_sha_cnt:
 
221
            note(gettext('%6d revisions are missing inventory_sha1'),
 
222
                 self.missing_inventory_sha_cnt)
 
223
        if self.missing_revision_cnt:
 
224
            note(gettext('%6d revisions are mentioned but not present'),
 
225
                 self.missing_revision_cnt)
 
226
        if len(self.ghosts):
 
227
            note(gettext('%6d ghost revisions'), len(self.ghosts))
 
228
            if verbose:
 
229
                for ghost in self.ghosts:
 
230
                    note('      %s', ghost)
 
231
        if len(self.missing_parent_links):
 
232
            note(gettext('%6d revisions missing parents in ancestry'),
 
233
                 len(self.missing_parent_links))
 
234
            if verbose:
 
235
                for link, linkers in viewitems(self.missing_parent_links):
 
236
                    note(gettext('      %s should be in the ancestry for:'), link)
 
237
                    for linker in linkers:
 
238
                        note('       * %s', linker)
 
239
        if len(self.inconsistent_parents):
 
240
            note(gettext('%6d inconsistent parents'), len(self.inconsistent_parents))
 
241
            if verbose:
 
242
                for info in self.inconsistent_parents:
 
243
                    revision_id, file_id, found_parents, correct_parents = info
 
244
                    note(gettext('      * {0} version {1} has parents {2!r} '
 
245
                         'but should have {3!r}').format(
 
246
                         file_id, revision_id, found_parents,
 
247
                             correct_parents))
 
248
        if self.revs_with_bad_parents_in_index:
 
249
            note(gettext(
 
250
                 '%6d revisions have incorrect parents in the revision index'),
 
251
                 len(self.revs_with_bad_parents_in_index))
 
252
            if verbose:
 
253
                for item in self.revs_with_bad_parents_in_index:
 
254
                    revision_id, index_parents, actual_parents = item
 
255
                    note(gettext(
 
256
                        '       {0} has wrong parents in index: '
 
257
                        '{1!r} should be {2!r}').format(
 
258
                        revision_id, index_parents, actual_parents))
 
259
        for item in self._report_items:
 
260
            note(item)
 
261
 
 
262
    def _check_one_rev(self, rev_id, rev):
 
263
        """Cross-check one revision.
 
264
 
 
265
        :param rev_id: A revision id to check.
 
266
        :param rev: A revision or None to indicate a missing revision.
 
267
        """
 
268
        if rev.revision_id != rev_id:
 
269
            self._report_items.append(gettext(
 
270
                'Mismatched internal revid {{{0}}} and index revid {{{1}}}').format(
 
271
                rev.revision_id, rev_id))
 
272
            rev_id = rev.revision_id
 
273
        # Check this revision tree etc, and count as seen when we encounter a
 
274
        # reference to it.
 
275
        self.planned_revisions.add(rev_id)
 
276
        # It is not a ghost
 
277
        self.ghosts.discard(rev_id)
 
278
        # Count all parents as ghosts if we haven't seen them yet.
 
279
        for parent in rev.parent_ids:
 
280
            if not parent in self.planned_revisions:
 
281
                self.ghosts.add(parent)
 
282
        
 
283
        self.ancestors[rev_id] = tuple(rev.parent_ids) or (NULL_REVISION,)
 
284
        self.add_pending_item(rev_id, ('inventories', rev_id), 'inventory',
 
285
            rev.inventory_sha1)
 
286
        self.checked_rev_cnt += 1
 
287
 
 
288
    def add_pending_item(self, referer, key, kind, sha1):
 
289
        """Add a reference to a sha1 to be cross checked against a key.
 
290
 
 
291
        :param referer: The referer that expects key to have sha1.
 
292
        :param key: A storage key e.g. ('texts', 'foo@bar-20040504-1234')
 
293
        :param kind: revision/inventory/text/map/signature
 
294
        :param sha1: A hex sha1 or None if no sha1 is known.
 
295
        """
 
296
        existing = self.pending_keys.get(key)
 
297
        if existing:
 
298
            if sha1 != existing[1]:
 
299
                self._report_items.append(gettext('Multiple expected sha1s for {0}. {{{1}}}'
 
300
                    ' expects {{{2}}}, {{{3}}} expects {{{4}}}').format(
 
301
                    key, referer, sha1, existing[1], existing[0]))
 
302
        else:
 
303
            self.pending_keys[key] = (kind, sha1, referer)
 
304
 
 
305
    def check_weaves(self):
 
306
        """Check all the weaves we can get our hands on.
 
307
        """
 
308
        weave_ids = []
 
309
        storebar = ui.ui_factory.nested_progress_bar()
 
310
        try:
 
311
            self._check_weaves(storebar)
 
312
        finally:
 
313
            storebar.finished()
 
314
 
 
315
    def _check_weaves(self, storebar):
 
316
        storebar.update('text-index', 0, 2)
 
317
        if self.repository._format.fast_deltas:
 
318
            # We haven't considered every fileid instance so far.
 
319
            weave_checker = self.repository._get_versioned_file_checker(
 
320
                ancestors=self.ancestors)
 
321
        else:
 
322
            weave_checker = self.repository._get_versioned_file_checker(
 
323
                text_key_references=self.text_key_references,
 
324
                ancestors=self.ancestors)
 
325
        storebar.update('file-graph', 1)
 
326
        wrongs, unused_versions = weave_checker.check_file_version_parents(
 
327
            self.repository.texts)
 
328
        self.checked_weaves = weave_checker.file_ids
 
329
        for text_key, (stored_parents, correct_parents) in viewitems(wrongs):
 
330
            # XXX not ready for id join/split operations.
 
331
            weave_id = text_key[0]
 
332
            revision_id = text_key[-1]
 
333
            weave_parents = tuple([parent[-1] for parent in stored_parents])
 
334
            correct_parents = tuple([parent[-1] for parent in correct_parents])
 
335
            self.inconsistent_parents.append(
 
336
                (revision_id, weave_id, weave_parents, correct_parents))
 
337
        self.unreferenced_versions.update(unused_versions)
 
338
 
 
339
    def _add_entry_to_text_key_references(self, inv, entry):
 
340
        if not self.rich_roots and entry.name == '':
 
341
            return
 
342
        key = (entry.file_id, entry.revision)
 
343
        self.text_key_references.setdefault(key, False)
 
344
        if entry.revision == inv.revision_id:
 
345
            self.text_key_references[key] = True
 
346
 
 
347
 
 
348
def scan_branch(branch, needed_refs, to_unlock):
58
349
    """Scan a branch for refs.
59
350
 
60
351
    :param branch:  The branch to schedule for checking.
61
352
    :param needed_refs: Refs we are accumulating.
62
 
    :param exit_stack: The exit stack accumulating.
 
353
    :param to_unlock: The unlock list accumulating.
63
354
    """
64
355
    note(gettext("Checking branch at '%s'.") % (branch.base,))
65
 
    exit_stack.enter_context(branch.lock_read())
 
356
    branch.lock_read()
 
357
    to_unlock.append(branch)
66
358
    branch_refs = branch._get_check_refs()
67
359
    for ref in branch_refs:
68
360
        reflist = needed_refs.setdefault(ref, [])
69
361
        reflist.append(branch)
70
362
 
71
363
 
72
 
def scan_tree(base_tree, tree, needed_refs, exit_stack):
 
364
def scan_tree(base_tree, tree, needed_refs, to_unlock):
73
365
    """Scan a tree for refs.
74
366
 
75
367
    :param base_tree: The original tree check opened, used to detect duplicate
76
368
        tree checks.
77
369
    :param tree:  The tree to schedule for checking.
78
370
    :param needed_refs: Refs we are accumulating.
79
 
    :param exit_stack: The exit stack accumulating.
 
371
    :param to_unlock: The unlock list accumulating.
80
372
    """
81
373
    if base_tree is not None and tree.basedir == base_tree.basedir:
82
374
        return
83
375
    note(gettext("Checking working tree at '%s'.") % (tree.basedir,))
84
 
    exit_stack.enter_context(tree.lock_read())
 
376
    tree.lock_read()
 
377
    to_unlock.append(tree)
85
378
    tree_refs = tree._get_check_refs()
86
379
    for ref in tree_refs:
87
380
        reflist = needed_refs.setdefault(ref, [])
96
389
    """
97
390
    try:
98
391
        base_tree, branch, repo, relpath = \
99
 
            ControlDir.open_containing_tree_branch_or_repository(path)
 
392
                        ControlDir.open_containing_tree_branch_or_repository(path)
100
393
    except errors.NotBranchError:
101
394
        base_tree = branch = repo = None
102
395
 
103
 
    with contextlib.ExitStack() as exit_stack:
104
 
        needed_refs = {}
 
396
    to_unlock = []
 
397
    needed_refs= {}
 
398
    try:
105
399
        if base_tree is not None:
106
400
            # If the tree is a lightweight checkout we won't see it in
107
401
            # repo.find_branches - add now.
108
402
            if do_tree:
109
 
                scan_tree(None, base_tree, needed_refs, exit_stack)
 
403
                scan_tree(None, base_tree, needed_refs, to_unlock)
110
404
            branch = base_tree.branch
111
405
        if branch is not None:
112
406
            # We have a branch
114
408
                # The branch is in a shared repository
115
409
                repo = branch.repository
116
410
        if repo is not None:
117
 
            exit_stack.enter_context(repo.lock_read())
118
 
            branches = list(repo.find_branches(using=True))
 
411
            repo.lock_read()
 
412
            to_unlock.append(repo)
 
413
            branches = repo.find_branches(using=True)
119
414
            saw_tree = False
120
415
            if do_branch or do_tree:
121
416
                for branch in branches:
122
417
                    if do_tree:
123
418
                        try:
124
 
                            tree = branch.controldir.open_workingtree()
 
419
                            tree = branch.bzrdir.open_workingtree()
125
420
                            saw_tree = True
126
421
                        except (errors.NotLocalUrl, errors.NoWorkingTree):
127
422
                            pass
128
423
                        else:
129
 
                            scan_tree(base_tree, tree, needed_refs, exit_stack)
 
424
                            scan_tree(base_tree, tree, needed_refs, to_unlock)
130
425
                    if do_branch:
131
 
                        scan_branch(branch, needed_refs, exit_stack)
 
426
                        scan_branch(branch, needed_refs, to_unlock)
132
427
            if do_branch and not branches:
133
428
                note(gettext("No branch found at specified location."))
134
429
            if do_tree and base_tree is None and not saw_tree:
138
433
                    note(gettext("Checking repository at '%s'.")
139
434
                         % (repo.user_url,))
140
435
                result = repo.check(None, callback_refs=needed_refs,
141
 
                                    check_repo=do_repo)
 
436
                    check_repo=do_repo)
142
437
                result.report_results(verbose)
143
438
        else:
144
439
            if do_tree:
147
442
                note(gettext("No branch found at specified location."))
148
443
            if do_repo:
149
444
                note(gettext("No repository found at specified location."))
 
445
    finally:
 
446
        for thing in to_unlock:
 
447
            thing.unlock()