~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-19 08:19:38 UTC
  • Revision ID: mbp@sourcefrog.net-20050319081938-596d89f99a644569
use "/usr/bin/env python" for shebang"

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.errors import BzrCheckError
36
 
import bzrlib.ui
37
 
from bzrlib.trace import note
38
 
 
39
 
class Check(object):
40
 
    """Check a repository"""
41
 
 
42
 
    # The Check object interacts with InventoryEntry.check, etc.
43
 
 
44
 
    def __init__(self, repository):
45
 
        self.repository = repository
46
 
        self.checked_text_cnt = 0
47
 
        self.checked_rev_cnt = 0
48
 
        self.ghosts = []
49
 
        self.repeated_text_cnt = 0
50
 
        self.missing_parent_links = {}
51
 
        self.missing_inventory_sha_cnt = 0
52
 
        self.missing_revision_cnt = 0
53
 
        # maps (file-id, version) -> sha1; used by InventoryFile._check
54
 
        self.checked_texts = {}
55
 
        self.checked_weaves = {}
56
 
 
57
 
    def check(self):
58
 
        self.repository.lock_read()
59
 
        self.progress = bzrlib.ui.ui_factory.nested_progress_bar()
60
 
        try:
61
 
            self.progress.update('retrieving inventory', 0, 0)
62
 
            # do not put in init, as it should be done with progess,
63
 
            # and inside the lock.
64
 
            self.inventory_weave = self.repository.get_inventory_weave()
65
 
            self.plan_revisions()
66
 
            revno = 0
67
 
            self.check_weaves()
68
 
            while revno < len(self.planned_revisions):
69
 
                rev_id = self.planned_revisions[revno]
70
 
                self.progress.update('checking revision', revno,
71
 
                                     len(self.planned_revisions))
72
 
                revno += 1
73
 
                self.check_one_rev(rev_id)
74
 
        finally:
75
 
            self.progress.finished()
76
 
            self.repository.unlock()
77
 
 
78
 
    def plan_revisions(self):
79
 
        repository = self.repository
80
 
        self.planned_revisions = set(repository.all_revision_ids())
81
 
        self.progress.clear()
82
 
        inventoried = set(self.inventory_weave.versions())
83
 
        awol = self.planned_revisions - inventoried
84
 
        if len(awol) > 0:
85
 
            raise BzrCheckError('Stored revisions missing from inventory'
86
 
                '{%s}' % ','.join([f for f in awol]))
87
 
        self.planned_revisions = list(self.planned_revisions)
88
 
 
89
 
    def report_results(self, verbose):
90
 
        note('checked repository %s format %s',
91
 
             self.repository.bzrdir.root_transport,
92
 
             self.repository._format)
93
 
        note('%6d revisions', self.checked_rev_cnt)
94
 
        note('%6d unique file texts', self.checked_text_cnt)
95
 
        note('%6d repeated file texts', self.repeated_text_cnt)
96
 
        note('%6d weaves', len(self.checked_weaves))
97
 
        if self.missing_inventory_sha_cnt:
98
 
            note('%6d revisions are missing inventory_sha1',
99
 
                 self.missing_inventory_sha_cnt)
100
 
        if self.missing_revision_cnt:
101
 
            note('%6d revisions are mentioned but not present',
102
 
                 self.missing_revision_cnt)
103
 
        if len(self.ghosts):
104
 
            note('%6d ghost revisions', len(self.ghosts))
105
 
            if verbose:
106
 
                for ghost in self.ghosts:
107
 
                    note('      %s', ghost)
108
 
        if len(self.missing_parent_links):
109
 
            note('%6d revisions missing parents in ancestry',
110
 
                 len(self.missing_parent_links))
111
 
            if verbose:
112
 
                for link, linkers in self.missing_parent_links.items():
113
 
                    note('      %s should be in the ancestry for:', link)
114
 
                    for linker in linkers:
115
 
                        note('       * %s', linker)
116
 
 
117
 
    def check_one_rev(self, rev_id):
118
 
        """Check one revision.
119
 
 
120
 
        rev_id - the one to check
121
 
        """
122
 
        rev = self.repository.get_revision(rev_id)
123
 
                
124
 
        if rev.revision_id != rev_id:
125
 
            raise BzrCheckError('wrong internal revision id in revision {%s}'
126
 
                                % rev_id)
127
 
 
128
 
        for parent in rev.parent_ids:
129
 
            if not parent in self.planned_revisions:
130
 
                missing_links = self.missing_parent_links.get(parent, [])
131
 
                missing_links.append(rev_id)
132
 
                self.missing_parent_links[parent] = missing_links
133
 
                # list based so somewhat slow,
134
 
                # TODO have a planned_revisions list and set.
135
 
                if self.repository.has_revision(parent):
136
 
                    missing_ancestry = self.repository.get_ancestry(parent)
137
 
                    for missing in missing_ancestry:
138
 
                        if (missing is not None 
139
 
                            and missing not in self.planned_revisions):
140
 
                            self.planned_revisions.append(missing)
141
 
                else:
142
 
                    self.ghosts.append(rev_id)
143
 
 
144
 
        if rev.inventory_sha1:
145
 
            inv_sha1 = self.repository.get_inventory_sha1(rev_id)
146
 
            if inv_sha1 != rev.inventory_sha1:
147
 
                raise BzrCheckError('Inventory sha1 hash doesn\'t match'
148
 
                    ' value in revision {%s}' % rev_id)
149
 
        self._check_revision_tree(rev_id)
150
 
        self.checked_rev_cnt += 1
151
 
 
152
 
    def check_weaves(self):
153
 
        """Check all the weaves we can get our hands on.
154
 
        """
155
 
        n_weaves = 1
156
 
        weave_ids = []
157
 
        if self.repository.weave_store.listable():
158
 
            weave_ids = list(self.repository.weave_store)
159
 
            n_weaves = len(weave_ids)
160
 
        self.progress.update('checking weave', 0, n_weaves)
161
 
        self.inventory_weave.check(progress_bar=self.progress)
162
 
        for i, weave_id in enumerate(weave_ids):
163
 
            self.progress.update('checking weave', i, n_weaves)
164
 
            w = self.repository.weave_store.get_weave(weave_id,
165
 
                    self.repository.get_transaction())
166
 
            # No progress here, because it looks ugly.
167
 
            w.check()
168
 
            self.checked_weaves[weave_id] = True
169
 
 
170
 
    def _check_revision_tree(self, rev_id):
171
 
        tree = self.repository.revision_tree(rev_id)
172
 
        inv = tree.inventory
173
 
        seen_ids = {}
174
 
        for file_id in inv:
175
 
            if file_id in seen_ids:
176
 
                raise BzrCheckError('duplicated file_id {%s} '
177
 
                                    'in inventory for revision {%s}'
178
 
                                    % (file_id, rev_id))
179
 
            seen_ids[file_id] = True
180
 
        for file_id in inv:
181
 
            ie = inv[file_id]
182
 
            ie.check(self, rev_id, inv, tree)
183
 
        seen_names = {}
184
 
        for path, ie in inv.iter_entries():
185
 
            if path in seen_names:
186
 
                raise BzrCheckError('duplicated path %s '
187
 
                                    'in inventory for revision {%s}'
188
 
                                    % (path, rev_id))
189
 
            seen_names[path] = True
190
 
 
191
 
 
192
 
def check(branch, verbose):
193
 
    """Run consistency checks on a branch.
194
 
    
195
 
    Results are reported through logging.
196
 
    
197
 
    :raise BzrCheckError: if there's a consistency error.
198
 
    """
199
 
    branch.lock_read()
200
 
    try:
201
 
        branch_result = branch.check()
202
 
        repo_result = branch.repository.check([branch.last_revision()])
203
 
    finally:
204
 
        branch.unlock()
205
 
    branch_result.report_results(verbose)
206
 
    repo_result.report_results(verbose)
 
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
        
 
106