~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/check.py

  • Committer: John Arbash Meinel
  • Date: 2007-05-04 18:59:36 UTC
  • mto: This revision was merged to the branch mainline in revision 2643.
  • Revision ID: john@arbash-meinel.com-20070504185936-1mjdoqmtz74xe5mg
A C implementation of _fields_to_entry_0_parents drops the time from 400ms to 330ms for a 21k-entry tree

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#! /usr/bin/python
2
 
 
3
 
 
4
 
# Copyright (C) 2004, 2005 by Martin Pool
5
 
# Copyright (C) 2005 by Canonical Ltd
6
 
 
7
 
 
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
#
8
3
# This program is free software; you can redistribute it and/or modify
9
4
# it under the terms of the GNU General Public License as published by
10
5
# the Free Software Foundation; either version 2 of the License, or
11
6
# (at your option) any later version.
12
 
 
 
7
#
13
8
# This program is distributed in the hope that it will be useful,
14
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
11
# GNU General Public License for more details.
17
 
 
 
12
#
18
13
# You should have received a copy of the GNU General Public License
19
14
# along with this program; if not, write to the Free Software
20
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21
16
 
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
 
 
 
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)