~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-09 04:51:05 UTC
  • Revision ID: mbp@sourcefrog.net-20050309045105-d02cd410a115da2c
import all docs from arch

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
2
 
#
 
1
#! /usr/bin/python
 
2
 
 
3
 
 
4
# Copyright (C) 2004, 2005 by Martin Pool
 
5
# Copyright (C) 2005 by Canonical Ltd
 
6
 
 
7
 
3
8
# This program is free software; you can redistribute it and/or modify
4
9
# it under the terms of the GNU General Public License as published by
5
10
# the Free Software Foundation; either version 2 of the License, or
6
11
# (at your option) any later version.
7
 
#
 
12
 
8
13
# This program is distributed in the hope that it will be useful,
9
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
16
# GNU General Public License for more details.
12
 
#
 
17
 
13
18
# You should have received a copy of the GNU General Public License
14
19
# along with this program; if not, write to the Free Software
15
20
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
21
 
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
36
 
from bzrlib import repository as _mod_repository
37
 
from bzrlib import revision
38
 
from bzrlib.errors import BzrCheckError
39
 
import bzrlib.ui
40
 
from bzrlib.trace import log_error, note
41
 
 
42
 
class Check(object):
43
 
    """Check a repository"""
44
 
 
45
 
    # The Check object interacts with InventoryEntry.check, etc.
46
 
 
47
 
    def __init__(self, repository):
48
 
        self.repository = repository
49
 
        self.checked_text_cnt = 0
50
 
        self.checked_rev_cnt = 0
51
 
        self.ghosts = []
52
 
        self.repeated_text_cnt = 0
53
 
        self.missing_parent_links = {}
54
 
        self.missing_inventory_sha_cnt = 0
55
 
        self.missing_revision_cnt = 0
56
 
        # maps (file-id, version) -> sha1; used by InventoryFile._check
57
 
        self.checked_texts = {}
58
 
        self.checked_weaves = {}
59
 
        self.revision_versions = _mod_repository._RevisionTextVersionCache(
60
 
            self.repository)
61
 
        self.unreferenced_ancestors = set()
62
 
        self.inconsistent_parents = []
63
 
 
64
 
    def check(self):
65
 
        self.repository.lock_read()
66
 
        self.progress = bzrlib.ui.ui_factory.nested_progress_bar()
67
 
        try:
68
 
            self.progress.update('retrieving inventory', 0, 2)
69
 
            # do not put in init, as it should be done with progess,
70
 
            # and inside the lock.
71
 
            self.inventory_weave = self.repository.get_inventory_weave()
72
 
            self.progress.update('checking revision graph', 1)
73
 
            self.check_revision_graph()
74
 
            self.plan_revisions()
75
 
            revno = 0
76
 
            while revno < len(self.planned_revisions):
77
 
                rev_id = self.planned_revisions[revno]
78
 
                self.progress.update('checking revision', revno,
79
 
                                     len(self.planned_revisions))
80
 
                revno += 1
81
 
                self.check_one_rev(rev_id)
82
 
            # check_weaves is done after the revision scan so that
83
 
            # revision_versions is pre-populated
84
 
            self.check_weaves()
85
 
        finally:
86
 
            self.progress.finished()
87
 
            self.repository.unlock()
88
 
 
89
 
    def check_revision_graph(self):
90
 
        if not self.repository.revision_graph_can_have_wrong_parents():
91
 
            # This check is not necessary.
92
 
            self.revs_with_bad_parents_in_index = None
93
 
            return
94
 
        bad_revisions = self.repository._find_inconsistent_revision_parents()
95
 
        self.revs_with_bad_parents_in_index = list(bad_revisions)
96
 
 
97
 
    def plan_revisions(self):
98
 
        repository = self.repository
99
 
        self.planned_revisions = repository.all_revision_ids()
100
 
        self.progress.clear()
101
 
        inventoried = set(self.inventory_weave.versions())
102
 
        awol = set(self.planned_revisions) - inventoried
103
 
        if len(awol) > 0:
104
 
            raise BzrCheckError('Stored revisions missing from inventory'
105
 
                '{%s}' % ','.join([f for f in awol]))
106
 
 
107
 
    def report_results(self, verbose):
108
 
        note('checked repository %s format %s',
109
 
             self.repository.bzrdir.root_transport,
110
 
             self.repository._format)
111
 
        note('%6d revisions', self.checked_rev_cnt)
112
 
        note('%6d file-ids', len(self.checked_weaves))
113
 
        note('%6d unique file texts', self.checked_text_cnt)
114
 
        note('%6d repeated file texts', self.repeated_text_cnt)
115
 
        note('%6d unreferenced text ancestors',
116
 
             len(self.unreferenced_ancestors))
117
 
        if self.missing_inventory_sha_cnt:
118
 
            note('%6d revisions are missing inventory_sha1',
119
 
                 self.missing_inventory_sha_cnt)
120
 
        if self.missing_revision_cnt:
121
 
            note('%6d revisions are mentioned but not present',
122
 
                 self.missing_revision_cnt)
123
 
        if len(self.ghosts):
124
 
            note('%6d ghost revisions', len(self.ghosts))
125
 
            if verbose:
126
 
                for ghost in self.ghosts:
127
 
                    note('      %s', ghost)
128
 
        if len(self.missing_parent_links):
129
 
            note('%6d revisions missing parents in ancestry',
130
 
                 len(self.missing_parent_links))
131
 
            if verbose:
132
 
                for link, linkers in self.missing_parent_links.items():
133
 
                    note('      %s should be in the ancestry for:', link)
134
 
                    for linker in linkers:
135
 
                        note('       * %s', linker)
136
 
            if verbose:
137
 
                for file_id, revision_id in self.unreferenced_ancestors:
138
 
                    log_error('unreferenced ancestor: {%s} in %s', revision_id,
139
 
                        file_id)
140
 
        if len(self.inconsistent_parents):
141
 
            note('%6d inconsistent parents', len(self.inconsistent_parents))
142
 
            if verbose:
143
 
                for info in self.inconsistent_parents:
144
 
                    revision_id, file_id, found_parents, correct_parents = info
145
 
                    note('      * %s version %s has parents %r '
146
 
                         'but should have %r'
147
 
                         % (file_id, revision_id, found_parents,
148
 
                             correct_parents))
149
 
        if self.revs_with_bad_parents_in_index:
150
 
            note('%6d revisions have incorrect parents in the revision index',
151
 
                 len(self.revs_with_bad_parents_in_index))
152
 
            if verbose:
153
 
                for item in self.revs_with_bad_parents_in_index:
154
 
                    revision_id, index_parents, actual_parents = item
155
 
                    note(
156
 
                        '       %s has wrong parents in index: '
157
 
                        '%r should be %r',
158
 
                        revision_id, index_parents, actual_parents)
159
 
 
160
 
    def check_one_rev(self, rev_id):
161
 
        """Check one revision.
162
 
 
163
 
        rev_id - the one to check
164
 
        """
165
 
        rev = self.repository.get_revision(rev_id)
166
 
                
167
 
        if rev.revision_id != rev_id:
168
 
            raise BzrCheckError('wrong internal revision id in revision {%s}'
169
 
                                % rev_id)
170
 
 
171
 
        for parent in rev.parent_ids:
172
 
            if not parent in self.planned_revisions:
173
 
                missing_links = self.missing_parent_links.get(parent, [])
174
 
                missing_links.append(rev_id)
175
 
                self.missing_parent_links[parent] = missing_links
176
 
                # list based so somewhat slow,
177
 
                # TODO have a planned_revisions list and set.
178
 
                if self.repository.has_revision(parent):
179
 
                    missing_ancestry = self.repository.get_ancestry(parent)
180
 
                    for missing in missing_ancestry:
181
 
                        if (missing is not None 
182
 
                            and missing not in self.planned_revisions):
183
 
                            self.planned_revisions.append(missing)
184
 
                else:
185
 
                    self.ghosts.append(rev_id)
186
 
 
187
 
        if rev.inventory_sha1:
188
 
            inv_sha1 = self.repository.get_inventory_sha1(rev_id)
189
 
            if inv_sha1 != rev.inventory_sha1:
190
 
                raise BzrCheckError('Inventory sha1 hash doesn\'t match'
191
 
                    ' value in revision {%s}' % rev_id)
192
 
        self._check_revision_tree(rev_id)
193
 
        self.checked_rev_cnt += 1
194
 
 
195
 
    def check_weaves(self):
196
 
        """Check all the weaves we can get our hands on.
197
 
        """
198
 
        n_weaves = 1
199
 
        weave_ids = []
200
 
        if self.repository.weave_store.listable():
201
 
            weave_ids = list(self.repository.weave_store)
202
 
            n_weaves = len(weave_ids) + 1
203
 
        self.progress.update('checking versionedfile', 0, n_weaves)
204
 
        self.inventory_weave.check(progress_bar=self.progress)
205
 
        files_in_revisions = {}
206
 
        revisions_of_files = {}
207
 
        for i, weave_id in enumerate(weave_ids):
208
 
            self.progress.update('checking versionedfile', i, n_weaves)
209
 
            w = self.repository.weave_store.get_weave(weave_id,
210
 
                    self.repository.get_transaction())
211
 
            # No progress here, because it looks ugly.
212
 
            w.check()
213
 
 
214
 
            weave_checker = self.repository.get_versioned_file_checker(
215
 
                self.planned_revisions, self.revision_versions)
216
 
            result = weave_checker.check_file_version_parents(w, weave_id)
217
 
 
218
 
            for revision_id, (weave_parents,correct_parents) in result.items():
219
 
                self.inconsistent_parents.append(
220
 
                    (revision_id, weave_id, weave_parents, correct_parents))
221
 
                unreferenced_parents = set(weave_parents)-set(correct_parents)
222
 
                for unreferenced_parent in unreferenced_parents:
