##// END OF EJS Templates
verify: add some inline documentation to the top level `verify` method...
marmoute -
r42035:4da2261e default
parent child Browse files
Show More
@@ -1,467 +1,465
1 # verify.py - repository integrity checking for Mercurial
1 # verify.py - repository integrity checking for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import (
13 from .node import (
14 nullid,
14 nullid,
15 short,
15 short,
16 )
16 )
17
17
18 from . import (
18 from . import (
19 error,
19 error,
20 pycompat,
20 pycompat,
21 revlog,
21 revlog,
22 util,
22 util,
23 )
23 )
24
24
25 def verify(repo):
25 def verify(repo):
26 with repo.lock():
26 with repo.lock():
27 return verifier(repo).verify()
27 return verifier(repo).verify()
28
28
29 def _normpath(f):
29 def _normpath(f):
30 # under hg < 2.4, convert didn't sanitize paths properly, so a
30 # under hg < 2.4, convert didn't sanitize paths properly, so a
31 # converted repo may contain repeated slashes
31 # converted repo may contain repeated slashes
32 while '//' in f:
32 while '//' in f:
33 f = f.replace('//', '/')
33 f = f.replace('//', '/')
34 return f
34 return f
35
35
36 class verifier(object):
36 class verifier(object):
37 def __init__(self, repo):
37 def __init__(self, repo):
38 self.repo = repo.unfiltered()
38 self.repo = repo.unfiltered()
39 self.ui = repo.ui
39 self.ui = repo.ui
40 self.match = repo.narrowmatch()
40 self.match = repo.narrowmatch()
41 self.badrevs = set()
41 self.badrevs = set()
42 self.errors = 0
42 self.errors = 0
43 self.warnings = 0
43 self.warnings = 0
44 self.havecl = len(repo.changelog) > 0
44 self.havecl = len(repo.changelog) > 0
45 self.havemf = len(repo.manifestlog.getstorage(b'')) > 0
45 self.havemf = len(repo.manifestlog.getstorage(b'')) > 0
46 self.revlogv1 = repo.changelog.version != revlog.REVLOGV0
46 self.revlogv1 = repo.changelog.version != revlog.REVLOGV0
47 self.lrugetctx = util.lrucachefunc(repo.__getitem__)
47 self.lrugetctx = util.lrucachefunc(repo.__getitem__)
48 self.refersmf = False
48 self.refersmf = False
49 self.fncachewarned = False
49 self.fncachewarned = False
50 # developer config: verify.skipflags
50 # developer config: verify.skipflags
51 self.skipflags = repo.ui.configint('verify', 'skipflags')
51 self.skipflags = repo.ui.configint('verify', 'skipflags')
52 self.warnorphanstorefiles = True
52 self.warnorphanstorefiles = True
53
53
54 def _warn(self, msg):
54 def _warn(self, msg):
55 """record a "warning" level issue"""
55 """record a "warning" level issue"""
56 self.ui.warn(msg + "\n")
56 self.ui.warn(msg + "\n")
57 self.warnings += 1
57 self.warnings += 1
58
58
59 def _err(self, linkrev, msg, filename=None):
59 def _err(self, linkrev, msg, filename=None):
60 """record a "error" level issue"""
60 """record a "error" level issue"""
61 if linkrev is not None:
61 if linkrev is not None:
62 self.badrevs.add(linkrev)
62 self.badrevs.add(linkrev)
63 linkrev = "%d" % linkrev
63 linkrev = "%d" % linkrev
64 else:
64 else:
65 linkrev = '?'
65 linkrev = '?'
66 msg = "%s: %s" % (linkrev, msg)
66 msg = "%s: %s" % (linkrev, msg)
67 if filename:
67 if filename:
68 msg = "%s@%s" % (filename, msg)
68 msg = "%s@%s" % (filename, msg)
69 self.ui.warn(" " + msg + "\n")
69 self.ui.warn(" " + msg + "\n")
70 self.errors += 1
70 self.errors += 1
71
71
72 def _exc(self, linkrev, msg, inst, filename=None):
72 def _exc(self, linkrev, msg, inst, filename=None):
73 """record exception raised during the verify process"""
73 """record exception raised during the verify process"""
74 fmsg = pycompat.bytestr(inst)
74 fmsg = pycompat.bytestr(inst)
75 if not fmsg:
75 if not fmsg:
76 fmsg = pycompat.byterepr(inst)
76 fmsg = pycompat.byterepr(inst)
77 self._err(linkrev, "%s: %s" % (msg, fmsg), filename)
77 self._err(linkrev, "%s: %s" % (msg, fmsg), filename)
78
78
79 def checklog(self, obj, name, linkrev):
79 def checklog(self, obj, name, linkrev):
80 if not len(obj) and (self.havecl or self.havemf):
80 if not len(obj) and (self.havecl or self.havemf):
81 self._err(linkrev, _("empty or missing %s") % name)
81 self._err(linkrev, _("empty or missing %s") % name)
82 return
82 return
83
83
84 d = obj.checksize()
84 d = obj.checksize()
85 if d[0]:
85 if d[0]:
86 self.err(None, _("data length off by %d bytes") % d[0], name)
86 self.err(None, _("data length off by %d bytes") % d[0], name)
87 if d[1]:
87 if d[1]:
88 self.err(None, _("index contains %d extra bytes") % d[1], name)
88 self.err(None, _("index contains %d extra bytes") % d[1], name)
89
89
90 if obj.version != revlog.REVLOGV0:
90 if obj.version != revlog.REVLOGV0:
91 if not self.revlogv1:
91 if not self.revlogv1:
92 self._warn(_("warning: `%s' uses revlog format 1") % name)
92 self._warn(_("warning: `%s' uses revlog format 1") % name)
93 elif self.revlogv1:
93 elif self.revlogv1:
94 self._warn(_("warning: `%s' uses revlog format 0") % name)
94 self._warn(_("warning: `%s' uses revlog format 0") % name)
95
95
96 def checkentry(self, obj, i, node, seen, linkrevs, f):
96 def checkentry(self, obj, i, node, seen, linkrevs, f):
97 lr = obj.linkrev(obj.rev(node))
97 lr = obj.linkrev(obj.rev(node))
98 if lr < 0 or (self.havecl and lr not in linkrevs):
98 if lr < 0 or (self.havecl and lr not in linkrevs):
99 if lr < 0 or lr >= len(self.repo.changelog):
99 if lr < 0 or lr >= len(self.repo.changelog):
100 msg = _("rev %d points to nonexistent changeset %d")
100 msg = _("rev %d points to nonexistent changeset %d")
101 else:
101 else:
102 msg = _("rev %d points to unexpected changeset %d")
102 msg = _("rev %d points to unexpected changeset %d")
103 self._err(None, msg % (i, lr), f)
103 self._err(None, msg % (i, lr), f)
104 if linkrevs:
104 if linkrevs:
105 if f and len(linkrevs) > 1:
105 if f and len(linkrevs) > 1:
106 try:
106 try:
107 # attempt to filter down to real linkrevs
107 # attempt to filter down to real linkrevs
108 linkrevs = [l for l in linkrevs
108 linkrevs = [l for l in linkrevs
109 if self.lrugetctx(l)[f].filenode() == node]
109 if self.lrugetctx(l)[f].filenode() == node]
110 except Exception:
110 except Exception:
111 pass
111 pass
112 self._warn(_(" (expected %s)") % " ".join
112 self._warn(_(" (expected %s)") % " ".join
113 (map(pycompat.bytestr, linkrevs)))
113 (map(pycompat.bytestr, linkrevs)))
114 lr = None # can't be trusted
114 lr = None # can't be trusted
115
115
116 try:
116 try:
117 p1, p2 = obj.parents(node)
117 p1, p2 = obj.parents(node)
118 if p1 not in seen and p1 != nullid:
118 if p1 not in seen and p1 != nullid:
119 self._err(lr, _("unknown parent 1 %s of %s") %
119 self._err(lr, _("unknown parent 1 %s of %s") %
120 (short(p1), short(node)), f)
120 (short(p1), short(node)), f)
121 if p2 not in seen and p2 != nullid:
121 if p2 not in seen and p2 != nullid:
122 self._err(lr, _("unknown parent 2 %s of %s") %
122 self._err(lr, _("unknown parent 2 %s of %s") %
123 (short(p2), short(node)), f)
123 (short(p2), short(node)), f)
124 except Exception as inst:
124 except Exception as inst:
125 self._exc(lr, _("checking parents of %s") % short(node), inst, f)
125 self._exc(lr, _("checking parents of %s") % short(node), inst, f)
126
126
127 if node in seen:
127 if node in seen:
128 self._err(lr, _("duplicate revision %d (%d)") % (i, seen[node]), f)
128 self._err(lr, _("duplicate revision %d (%d)") % (i, seen[node]), f)
129 seen[node] = i
129 seen[node] = i
130 return lr
130 return lr
131
131
132 def verify(self):
132 def verify(self):
133 """verify the content of the Mercurial repository
133 """verify the content of the Mercurial repository
134
134
135 This method run all verifications, displaying issues as they are found.
135 This method run all verifications, displaying issues as they are found.
136
136
137 return 1 if any error have been encountered, 0 otherwise."""
137 return 1 if any error have been encountered, 0 otherwise."""
138 # initial validation and generic report
138 repo = self.repo
139 repo = self.repo
139
140 ui = repo.ui
140 ui = repo.ui
141
142 if not repo.url().startswith('file:'):
141 if not repo.url().startswith('file:'):
143 raise error.Abort(_("cannot verify bundle or remote repos"))
142 raise error.Abort(_("cannot verify bundle or remote repos"))
144
143
145 if os.path.exists(repo.sjoin("journal")):
144 if os.path.exists(repo.sjoin("journal")):
146 ui.warn(_("abandoned transaction found - run hg recover\n"))
145 ui.warn(_("abandoned transaction found - run hg recover\n"))
147
146
148 if ui.verbose or not self.revlogv1:
147 if ui.verbose or not self.revlogv1:
149 ui.status(_("repository uses revlog format %d\n") %
148 ui.status(_("repository uses revlog format %d\n") %
150 (self.revlogv1 and 1 or 0))
149 (self.revlogv1 and 1 or 0))
151
150
151 # data verification
152 mflinkrevs, filelinkrevs = self._verifychangelog()
152 mflinkrevs, filelinkrevs = self._verifychangelog()
153
154 filenodes = self._verifymanifest(mflinkrevs)
153 filenodes = self._verifymanifest(mflinkrevs)
155 del mflinkrevs
154 del mflinkrevs
156
157 self._crosscheckfiles(filelinkrevs, filenodes)
155 self._crosscheckfiles(filelinkrevs, filenodes)
158
159 totalfiles, filerevisions = self._verifyfiles(filenodes, filelinkrevs)
156 totalfiles, filerevisions = self._verifyfiles(filenodes, filelinkrevs)
160
157
158 # final report
161 ui.status(_("checked %d changesets with %d changes to %d files\n") %
159 ui.status(_("checked %d changesets with %d changes to %d files\n") %
162 (len(repo.changelog), filerevisions, totalfiles))
160 (len(repo.changelog), filerevisions, totalfiles))
163 if self.warnings:
161 if self.warnings:
164 ui.warn(_("%d warnings encountered!\n") % self.warnings)
162 ui.warn(_("%d warnings encountered!\n") % self.warnings)
165 if self.fncachewarned:
163 if self.fncachewarned:
166 ui.warn(_('hint: run "hg debugrebuildfncache" to recover from '
164 ui.warn(_('hint: run "hg debugrebuildfncache" to recover from '
167 'corrupt fncache\n'))
165 'corrupt fncache\n'))
168 if self.errors:
166 if self.errors:
169 ui.warn(_("%d integrity errors encountered!\n") % self.errors)
167 ui.warn(_("%d integrity errors encountered!\n") % self.errors)
170 if self.badrevs:
168 if self.badrevs:
171 ui.warn(_("(first damaged changeset appears to be %d)\n")
169 ui.warn(_("(first damaged changeset appears to be %d)\n")
172 % min(self.badrevs))
170 % min(self.badrevs))
173 return 1
171 return 1
174 return 0
172 return 0
175
173
176 def _verifychangelog(self):
174 def _verifychangelog(self):
177 ui = self.ui
175 ui = self.ui
178 repo = self.repo
176 repo = self.repo
179 match = self.match
177 match = self.match
180 cl = repo.changelog
178 cl = repo.changelog
181
179
182 ui.status(_("checking changesets\n"))
180 ui.status(_("checking changesets\n"))
183 mflinkrevs = {}
181 mflinkrevs = {}
184 filelinkrevs = {}
182 filelinkrevs = {}
185 seen = {}
183 seen = {}
186 self.checklog(cl, "changelog", 0)
184 self.checklog(cl, "changelog", 0)
187 progress = ui.makeprogress(_('checking'), unit=_('changesets'),
185 progress = ui.makeprogress(_('checking'), unit=_('changesets'),
188 total=len(repo))
186 total=len(repo))
189 for i in repo:
187 for i in repo:
190 progress.update(i)
188 progress.update(i)
191 n = cl.node(i)
189 n = cl.node(i)
192 self.checkentry(cl, i, n, seen, [i], "changelog")
190 self.checkentry(cl, i, n, seen, [i], "changelog")
193
191
194 try:
192 try:
195 changes = cl.read(n)
193 changes = cl.read(n)
196 if changes[0] != nullid:
194 if changes[0] != nullid:
197 mflinkrevs.setdefault(changes[0], []).append(i)
195 mflinkrevs.setdefault(changes[0], []).append(i)
198 self.refersmf = True
196 self.refersmf = True
199 for f in changes[3]:
197 for f in changes[3]:
200 if match(f):
198 if match(f):
201 filelinkrevs.setdefault(_normpath(f), []).append(i)
199 filelinkrevs.setdefault(_normpath(f), []).append(i)
202 except Exception as inst:
200 except Exception as inst:
203 self.refersmf = True
201 self.refersmf = True
204 self._exc(i, _("unpacking changeset %s") % short(n), inst)
202 self._exc(i, _("unpacking changeset %s") % short(n), inst)
205 progress.complete()
203 progress.complete()
206 return mflinkrevs, filelinkrevs
204 return mflinkrevs, filelinkrevs
207
205
208 def _verifymanifest(self, mflinkrevs, dir="", storefiles=None,
206 def _verifymanifest(self, mflinkrevs, dir="", storefiles=None,
209 subdirprogress=None):
207 subdirprogress=None):
210 repo = self.repo
208 repo = self.repo
211 ui = self.ui
209 ui = self.ui
212 match = self.match
210 match = self.match
213 mfl = self.repo.manifestlog
211 mfl = self.repo.manifestlog
214 mf = mfl.getstorage(dir)
212 mf = mfl.getstorage(dir)
215
213
216 if not dir:
214 if not dir:
217 self.ui.status(_("checking manifests\n"))
215 self.ui.status(_("checking manifests\n"))
218
216
219 filenodes = {}
217 filenodes = {}
220 subdirnodes = {}
218 subdirnodes = {}
221 seen = {}
219 seen = {}
222 label = "manifest"
220 label = "manifest"
223 if dir:
221 if dir:
224 label = dir
222 label = dir
225 revlogfiles = mf.files()
223 revlogfiles = mf.files()
226 storefiles.difference_update(revlogfiles)
224 storefiles.difference_update(revlogfiles)
227 if subdirprogress: # should be true since we're in a subdirectory
225 if subdirprogress: # should be true since we're in a subdirectory
228 subdirprogress.increment()
226 subdirprogress.increment()
229 if self.refersmf:
227 if self.refersmf:
230 # Do not check manifest if there are only changelog entries with
228 # Do not check manifest if there are only changelog entries with
231 # null manifests.
229 # null manifests.
232 self.checklog(mf, label, 0)
230 self.checklog(mf, label, 0)
233 progress = ui.makeprogress(_('checking'), unit=_('manifests'),
231 progress = ui.makeprogress(_('checking'), unit=_('manifests'),
234 total=len(mf))
232 total=len(mf))
235 for i in mf:
233 for i in mf:
236 if not dir:
234 if not dir:
237 progress.update(i)
235 progress.update(i)
238 n = mf.node(i)
236 n = mf.node(i)
239 lr = self.checkentry(mf, i, n, seen, mflinkrevs.get(n, []), label)
237 lr = self.checkentry(mf, i, n, seen, mflinkrevs.get(n, []), label)
240 if n in mflinkrevs:
238 if n in mflinkrevs:
241 del mflinkrevs[n]
239 del mflinkrevs[n]
242 elif dir:
240 elif dir:
243 self._err(lr, _("%s not in parent-directory manifest") %
241 self._err(lr, _("%s not in parent-directory manifest") %
244 short(n), label)
242 short(n), label)
245 else:
243 else:
246 self._err(lr, _("%s not in changesets") % short(n), label)
244 self._err(lr, _("%s not in changesets") % short(n), label)
247
245
248 try:
246 try:
249 mfdelta = mfl.get(dir, n).readdelta(shallow=True)
247 mfdelta = mfl.get(dir, n).readdelta(shallow=True)
250 for f, fn, fl in mfdelta.iterentries():
248 for f, fn, fl in mfdelta.iterentries():
251 if not f:
249 if not f:
252 self._err(lr, _("entry without name in manifest"))
250 self._err(lr, _("entry without name in manifest"))
253 elif f == "/dev/null": # ignore this in very old repos
251 elif f == "/dev/null": # ignore this in very old repos
254 continue
252 continue
255 fullpath = dir + _normpath(f)
253 fullpath = dir + _normpath(f)
256 if fl == 't':
254 if fl == 't':
257 if not match.visitdir(fullpath):
255 if not match.visitdir(fullpath):
258 continue
256 continue
259 subdirnodes.setdefault(fullpath + '/', {}).setdefault(
257 subdirnodes.setdefault(fullpath + '/', {}).setdefault(
260 fn, []).append(lr)
258 fn, []).append(lr)
261 else:
259 else:
262 if not match(fullpath):
260 if not match(fullpath):
263 continue
261 continue
264 filenodes.setdefault(fullpath, {}).setdefault(fn, lr)
262 filenodes.setdefault(fullpath, {}).setdefault(fn, lr)
265 except Exception as inst:
263 except Exception as inst:
266 self._exc(lr, _("reading delta %s") % short(n), inst, label)
264 self._exc(lr, _("reading delta %s") % short(n), inst, label)
267 if not dir:
265 if not dir:
268 progress.complete()
266 progress.complete()
269
267
270 if self.havemf:
268 if self.havemf:
271 for c, m in sorted([(c, m) for m in mflinkrevs
269 for c, m in sorted([(c, m) for m in mflinkrevs
272 for c in mflinkrevs[m]]):
270 for c in mflinkrevs[m]]):
273 if dir:
271 if dir:
274 self._err(c, _("parent-directory manifest refers to unknown"
272 self._err(c, _("parent-directory manifest refers to unknown"
275 " revision %s") % short(m), label)
273 " revision %s") % short(m), label)
276 else:
274 else:
277 self._err(c, _("changeset refers to unknown revision %s") %
275 self._err(c, _("changeset refers to unknown revision %s") %
278 short(m), label)
276 short(m), label)
279
277
280 if not dir and subdirnodes:
278 if not dir and subdirnodes:
281 self.ui.status(_("checking directory manifests\n"))
279 self.ui.status(_("checking directory manifests\n"))
282 storefiles = set()
280 storefiles = set()
283 subdirs = set()
281 subdirs = set()
284 revlogv1 = self.revlogv1
282 revlogv1 = self.revlogv1
285 for f, f2, size in repo.store.datafiles():
283 for f, f2, size in repo.store.datafiles():
286 if not f:
284 if not f:
287 self._err(None, _("cannot decode filename '%s'") % f2)
285 self._err(None, _("cannot decode filename '%s'") % f2)
288 elif (size > 0 or not revlogv1) and f.startswith('meta/'):
286 elif (size > 0 or not revlogv1) and f.startswith('meta/'):
289 storefiles.add(_normpath(f))
287 storefiles.add(_normpath(f))
290 subdirs.add(os.path.dirname(f))
288 subdirs.add(os.path.dirname(f))
291 subdirprogress = ui.makeprogress(_('checking'), unit=_('manifests'),
289 subdirprogress = ui.makeprogress(_('checking'), unit=_('manifests'),
292 total=len(subdirs))
290 total=len(subdirs))
293
291
294 for subdir, linkrevs in subdirnodes.iteritems():
292 for subdir, linkrevs in subdirnodes.iteritems():
295 subdirfilenodes = self._verifymanifest(linkrevs, subdir, storefiles,
293 subdirfilenodes = self._verifymanifest(linkrevs, subdir, storefiles,
296 subdirprogress)
294 subdirprogress)
297 for f, onefilenodes in subdirfilenodes.iteritems():
295 for f, onefilenodes in subdirfilenodes.iteritems():
298 filenodes.setdefault(f, {}).update(onefilenodes)
296 filenodes.setdefault(f, {}).update(onefilenodes)
299
297
300 if not dir and subdirnodes:
298 if not dir and subdirnodes:
301 subdirprogress.complete()
299 subdirprogress.complete()
302 if self.warnorphanstorefiles:
300 if self.warnorphanstorefiles:
303 for f in sorted(storefiles):
301 for f in sorted(storefiles):
304 self._warn(_("warning: orphan data file '%s'") % f)
302 self._warn(_("warning: orphan data file '%s'") % f)
305
303
306 return filenodes
304 return filenodes
307
305
308 def _crosscheckfiles(self, filelinkrevs, filenodes):
306 def _crosscheckfiles(self, filelinkrevs, filenodes):
309 repo = self.repo
307 repo = self.repo
310 ui = self.ui
308 ui = self.ui
311 ui.status(_("crosschecking files in changesets and manifests\n"))
309 ui.status(_("crosschecking files in changesets and manifests\n"))
312
310
313 total = len(filelinkrevs) + len(filenodes)
311 total = len(filelinkrevs) + len(filenodes)
314 progress = ui.makeprogress(_('crosschecking'), unit=_('files'),
312 progress = ui.makeprogress(_('crosschecking'), unit=_('files'),
315 total=total)
313 total=total)
316 if self.havemf:
314 if self.havemf:
317 for f in sorted(filelinkrevs):
315 for f in sorted(filelinkrevs):
318 progress.increment()
316 progress.increment()
319 if f not in filenodes:
317 if f not in filenodes:
320 lr = filelinkrevs[f][0]
318 lr = filelinkrevs[f][0]
321 self._err(lr, _("in changeset but not in manifest"), f)
319 self._err(lr, _("in changeset but not in manifest"), f)
322
320
323 if self.havecl:
321 if self.havecl:
324 for f in sorted(filenodes):
322 for f in sorted(filenodes):
325 progress.increment()
323 progress.increment()
326 if f not in filelinkrevs:
324 if f not in filelinkrevs:
327 try:
325 try:
328 fl = repo.file(f)
326 fl = repo.file(f)
329 lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]])
327 lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]])
330 except Exception:
328 except Exception:
331 lr = None
329 lr = None
332 self._err(lr, _("in manifest but not in changeset"), f)
330 self._err(lr, _("in manifest but not in changeset"), f)
333
331
334 progress.complete()
332 progress.complete()
335
333
336 def _verifyfiles(self, filenodes, filelinkrevs):
334 def _verifyfiles(self, filenodes, filelinkrevs):
337 repo = self.repo
335 repo = self.repo
338 ui = self.ui
336 ui = self.ui
339 lrugetctx = self.lrugetctx
337 lrugetctx = self.lrugetctx
340 revlogv1 = self.revlogv1
338 revlogv1 = self.revlogv1
341 havemf = self.havemf
339 havemf = self.havemf
342 ui.status(_("checking files\n"))
340 ui.status(_("checking files\n"))
343
341
344 storefiles = set()
342 storefiles = set()
345 for f, f2, size in repo.store.datafiles():
343 for f, f2, size in repo.store.datafiles():
346 if not f:
344 if not f:
347 self._err(None, _("cannot decode filename '%s'") % f2)
345 self._err(None, _("cannot decode filename '%s'") % f2)
348 elif (size > 0 or not revlogv1) and f.startswith('data/'):
346 elif (size > 0 or not revlogv1) and f.startswith('data/'):
349 storefiles.add(_normpath(f))
347 storefiles.add(_normpath(f))
350
348
351 state = {
349 state = {
352 # TODO this assumes revlog storage for changelog.
350 # TODO this assumes revlog storage for changelog.
353 'expectedversion': self.repo.changelog.version & 0xFFFF,
351 'expectedversion': self.repo.changelog.version & 0xFFFF,
354 'skipflags': self.skipflags,
352 'skipflags': self.skipflags,
355 # experimental config: censor.policy
353 # experimental config: censor.policy
356 'erroroncensored': ui.config('censor', 'policy') == 'abort',
354 'erroroncensored': ui.config('censor', 'policy') == 'abort',
357 }
355 }
358
356
359 files = sorted(set(filenodes) | set(filelinkrevs))
357 files = sorted(set(filenodes) | set(filelinkrevs))
360 revisions = 0
358 revisions = 0
361 progress = ui.makeprogress(_('checking'), unit=_('files'),
359 progress = ui.makeprogress(_('checking'), unit=_('files'),
362 total=len(files))
360 total=len(files))
363 for i, f in enumerate(files):
361 for i, f in enumerate(files):
364 progress.update(i, item=f)
362 progress.update(i, item=f)
365 try:
363 try:
366 linkrevs = filelinkrevs[f]
364 linkrevs = filelinkrevs[f]
367 except KeyError:
365 except KeyError:
368 # in manifest but not in changelog
366 # in manifest but not in changelog
369 linkrevs = []
367 linkrevs = []
370
368
371 if linkrevs:
369 if linkrevs:
372 lr = linkrevs[0]
370 lr = linkrevs[0]
373 else:
371 else:
374 lr = None
372 lr = None
375
373
376 try:
374 try:
377 fl = repo.file(f)
375 fl = repo.file(f)
378 except error.StorageError as e:
376 except error.StorageError as e:
379 self._err(lr, _("broken revlog! (%s)") % e, f)
377 self._err(lr, _("broken revlog! (%s)") % e, f)
380 continue
378 continue
381
379
382 for ff in fl.files():
380 for ff in fl.files():
383 try:
381 try:
384 storefiles.remove(ff)
382 storefiles.remove(ff)
385 except KeyError:
383 except KeyError:
386 if self.warnorphanstorefiles:
384 if self.warnorphanstorefiles:
387 self._warn(_(" warning: revlog '%s' not in fncache!") %
385 self._warn(_(" warning: revlog '%s' not in fncache!") %
388 ff)
386 ff)
389 self.fncachewarned = True
387 self.fncachewarned = True
390
388
391 if not len(fl) and (self.havecl or self.havemf):
389 if not len(fl) and (self.havecl or self.havemf):
392 self._err(lr, _("empty or missing %s") % f)
390 self._err(lr, _("empty or missing %s") % f)
393 else:
391 else:
394 # Guard against implementations not setting this.
392 # Guard against implementations not setting this.
395 state['skipread'] = set()
393 state['skipread'] = set()
396 for problem in fl.verifyintegrity(state):
394 for problem in fl.verifyintegrity(state):
397 if problem.node is not None:
395 if problem.node is not None:
398 linkrev = fl.linkrev(fl.rev(problem.node))
396 linkrev = fl.linkrev(fl.rev(problem.node))
399 else:
397 else:
400 linkrev = None
398 linkrev = None
401
399
402 if problem.warning:
400 if problem.warning:
403 self._warn(problem.warning)
401 self._warn(problem.warning)
404 elif problem.error:
402 elif problem.error:
405 self._err(linkrev if linkrev is not None else lr,
403 self._err(linkrev if linkrev is not None else lr,
406 problem.error, f)
404 problem.error, f)
407 else:
405 else:
408 raise error.ProgrammingError(
406 raise error.ProgrammingError(
409 'problem instance does not set warning or error '
407 'problem instance does not set warning or error '
410 'attribute: %s' % problem.msg)
408 'attribute: %s' % problem.msg)
411
409
412 seen = {}
410 seen = {}
413 for i in fl:
411 for i in fl:
414 revisions += 1
412 revisions += 1
415 n = fl.node(i)
413 n = fl.node(i)
416 lr = self.checkentry(fl, i, n, seen, linkrevs, f)
414 lr = self.checkentry(fl, i, n, seen, linkrevs, f)
417 if f in filenodes:
415 if f in filenodes:
418 if havemf and n not in filenodes[f]:
416 if havemf and n not in filenodes[f]:
419 self._err(lr, _("%s not in manifests") % (short(n)), f)
417 self._err(lr, _("%s not in manifests") % (short(n)), f)
420 else:
418 else:
421 del filenodes[f][n]
419 del filenodes[f][n]
422
420
423 if n in state['skipread']:
421 if n in state['skipread']:
424 continue
422 continue
425
423
426 # check renames
424 # check renames
427 try:
425 try:
428 # This requires resolving fulltext (at least on revlogs). We
426 # This requires resolving fulltext (at least on revlogs). We
429 # may want ``verifyintegrity()`` to pass a set of nodes with
427 # may want ``verifyintegrity()`` to pass a set of nodes with
430 # rename metadata as an optimization.
428 # rename metadata as an optimization.
431 rp = fl.renamed(n)
429 rp = fl.renamed(n)
432 if rp:
430 if rp:
433 if lr is not None and ui.verbose:
431 if lr is not None and ui.verbose:
434 ctx = lrugetctx(lr)
432 ctx = lrugetctx(lr)
435 if not any(rp[0] in pctx for pctx in ctx.parents()):
433 if not any(rp[0] in pctx for pctx in ctx.parents()):
436 self._warn(_("warning: copy source of '%s' not"
434 self._warn(_("warning: copy source of '%s' not"
437 " in parents of %s") % (f, ctx))
435 " in parents of %s") % (f, ctx))
438 fl2 = repo.file(rp[0])
436 fl2 = repo.file(rp[0])
439 if not len(fl2):
437 if not len(fl2):
440 self._err(lr,
438 self._err(lr,
441 _("empty or missing copy source revlog "
439 _("empty or missing copy source revlog "
442 "%s:%s") % (rp[0],
440 "%s:%s") % (rp[0],
443 short(rp[1])),
441 short(rp[1])),
444 f)
442 f)
445 elif rp[1] == nullid:
443 elif rp[1] == nullid:
446 ui.note(_("warning: %s@%s: copy source"
444 ui.note(_("warning: %s@%s: copy source"
447 " revision is nullid %s:%s\n")
445 " revision is nullid %s:%s\n")
448 % (f, lr, rp[0], short(rp[1])))
446 % (f, lr, rp[0], short(rp[1])))
449 else:
447 else:
450 fl2.rev(rp[1])
448 fl2.rev(rp[1])
451 except Exception as inst:
449 except Exception as inst:
452 self._exc(lr, _("checking rename of %s") % short(n),
450 self._exc(lr, _("checking rename of %s") % short(n),
453 inst, f)
451 inst, f)
454
452
455 # cross-check
453 # cross-check
456 if f in filenodes:
454 if f in filenodes:
457 fns = [(v, k) for k, v in filenodes[f].iteritems()]
455 fns = [(v, k) for k, v in filenodes[f].iteritems()]
458 for lr, node in sorted(fns):
456 for lr, node in sorted(fns):
459 self._err(lr, _("manifest refers to unknown revision %s") %
457 self._err(lr, _("manifest refers to unknown revision %s") %
460 short(node), f)
458 short(node), f)
461 progress.complete()
459 progress.complete()
462
460
463 if self.warnorphanstorefiles:
461 if self.warnorphanstorefiles:
464 for f in sorted(storefiles):
462 for f in sorted(storefiles):
465 self._warn(_("warning: orphan data file '%s'") % f)
463 self._warn(_("warning: orphan data file '%s'") % f)
466
464
467 return len(files), revisions
465 return len(files), revisions
General Comments 0
You need to be logged in to leave comments. Login now