1
# Copyright (C) 2005, 2006 by Canonical Ltd
1
# Copyright (C) 2004, 2005 by Martin Pool
2
# Copyright (C) 2005 by Canonical Ltd
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.
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.
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
27
28
# TODO: Get every revision in the revision-store even if they're not
28
29
# 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
37
from bzrlib.trace import note
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
39
39
class Check(object):
40
"""Check a repository"""
42
# The Check object interacts with InventoryEntry.check, etc.
44
def __init__(self, repository):
45
self.repository = repository
42
def __init__(self, branch):
46
44
self.checked_text_cnt = 0
47
45
self.checked_rev_cnt = 0
50
48
self.missing_parent_links = {}
51
49
self.missing_inventory_sha_cnt = 0
52
50
self.missing_revision_cnt = 0
53
# maps (file-id, version) -> sha1; used by InventoryFile._check
51
# maps (file-id, version) -> sha1
54
52
self.checked_texts = {}
55
self.checked_weaves = {}
58
self.repository.lock_read()
59
self.progress = bzrlib.ui.ui_factory.nested_progress_bar()
55
self.branch.lock_read()
56
self.progress = bzrlib.ui.ui_factory.progress_bar()
61
58
self.progress.update('retrieving inventory', 0, 0)
62
59
# do not put in init, as it should be done with progess,
63
60
# and inside the lock.
64
self.inventory_weave = self.repository.get_inventory_weave()
61
self.inventory_weave = self.branch._get_inventory_weave()
62
self.history = self.branch.revision_history()
63
if not len(self.history):
65
66
self.plan_revisions()
68
68
while revno < len(self.planned_revisions):
69
69
rev_id = self.planned_revisions[revno]
70
70
self.progress.update('checking revision', revno,
73
73
self.check_one_rev(rev_id)
75
self.progress.finished()
76
self.repository.unlock()
78
78
def plan_revisions(self):
79
repository = self.repository
80
self.planned_revisions = set(repository.all_revision_ids())
82
inventoried = set(self.inventory_weave.versions())
79
if not self.branch.revision_store.listable():
80
self.planned_revisions = self.branch.get_ancestry(self.history[-1])
81
self.planned_revisions.remove(None)
82
# FIXME progress bars should support this more nicely.
84
print ("Checking reachable history -"
85
" for a complete check use a local branch.")
88
self.planned_revisions = set(self.branch.revision_store)
89
inventoried = set(self.inventory_weave.names())
83
90
awol = self.planned_revisions - inventoried
85
92
raise BzrCheckError('Stored revisions missing from inventory'
87
94
self.planned_revisions = list(self.planned_revisions)
89
96
def report_results(self, verbose):
90
note('checked repository %s format %s',
91
self.repository.bzrdir.root_transport,
92
self.repository._format)
97
note('checked branch %s format %d',
99
self.branch._branch_format)
93
101
note('%6d revisions', self.checked_rev_cnt)
94
102
note('%6d unique file texts', self.checked_text_cnt)
95
103
note('%6d repeated file texts', self.repeated_text_cnt)
96
note('%6d weaves', len(self.checked_weaves))
97
104
if self.missing_inventory_sha_cnt:
98
105
note('%6d revisions are missing inventory_sha1',
99
106
self.missing_inventory_sha_cnt)
118
125
"""Check one revision.
120
127
rev_id - the one to check
129
last_rev_id - the previous one on the mainline, if any.
122
rev = self.repository.get_revision(rev_id)
132
# mutter(' revision {%s}', rev_id)
135
rev_history_position = self.history.index(rev_id)
137
rev_history_position = None
139
if rev_history_position:
140
rev = branch.get_revision(rev_id)
141
if rev_history_position > 0:
142
last_rev_id = self.history[rev_history_position - 1]
144
rev = branch.get_revision(rev_id)
124
146
if rev.revision_id != rev_id:
125
147
raise BzrCheckError('wrong internal revision id in revision {%s}'
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)
150
# check the previous history entry is a parent of this entry
152
if last_rev_id is not None:
153
for parent_id in rev.parent_ids:
154
if parent_id == last_rev_id:
142
self.ghosts.append(rev_id)
157
raise BzrCheckError("previous revision {%s} not listed among "
159
% (last_rev_id, rev_id))
160
for parent in rev.parent_ids:
161
if not parent in self.planned_revisions:
162
missing_links = self.missing_parent_links.get(parent, [])
163
missing_links.append(rev_id)
164
self.missing_parent_links[parent] = missing_links
165
# list based so somewhat slow,
166
# TODO have a planned_revisions list and set.
167
if self.branch.has_revision(parent):
168
missing_ancestry = self.branch.get_ancestry(parent)
169
for missing in missing_ancestry:
170
if (missing is not None
171
and missing not in self.planned_revisions):
172
self.planned_revisions.append(missing)
174
self.ghosts.append(rev_id)
176
raise BzrCheckError("revision {%s} has no parents listed "
177
"but preceded by {%s}"
178
% (rev_id, last_rev_id))
144
180
if rev.inventory_sha1:
145
inv_sha1 = self.repository.get_inventory_sha1(rev_id)
181
inv_sha1 = branch.get_inventory_sha1(rev_id)
146
182
if inv_sha1 != rev.inventory_sha1:
147
183
raise BzrCheckError('Inventory sha1 hash doesn\'t match'
148
184
' value in revision {%s}' % rev_id)
186
missing_inventory_sha_cnt += 1
187
mutter("no inventory_sha1 on revision {%s}", rev_id)
149
188
self._check_revision_tree(rev_id)
150
189
self.checked_rev_cnt += 1
152
def check_weaves(self):
153
"""Check all the weaves we can get our hands on.
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.
168
self.checked_weaves[weave_id] = True
170
191
def _check_revision_tree(self, rev_id):
171
tree = self.repository.revision_tree(rev_id)
192
tree = self.branch.revision_tree(rev_id)
172
193
inv = tree.inventory
174
195
for file_id in inv: