~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/check.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-29 02:41:07 UTC
  • Revision ID: mbp@sourcefrog.net-20050329024107-7fd789f7ca7d64ab
Tree.is_ignored returns the pattern that matched, if any

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2004, 2005 by Martin Pool
 
2
# Copyright (C) 2005 by Canonical Ltd
 
3
 
3
4
# This program is free software; you can redistribute it and/or modify
4
5
# it under the terms of the GNU General Public License as published by
5
6
# the Free Software Foundation; either version 2 of the License, or
6
7
# (at your option) any later version.
7
 
#
 
8
 
8
9
# This program is distributed in the hope that it will be useful,
9
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
12
# GNU General Public License for more details.
12
 
#
 
13
 
13
14
# You should have received a copy of the GNU General Public License
14
15
# along with this program; if not, write to the Free Software
15
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
 
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
 
 
30
 
# TODO: Perhaps have a way to record errors other than by raising exceptions;
31
 
# would perhaps be enough to accumulate exception objects in a list without
32
 
# raising them.  If there's more than one exception it'd be good to see them
33
 
# all.
34
 
 
35
 
from bzrlib import errors, osutils
36
 
from bzrlib import repository as _mod_repository
37
 
from bzrlib import revision
38
 
from bzrlib.branch import Branch
39
 
from bzrlib.bzrdir import BzrDir
40
 
from bzrlib.errors import BzrCheckError
41
 
from bzrlib.repository import Repository
42
 
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
43
 
from bzrlib.trace import log_error, note
44
 
import bzrlib.ui
45
 
from bzrlib.workingtree import WorkingTree
46
 
 
47
 
class Check(object):
48
 
    """Check a repository"""
49
 
 
50
 
    # The Check object interacts with InventoryEntry.check, etc.
51
 
 
52
 
    def __init__(self, repository):
53
 
        self.repository = repository
54
 
        self.checked_text_cnt = 0
55
 
        self.checked_rev_cnt = 0
56
 
        self.ghosts = []
57
 
        self.repeated_text_cnt = 0
58
 
        self.missing_parent_links = {}
59
 
        self.missing_inventory_sha_cnt = 0
60
 
        self.missing_revision_cnt = 0
61
 
        # maps (file-id, version) -> sha1; used by InventoryFile._check
62
 
        self.checked_texts = {}
63
 
        self.checked_weaves = set()
64
 
        self.unreferenced_versions = set()
65
 
        self.inconsistent_parents = []
66
 
 
67
 
    def check(self):
68
 
        self.repository.lock_read()
69
 
        self.progress = bzrlib.ui.ui_factory.nested_progress_bar()
70
 
        try:
71
 
            self.progress.update('retrieving inventory', 0, 2)
72
 
            # do not put in init, as it should be done with progess,
73
 
            # and inside the lock.
74
 
            self.inventory_weave = self.repository.inventories
75
 
            self.progress.update('checking revision graph', 1)
76
 
            self.check_revision_graph()
77
 
            self.plan_revisions()
78
 
            revno = 0
79
 
            while revno < len(self.planned_revisions):
80
 
                rev_id = self.planned_revisions[revno]
81
 
                self.progress.update('checking revision', revno,
82
 
                                     len(self.planned_revisions))
83
 
                revno += 1
84
 
                self.check_one_rev(rev_id)
85
 
            # check_weaves is done after the revision scan so that
86
 
            # revision index is known to be valid.
87
 
            self.check_weaves()
88
 
        finally:
89
 
            self.progress.finished()
90
 
            self.repository.unlock()
91
 
 
92
 
    def check_revision_graph(self):
93
 
        if not self.repository.revision_graph_can_have_wrong_parents():
94
 
            # This check is not necessary.
95
 
            self.revs_with_bad_parents_in_index = None
96
 
            return
97
 
        bad_revisions = self.repository._find_inconsistent_revision_parents()
98
 
        self.revs_with_bad_parents_in_index = list(bad_revisions)
99
 
 
100
 
    def plan_revisions(self):
101
 
        repository = self.repository
102
 
        self.planned_revisions = repository.all_revision_ids()
103
 
        self.progress.clear()
104
 
        inventoried = set(key[-1] for key in self.inventory_weave.keys())
105
 
        awol = set(self.planned_revisions) - inventoried
106
 
        if len(awol) > 0:
107
 
            raise BzrCheckError('Stored revisions missing from inventory'
108
 
                '{%s}' % ','.join([f for f in awol]))