223
 
                    self.unreferenced_ancestors.add(
224
 
                        (weave_id, unreferenced_parent))
225
 
            self.checked_weaves[weave_id] = True
226
 
 
227
 
    def _check_revision_tree(self, rev_id):
228
 
        tree = self.repository.revision_tree(rev_id)
229
 
        self.revision_versions.add_revision_text_versions(tree)
230
 
        inv = tree.inventory
231
 
        seen_ids = {}
232
 
        for file_id in inv:
233
 
            if file_id in seen_ids:
234
 
                raise BzrCheckError('duplicated file_id {%s} '
235
 
                                    'in inventory for revision {%s}'
236
 
                                    % (file_id, rev_id))
237
 
            seen_ids[file_id] = True
238
 
        for file_id in inv:
239
 
            ie = inv[file_id]
240
 
            ie.check(self, rev_id, inv, tree)
241
 
        seen_names = {}
242
 
        for path, ie in inv.iter_entries():
243
 
            if path in seen_names:
244
 
                raise BzrCheckError('duplicated path %s '
245
 
                                    'in inventory for revision {%s}'
246
 
                                    % (path, rev_id))
247
 
            seen_names[path] = True
248
 
 
249
 
 
250
 
def check(branch, verbose):
251
 
    """Run consistency checks on a branch.
252
 
    
253
 
    Results are reported through logging.
254
 
    
255
 
    :raise BzrCheckError: if there's a consistency error.
256
 
    """
257
 
    branch.lock_read()
258
 
    try:
259
 
        branch_result = branch.check()
260
 
        repo_result = branch.repository.check([branch.last_revision()])
261
 
    finally:
262
 
        branch.unlock()
263
 
    branch_result.report_results(verbose)
264
 
    repo_result.report_results(verbose)
265
 
 
266
 
 
 
22
 
 
23
 
 
24
######################################################################
 
25
# consistency checks
 
26
 
 
27
def check():
 
28
    """Consistency check of tree."""
 
29
    assert_in_tree()
 
30
    mutter("checking tree")
 
31
    check_patches_exist()
 
32
    check_patch_chaining()
 
33
    check_patch_uniqueness()
 
34
    check_inventory()
 
35
    mutter("tree looks OK")
 
36
    ## TODO: Check that previous-inventory and previous-manifest
 
37
    ## are the same as those stored in the previous changeset.
 
38
 
 
39
    ## TODO: Check all patches present in patch directory are
 
40
    ## mentioned in patch history; having an orphaned patch only gives
 
41
    ## a warning.
 
42
 
 
43
    ## TODO: Check cached data is consistent with data reconstructed
 
44
    ## from scratch.
 
45
 
 
46
    ## TODO: Check no control files are versioned.
 
47
 
 
48
    ## TODO: Check that the before-hash of each file in a later
 
49
    ## revision matches the after-hash in the previous revision to
 
50
    ## touch it.
 
51
 
 
52
 
 
53
def check_inventory():
 
54
    mutter("checking inventory file and ids...")
 
55
    seen_ids = Set()
 
56
    seen_names = Set()
 
57
    
 
58
    for l in controlfile('inventory').readlines():
 
59
        parts = l.split()
 
60
        if len(parts) != 2:
 
61
            bailout("malformed inventory line: " + `l`)
 
62
        file_id, name = parts
 
63
        
 
64
        if file_id in seen_ids:
 
65
            bailout("duplicated file id " + file_id)
 
66
        seen_ids.add(file_id)
 
67
 
 
68
        if name in seen_names:
 
69
            bailout("duplicated file name in inventory: " + quotefn(name))
 
70
        seen_names.add(name)
 
71
        
 
72
        if is_control_file(name):
 
73
            raise BzrError("control file %s present in inventory" % quotefn(name))
 
74
 
 
75
 
 
76
def check_patches_exist():
 
77
    """Check constraint of current version: all patches exist"""
 
78
    mutter("checking all patches are present...")
 
79
    for pid in revision_history():
 
80
        read_patch_header(pid)
 
81
 
 
82
 
 
83
def check_patch_chaining():
 
84
    """Check ancestry of patches and history file is consistent"""
 
85
    mutter("checking patch chaining...")
 
86
    prev = None
 
87
    for pid in revision_history():
 
88
        log_prev = read_patch_header(pid).precursor
 
89
        if log_prev != prev:
 
90
            bailout("inconsistent precursor links on " + pid)
 
91
        prev = pid
 
92
 
 
93
 
 
94
def check_patch_uniqueness():
 
95
    """Make sure no patch is listed twice in the history.
 
96
 
 
97
    This should be implied by having correct ancestry but I'll check it
 
98
    anyhow."""
 
99
    mutter("checking history for duplicates...")
 
100
    seen = Set()
 
101
    for pid in revision_history():
 
102
        if pid in seen:
 
103
            bailout("patch " + pid + " appears twice in history")
 
104
        seen.add(pid)
 
105
        
267
106