1
# Copyright (C) 2004, 2005 by Martin Pool
2
# Copyright (C) 2005 by Canonical Ltd
1
# Copyright (C) 2005, 2006 Canonical Ltd
4
3
# This program is free software; you can redistribute it and/or modify
5
4
# it under the terms of the GNU General Public License as published by
6
5
# the Free Software Foundation; either version 2 of the License, or
7
6
# (at your option) any later version.
9
8
# This program is distributed in the hope that it will be useful,
10
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
11
# GNU General Public License for more details.
14
13
# You should have received a copy of the GNU General Public License
15
14
# along with this program; if not, write to the Free Software
16
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28
27
# TODO: Get every revision in the revision-store even if they're not
29
28
# referenced by history and make sure they're all valid.
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
35
from bzrlib.errors import BzrCheckError
32
from bzrlib.trace import note, warning
33
from bzrlib.osutils import rename, sha_string, fingerprint_file
34
from bzrlib.trace import mutter
35
from bzrlib.errors import BzrCheckError, NoSuchRevision
36
from bzrlib.inventory import ROOT_ID
37
from bzrlib.trace import note
39
39
class Check(object):
42
def __init__(self, branch):
44
self.repository = branch.repository
40
"""Check a repository"""
42
# The Check object interacts with InventoryEntry.check, etc.
44
def __init__(self, repository):
45
self.repository = repository
45
46
self.checked_text_cnt = 0
46
47
self.checked_rev_cnt = 0
49
50
self.missing_parent_links = {}
50
51
self.missing_inventory_sha_cnt = 0
51
52
self.missing_revision_cnt = 0
52
# maps (file-id, version) -> sha1
53
# maps (file-id, version) -> sha1; used by InventoryFile._check
53
54
self.checked_texts = {}
54
55
self.checked_weaves = {}
57
self.branch.lock_read()
58
self.progress = bzrlib.ui.ui_factory.progress_bar()
58
self.repository.lock_read()
59
self.progress = bzrlib.ui.ui_factory.nested_progress_bar()
60
61
self.progress.update('retrieving inventory', 0, 0)
61
62
# do not put in init, as it should be done with progess,
62
63
# and inside the lock.
63
self.inventory_weave = self.branch.repository.get_inventory_weave()
64
self.history = self.branch.revision_history()
65
if not len(self.history):
64
self.inventory_weave = self.repository.get_inventory_weave()
68
65
self.plan_revisions()
70
67
self.check_weaves()
76
73
self.check_one_rev(rev_id)
75
self.progress.finished()
76
self.repository.unlock()
81
78
def plan_revisions(self):
82
repository = self.branch.repository
83
if not repository.revision_store.listable():
84
self.planned_revisions = repository.get_ancestry(self.history[-1])
85
self.planned_revisions.remove(None)
86
# FIXME progress bars should support this more nicely.
88
print ("Checking reachable history -"
89
" for a complete check use a local branch.")
92
self.planned_revisions = set(repository.revision_store)
93
inventoried = set(self.inventory_weave.names())
79
repository = self.repository
80
self.planned_revisions = set(repository.all_revision_ids())
82
inventoried = set(self.inventory_weave.versions())
94
83
awol = self.planned_revisions - inventoried
96
85
raise BzrCheckError('Stored revisions missing from inventory'
98
87
self.planned_revisions = list(self.planned_revisions)
100
89
def report_results(self, verbose):
101
note('checked branch %s format %s',
90
note('checked repository %s format %s',
91
self.repository.bzrdir.root_transport,
92
self.repository._format)
105
93
note('%6d revisions', self.checked_rev_cnt)
106
94
note('%6d unique file texts', self.checked_text_cnt)
107
95
note('%6d repeated file texts', self.repeated_text_cnt)
130
118
"""Check one revision.
132
120
rev_id - the one to check
134
last_rev_id - the previous one on the mainline, if any.
137
# mutter(' revision {%s}', rev_id)
140
rev_history_position = self.history.index(rev_id)
142
rev_history_position = None
144
if rev_history_position:
145
rev = branch.repository.get_revision(rev_id)
146
if rev_history_position > 0:
147
last_rev_id = self.history[rev_history_position - 1]
149
rev = branch.repository.get_revision(rev_id)
122
rev = self.repository.get_revision(rev_id)
151
124
if rev.revision_id != rev_id:
152
125
raise BzrCheckError('wrong internal revision id in revision {%s}'
155
# check the previous history entry is a parent of this entry
157
if last_rev_id is not None:
158
for parent_id in rev.parent_ids:
159
if parent_id == last_rev_id:
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)
162
raise BzrCheckError("previous revision {%s} not listed among "
164
% (last_rev_id, rev_id))
165
for parent in rev.parent_ids:
166
if not parent in self.planned_revisions:
167
missing_links = self.missing_parent_links.get(parent, [])
168
missing_links.append(rev_id)
169
self.missing_parent_links[parent] = missing_links
170
# list based so somewhat slow,
171
# TODO have a planned_revisions list and set.
172
if self.branch.has_revision(parent):
173
missing_ancestry = self.repository.get_ancestry(parent)
174
for missing in missing_ancestry:
175
if (missing is not None
176
and missing not in self.planned_revisions):
177
self.planned_revisions.append(missing)
179
self.ghosts.append(rev_id)
181
raise BzrCheckError("revision {%s} has no parents listed "
182
"but preceded by {%s}"
183
% (rev_id, last_rev_id))
142
self.ghosts.append(rev_id)
185
144
if rev.inventory_sha1:
186
inv_sha1 = branch.repository.get_inventory_sha1(rev_id)
145
inv_sha1 = self.repository.get_inventory_sha1(rev_id)
187
146
if inv_sha1 != rev.inventory_sha1:
188
147
raise BzrCheckError('Inventory sha1 hash doesn\'t match'
189
148
' value in revision {%s}' % rev_id)
191
missing_inventory_sha_cnt += 1
192
mutter("no inventory_sha1 on revision {%s}", rev_id)
193
149
self._check_revision_tree(rev_id)
194
150
self.checked_rev_cnt += 1
201
if self.branch.repository.weave_store.listable():
202
weave_ids = list(self.branch.repository.weave_store)
157
if self.repository.weave_store.listable():
158
weave_ids = list(self.repository.weave_store)
203
159
n_weaves = len(weave_ids)
204
160
self.progress.update('checking weave', 0, n_weaves)
205
161
self.inventory_weave.check(progress_bar=self.progress)
206
162
for i, weave_id in enumerate(weave_ids):
207
163
self.progress.update('checking weave', i, n_weaves)
208
w = self.branch.repository.weave_store.get_weave(weave_id,
209
self.branch.repository.get_transaction())
164
w = self.repository.weave_store.get_weave(weave_id,
165
self.repository.get_transaction())
210
166
# No progress here, because it looks ugly.
212
168
self.checked_weaves[weave_id] = True
214
170
def _check_revision_tree(self, rev_id):
215
tree = self.branch.repository.revision_tree(rev_id)
171
tree = self.repository.revision_tree(rev_id)
216
172
inv = tree.inventory
218
174
for file_id in inv:
236
192
def check(branch, verbose):
237
"""Run consistency checks on a branch."""
238
checker = Check(branch)
240
checker.report_results(verbose)
193
"""Run consistency checks on a branch.
195
Results are reported through logging.
197
:raise BzrCheckError: if there's a consistency error.
201
branch_result = branch.check()
202
repo_result = branch.repository.check([branch.last_revision()])
205
branch_result.report_results(verbose)
206
repo_result.report_results(verbose)