109
 
 
110
 
    def report_results(self, verbose):
111
 
        note('checked repository %s format %s',
112
 
             self.repository.bzrdir.root_transport,
113
 
             self.repository._format)
114
 
        note('%6d revisions', self.checked_rev_cnt)
115
 
        note('%6d file-ids', len(self.checked_weaves))
116
 
        note('%6d unique file texts', self.checked_text_cnt)
117
 
        note('%6d repeated file texts', self.repeated_text_cnt)
118
 
        note('%6d unreferenced text versions',
119
 
             len(self.unreferenced_versions))
120
 
        if self.missing_inventory_sha_cnt:
121
 
            note('%6d revisions are missing inventory_sha1',
122
 
                 self.missing_inventory_sha_cnt)
123
 
        if self.missing_revision_cnt:
124
 
            note('%6d revisions are mentioned but not present',
125
 
                 self.missing_revision_cnt)
126
 
        if len(self.ghosts):
127
 
            note('%6d ghost revisions', len(self.ghosts))
128
 
            if verbose:
129
 
                for ghost in self.ghosts:
130
 
                    note('      %s', ghost)
131
 
        if len(self.missing_parent_links):
132
 
            note('%6d revisions missing parents in ancestry',
133
 
                 len(self.missing_parent_links))
134
 
            if verbose:
135
 
                for link, linkers in self.missing_parent_links.items():
136
 
                    note('      %s should be in the ancestry for:', link)
137
 
                    for linker in linkers:
138
 
                        note('       * %s', linker)
139
 
            if verbose:
140
 
                for file_id, revision_id in self.unreferenced_versions:
141
 
                    log_error('unreferenced version: {%s} in %s', revision_id,
142
 
                        file_id)
143
 
        if len(self.inconsistent_parents):
144
 
            note('%6d inconsistent parents', len(self.inconsistent_parents))
145
 
            if verbose:
146
 
                for info in self.inconsistent_parents:
147
 
                    revision_id, file_id, found_parents, correct_parents = info
148
 
                    note('      * %s version %s has parents %r '
149
 
                         'but should have %r'
150
 
                         % (file_id, revision_id, found_parents,
151
 
                             correct_parents))
152
 
        if self.revs_with_bad_parents_in_index:
153
 
            note('%6d revisions have incorrect parents in the revision index',
154
 
                 len(self.revs_with_bad_parents_in_index))
155
 
            if verbose:
156
 
                for item in self.revs_with_bad_parents_in_index:
157
 
                    revision_id, index_parents, actual_parents = item
158
 
                    note(
159
 
                        '       %s has wrong parents in index: '
160
 
                        '%r should be %r',
161
 
                        revision_id, index_parents, actual_parents)
162
 
 
163
 
    def check_one_rev(self, rev_id):
164
 
        """Check one revision.
165
 
 
166
 
        rev_id - the one to check
167
 
        """
168
 
        rev = self.repository.get_revision(rev_id)
169
 
                
170
 
        if rev.revision_id != rev_id:
171
 
            raise BzrCheckError('wrong internal revision id in revision {%s}'
172
 
                                % rev_id)
173
 
 
174
 
        for parent in rev.parent_ids:
175
 
            if not parent in self.planned_revisions:
176
 
                missing_links = self.missing_parent_links.get(parent, [])
177
 
                missing_links.append(rev_id)
178
 
                self.missing_parent_links[parent] = missing_links
179
 
                # list based so somewhat slow,
180
 
                # TODO have a planned_revisions list and set.
181
 
                if self.repository.has_revision(parent):
182
 
                    missing_ancestry = self.repository.get_ancestry(parent)
183
 
                    for missing in missing_ancestry:
184
 
                        if (missing is not None 
185
 
                            and missing not in self.planned_revisions):
186
 
                            self.planned_revisions.append(missing)
187
 
                else:
188
 
                    self.ghosts.append(rev_id)
189
 
 
190
 
        if rev.inventory_sha1:
191
 
            inv_sha1 = self.repository.get_inventory_sha1(rev_id)
192
 
            if inv_sha1 != rev.inventory_sha1:
193
 
                raise BzrCheckError('Inventory sha1 hash doesn\'t match'
194
 
                    ' value in revision {%s}' % rev_id)
195
 
        self._check_revision_tree(rev_id)
196
 
        self.checked_rev_cnt += 1
197
 
 
198
 
    def check_weaves(self):
199
 
        """Check all the weaves we can get our hands on.
200
 
        """
201
 
        weave_ids = []
202
 
        self.progress.update('checking inventory', 0, 2)
203
 
        self.inventory_weave.check(progress_bar=self.progress)
204
 
        self.progress.update('checking text storage', 1, 2)
205
 
        self.repository.texts.check(progress_bar=self.progress)
206
 
        weave_checker = self.repository._get_versioned_file_checker()
207
 
        result = weave_checker.check_file_version_parents(
208
 
            self.repository.texts, progress_bar=self.progress)
209
 
        self.checked_weaves = weave_checker.file_ids
210
 
        bad_parents, unused_versions = result
211
 
        bad_parents = bad_parents.items()
212
 
        for text_key, (stored_parents, correct_parents) in bad_parents:
213
 
            # XXX not ready for id join/split operations.
214
 
            weave_id = text_key[0]
215
 
            revision_id = text_key[-1]
216
 
            weave_parents = tuple([parent[-1] for parent in stored_parents])
217
 
            correct_parents = tuple([parent[-1] for parent in correct_parents])
218
 
            self.inconsistent_parents.append(
219
 
                (revision_id, weave_id, weave_parents, correct_parents))
220
 
        self.unreferenced_versions.update(unused_versions)
221
 
 
222
 
    def _check_revision_tree(self, rev_id):
223
 
        tree = self.repository.revision_tree(rev_id)
224
 
        inv = tree.inventory
225
 
        seen_ids = {}
 
18
 
 
19
 
 
20
######################################################################
 
21
# consistency checks
 
22
 
 
23
import sys
 
24
from sets import Set
 
25
 
 
26
import bzrlib
 
27
from trace import mutter
 
28
from errors import bailout
 
29
import osutils
 
30
 
 
31
def check(branch, progress=True):
 
32
    out = sys.stdout
 
33
 
 
34
    if progress:
 
35
        def p(m):
 
36
            mutter('checking ' + m)
 
37
            out.write('\rchecking: %-50.50s' % m)
 
38
            out.flush()
 
39
    else:
 
40
        def p(m):
 
41
            mutter('checking ' + m)
 
42
 
 
43
    p('history of %r' % branch.base)
 
44
    last_ptr = None
 
45
    checked_revs = Set()
 
46
    
 
47
    history = branch.revision_history()
 
48
    revno = 0
 
49
    revcount = len(history)
 
50
 
 
51
    checked_texts = {}
 
52
    
 
53
    for rid in history:
 
54
        revno += 1
 
55
        p('revision %d/%d' % (revno, revcount))
 
56
        mutter('    revision {%s}' % rid)
 
57
        rev = branch.get_revision(rid)
 
58
        if rev.revision_id != rid:
 
59
            bailout('wrong internal revision id in revision {%s}' % rid)
 
60
        if rev.precursor != last_ptr:
 
61
            bailout('mismatched precursor in revision {%s}' % rid)
 
62
        last_ptr = rid
 
63
        if rid in checked_revs:
 
64
            bailout('repeated revision {%s}' % rid)
 
65
        checked_revs.add(rid)
 
66
 
 
67
        ## TODO: Check all the required fields are present on the revision.
 
68
 
 
69
        inv = branch.get_inventory(rev.inventory_id)
 
70
        seen_ids = Set()
 
71
        seen_names = Set()
 
72
 
 
73
        p('revision %d/%d file ids' % (revno, revcount))
226
74
        for file_id in inv:
227
75
            if file_id in seen_ids:
228
 
                raise BzrCheckError('duplicated file_id {%s} '
229
 
                                    'in inventory for revision {%s}'
230
 
                                    % (file_id, rev_id))
231
 
            seen_ids[file_id] = True
 
76
                bailout('duplicated file_id {%s} in inventory for revision {%s}'
 
77
                        % (file_id, revid))
 
78
            seen_ids.add(file_id)
 
79
 
 
80
        i = 0
 
81
        len_inv = len(inv)
232
82
        for file_id in inv:
 
83
            i += 1
 
84
            if (i % 100) == 0:
 
85
                p('revision %d/%d file text %d/%d' % (revno, revcount, i, len_inv))
 
86
 
233
87
            ie = inv[file_id]
234
 
            ie.check(self, rev_id, inv, tree)
235
 
        seen_names = {}
 
88
 
 
89
            if ie.parent_id != None:
 
90
                if ie.parent_id not in seen_ids:
 
91
                    bailout('missing parent {%s} in inventory for revision {%s}'
 
92
                            % (ie.parent_id, revid))
 
93
 
 
94
            if ie.kind == 'file':
 
95
                if ie.text_id in checked_texts:
 
96
                    fp = checked_texts[ie.text_id]
 
97
                else:
 
98
                    if not ie.text_id in branch.text_store:
 
99
                        bailout('text {%s} not in text_store' % ie.text_id)
 
100
 
 
101
                    tf = branch.text_store[ie.text_id]
 
102
                    fp = osutils.fingerprint_file(tf)
 
103
                    checked_texts[ie.text_id] = fp
 
104
 
 
105
                if ie.text_size != fp['size']:
 
106
                    bailout('text {%s} wrong size' % ie.text_id)
 
107
                if ie.text_sha1 != fp['sha1']:
 
108
                    bailout('text {%s} wrong sha1' % ie.text_id)
 
109
            elif ie.kind == 'directory':
 
110
                if ie.text_sha1 != None or ie.text_size != None or ie.text_id != None:
 
111
                    bailout('directory {%s} has text in revision {%s}'
 
112
                            % (file_id, revid))
 
113
 
 
114
        p('revision %d/%d file paths' % (revno, revcount))
236
115
        for path, ie in inv.iter_entries():
237
116
            if path in seen_names:
238
 
                raise BzrCheckError('duplicated path %s '
239
 
                                    'in inventory for revision {%s}'
240
 
                                    % (path, rev_id))
241
 
            seen_names[path] = True
242
 
 
243
 
 
244
 
@deprecated_function(deprecated_in((1,6,0)))
245
 
def check(branch, verbose):
246
 
    """Run consistency checks on a branch.
247
 
    
248
 
    Results are reported through logging.
249
 
    
250
 
    Deprecated in 1.6.  Please use check_branch instead.
251
 
 
252
 
    :raise BzrCheckError: if there's a consistency error.
253
 
    """
254
 
    check_branch(branch, verbose)
255
 
 
256
 
 
257
 
def check_branch(branch, verbose):
258
 
    """Run consistency checks on a branch.
259
 
 
260
 
    Results are reported through logging.
261
 
 
262
 
    :raise BzrCheckError: if there's a consistency error.
263
 
    """
264
 
    branch.lock_read()
265
 
    try:
266
 
        branch_result = branch.check()
267
 
    finally:
268
 
        branch.unlock()
269
 
    branch_result.report_results(verbose)
270
 
 
271
 
 
272
 
def check_dwim(path, verbose, do_branch=False, do_repo=False, do_tree=False):
273
 
    try:
274
 
        tree, branch, repo, relpath = \
275
 
                        BzrDir.open_containing_tree_branch_or_repository(path)
276
 
    except errors.NotBranchError:
277
 
        tree = branch = repo = None
278
 
 
279
 
    if do_tree:
280
 
        if tree is not None:
281
 
            note("Checking working tree at '%s'." 
282
 
                 % (tree.bzrdir.root_transport.base,))
283
 
            tree._check()
284
 
        else:
285
 
            log_error("No working tree found at specified location.")
286
 
 
287
 
    if branch is not None:
288
 
        # We have a branch
289
 
        if repo is None:
290
 
            # The branch is in a shared repository
291
 
            repo = branch.repository
292
 
        branches = [branch]
293
 
    elif repo is not None:
294
 
        branches = repo.find_branches(using=True)
295
 
 
296
 
    if repo is not None:
297
 
        repo.lock_read()
298
 
        try:
299
 
            if do_repo:
300
 
                note("Checking repository at '%s'."
301
 
                     % (repo.bzrdir.root_transport.base,))
302
 
                result = repo.check()
303
 
                result.report_results(verbose)
304
 
            if do_branch:
305
 
                if branches == []:
306
 
                    log_error("No branch found at specified location.")
307
 
                else:
308
 
                    for branch in branches:
309
 
                        note("Checking branch at '%s'."
310
 
                             % (branch.bzrdir.root_transport.base,))
311
 
                        check_branch(branch, verbose)
312
 
        finally:
313
 
            repo.unlock()
314
 
    else:
315
 
        if do_branch:
316
 
            log_error("No branch found at specified location.")
317
 
        if do_repo:
318
 
            log_error("No repository found at specified location.")
 
117
                bailout('duplicated path %r in inventory for revision {%s}' % (path, revid))
 
118
            seen_names.add(path)
 
119
 
 
120
 
 
121
    p('done')
 
122
    if progress:
 
123
        print 
 
124
 
 
125