##// END OF EJS Templates
verify: update comment to say that lfs doesn't need fulltext to check renames...
Matt Harbison -
r44408:a447efd9 default
parent child Browse files
Show More
@@ -1,625 +1,626
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 VERIFY_DEFAULT = 0
25 VERIFY_DEFAULT = 0
26 VERIFY_FULL = 1
26 VERIFY_FULL = 1
27
27
28
28
29 def verify(repo, level=None):
29 def verify(repo, level=None):
30 with repo.lock():
30 with repo.lock():
31 v = verifier(repo, level)
31 v = verifier(repo, level)
32 return v.verify()
32 return v.verify()
33
33
34
34
35 def _normpath(f):
35 def _normpath(f):
36 # under hg < 2.4, convert didn't sanitize paths properly, so a
36 # under hg < 2.4, convert didn't sanitize paths properly, so a
37 # converted repo may contain repeated slashes
37 # converted repo may contain repeated slashes
38 while b'//' in f:
38 while b'//' in f:
39 f = f.replace(b'//', b'/')
39 f = f.replace(b'//', b'/')
40 return f
40 return f
41
41
42
42
43 class verifier(object):
43 class verifier(object):
44 def __init__(self, repo, level=None):
44 def __init__(self, repo, level=None):
45 self.repo = repo.unfiltered()
45 self.repo = repo.unfiltered()
46 self.ui = repo.ui
46 self.ui = repo.ui
47 self.match = repo.narrowmatch()
47 self.match = repo.narrowmatch()
48 if level is None:
48 if level is None:
49 level = VERIFY_DEFAULT
49 level = VERIFY_DEFAULT
50 self._level = level
50 self._level = level
51 self.badrevs = set()
51 self.badrevs = set()
52 self.errors = 0
52 self.errors = 0
53 self.warnings = 0
53 self.warnings = 0
54 self.havecl = len(repo.changelog) > 0
54 self.havecl = len(repo.changelog) > 0
55 self.havemf = len(repo.manifestlog.getstorage(b'')) > 0
55 self.havemf = len(repo.manifestlog.getstorage(b'')) > 0
56 self.revlogv1 = repo.changelog.version != revlog.REVLOGV0
56 self.revlogv1 = repo.changelog.version != revlog.REVLOGV0
57 self.lrugetctx = util.lrucachefunc(repo.__getitem__)
57 self.lrugetctx = util.lrucachefunc(repo.__getitem__)
58 self.refersmf = False
58 self.refersmf = False
59 self.fncachewarned = False
59 self.fncachewarned = False
60 # developer config: verify.skipflags
60 # developer config: verify.skipflags
61 self.skipflags = repo.ui.configint(b'verify', b'skipflags')
61 self.skipflags = repo.ui.configint(b'verify', b'skipflags')
62 self.warnorphanstorefiles = True
62 self.warnorphanstorefiles = True
63
63
64 def _warn(self, msg):
64 def _warn(self, msg):
65 """record a "warning" level issue"""
65 """record a "warning" level issue"""
66 self.ui.warn(msg + b"\n")
66 self.ui.warn(msg + b"\n")
67 self.warnings += 1
67 self.warnings += 1
68
68
69 def _err(self, linkrev, msg, filename=None):
69 def _err(self, linkrev, msg, filename=None):
70 """record a "error" level issue"""
70 """record a "error" level issue"""
71 if linkrev is not None:
71 if linkrev is not None:
72 self.badrevs.add(linkrev)
72 self.badrevs.add(linkrev)
73 linkrev = b"%d" % linkrev
73 linkrev = b"%d" % linkrev
74 else:
74 else:
75 linkrev = b'?'
75 linkrev = b'?'
76 msg = b"%s: %s" % (linkrev, msg)
76 msg = b"%s: %s" % (linkrev, msg)
77 if filename:
77 if filename:
78 msg = b"%s@%s" % (filename, msg)
78 msg = b"%s@%s" % (filename, msg)
79 self.ui.warn(b" " + msg + b"\n")
79 self.ui.warn(b" " + msg + b"\n")
80 self.errors += 1
80 self.errors += 1
81
81
82 def _exc(self, linkrev, msg, inst, filename=None):
82 def _exc(self, linkrev, msg, inst, filename=None):
83 """record exception raised during the verify process"""
83 """record exception raised during the verify process"""
84 fmsg = pycompat.bytestr(inst)
84 fmsg = pycompat.bytestr(inst)
85 if not fmsg:
85 if not fmsg:
86 fmsg = pycompat.byterepr(inst)
86 fmsg = pycompat.byterepr(inst)
87 self._err(linkrev, b"%s: %s" % (msg, fmsg), filename)
87 self._err(linkrev, b"%s: %s" % (msg, fmsg), filename)
88
88
89 def _checkrevlog(self, obj, name, linkrev):
89 def _checkrevlog(self, obj, name, linkrev):
90 """verify high level property of a revlog
90 """verify high level property of a revlog
91
91
92 - revlog is present,
92 - revlog is present,
93 - revlog is non-empty,
93 - revlog is non-empty,
94 - sizes (index and data) are correct,
94 - sizes (index and data) are correct,
95 - revlog's format version is correct.
95 - revlog's format version is correct.
96 """
96 """
97 if not len(obj) and (self.havecl or self.havemf):
97 if not len(obj) and (self.havecl or self.havemf):
98 self._err(linkrev, _(b"empty or missing %s") % name)
98 self._err(linkrev, _(b"empty or missing %s") % name)
99 return
99 return
100
100
101 d = obj.checksize()
101 d = obj.checksize()
102 if d[0]:
102 if d[0]:
103 self._err(None, _(b"data length off by %d bytes") % d[0], name)
103 self._err(None, _(b"data length off by %d bytes") % d[0], name)
104 if d[1]:
104 if d[1]:
105 self._err(None, _(b"index contains %d extra bytes") % d[1], name)
105 self._err(None, _(b"index contains %d extra bytes") % d[1], name)
106
106
107 if obj.version != revlog.REVLOGV0:
107 if obj.version != revlog.REVLOGV0:
108 if not self.revlogv1:
108 if not self.revlogv1:
109 self._warn(_(b"warning: `%s' uses revlog format 1") % name)
109 self._warn(_(b"warning: `%s' uses revlog format 1") % name)
110 elif self.revlogv1:
110 elif self.revlogv1:
111 self._warn(_(b"warning: `%s' uses revlog format 0") % name)
111 self._warn(_(b"warning: `%s' uses revlog format 0") % name)
112
112
113 def _checkentry(self, obj, i, node, seen, linkrevs, f):
113 def _checkentry(self, obj, i, node, seen, linkrevs, f):
114 """verify a single revlog entry
114 """verify a single revlog entry
115
115
116 arguments are:
116 arguments are:
117 - obj: the source revlog
117 - obj: the source revlog
118 - i: the revision number
118 - i: the revision number
119 - node: the revision node id
119 - node: the revision node id
120 - seen: nodes previously seen for this revlog
120 - seen: nodes previously seen for this revlog
121 - linkrevs: [changelog-revisions] introducing "node"
121 - linkrevs: [changelog-revisions] introducing "node"
122 - f: string label ("changelog", "manifest", or filename)
122 - f: string label ("changelog", "manifest", or filename)
123
123
124 Performs the following checks:
124 Performs the following checks:
125 - linkrev points to an existing changelog revision,
125 - linkrev points to an existing changelog revision,
126 - linkrev points to a changelog revision that introduces this revision,
126 - linkrev points to a changelog revision that introduces this revision,
127 - linkrev points to the lowest of these changesets,
127 - linkrev points to the lowest of these changesets,
128 - both parents exist in the revlog,
128 - both parents exist in the revlog,
129 - the revision is not duplicated.
129 - the revision is not duplicated.
130
130
131 Return the linkrev of the revision (or None for changelog's revisions).
131 Return the linkrev of the revision (or None for changelog's revisions).
132 """
132 """
133 lr = obj.linkrev(obj.rev(node))
133 lr = obj.linkrev(obj.rev(node))
134 if lr < 0 or (self.havecl and lr not in linkrevs):
134 if lr < 0 or (self.havecl and lr not in linkrevs):
135 if lr < 0 or lr >= len(self.repo.changelog):
135 if lr < 0 or lr >= len(self.repo.changelog):
136 msg = _(b"rev %d points to nonexistent changeset %d")
136 msg = _(b"rev %d points to nonexistent changeset %d")
137 else:
137 else:
138 msg = _(b"rev %d points to unexpected changeset %d")
138 msg = _(b"rev %d points to unexpected changeset %d")
139 self._err(None, msg % (i, lr), f)
139 self._err(None, msg % (i, lr), f)
140 if linkrevs:
140 if linkrevs:
141 if f and len(linkrevs) > 1:
141 if f and len(linkrevs) > 1:
142 try:
142 try:
143 # attempt to filter down to real linkrevs
143 # attempt to filter down to real linkrevs
144 linkrevs = [
144 linkrevs = [
145 l
145 l
146 for l in linkrevs
146 for l in linkrevs
147 if self.lrugetctx(l)[f].filenode() == node
147 if self.lrugetctx(l)[f].filenode() == node
148 ]
148 ]
149 except Exception:
149 except Exception:
150 pass
150 pass
151 self._warn(
151 self._warn(
152 _(b" (expected %s)")
152 _(b" (expected %s)")
153 % b" ".join(map(pycompat.bytestr, linkrevs))
153 % b" ".join(map(pycompat.bytestr, linkrevs))
154 )
154 )
155 lr = None # can't be trusted
155 lr = None # can't be trusted
156
156
157 try:
157 try:
158 p1, p2 = obj.parents(node)
158 p1, p2 = obj.parents(node)
159 if p1 not in seen and p1 != nullid:
159 if p1 not in seen and p1 != nullid:
160 self._err(
160 self._err(
161 lr,
161 lr,
162 _(b"unknown parent 1 %s of %s") % (short(p1), short(node)),
162 _(b"unknown parent 1 %s of %s") % (short(p1), short(node)),
163 f,
163 f,
164 )
164 )
165 if p2 not in seen and p2 != nullid:
165 if p2 not in seen and p2 != nullid:
166 self._err(
166 self._err(
167 lr,
167 lr,
168 _(b"unknown parent 2 %s of %s") % (short(p2), short(node)),
168 _(b"unknown parent 2 %s of %s") % (short(p2), short(node)),
169 f,
169 f,
170 )
170 )
171 except Exception as inst:
171 except Exception as inst:
172 self._exc(lr, _(b"checking parents of %s") % short(node), inst, f)
172 self._exc(lr, _(b"checking parents of %s") % short(node), inst, f)
173
173
174 if node in seen:
174 if node in seen:
175 self._err(lr, _(b"duplicate revision %d (%d)") % (i, seen[node]), f)
175 self._err(lr, _(b"duplicate revision %d (%d)") % (i, seen[node]), f)
176 seen[node] = i
176 seen[node] = i
177 return lr
177 return lr
178
178
179 def verify(self):
179 def verify(self):
180 """verify the content of the Mercurial repository
180 """verify the content of the Mercurial repository
181
181
182 This method run all verifications, displaying issues as they are found.
182 This method run all verifications, displaying issues as they are found.
183
183
184 return 1 if any error have been encountered, 0 otherwise."""
184 return 1 if any error have been encountered, 0 otherwise."""
185 # initial validation and generic report
185 # initial validation and generic report
186 repo = self.repo
186 repo = self.repo
187 ui = repo.ui
187 ui = repo.ui
188 if not repo.url().startswith(b'file:'):
188 if not repo.url().startswith(b'file:'):
189 raise error.Abort(_(b"cannot verify bundle or remote repos"))
189 raise error.Abort(_(b"cannot verify bundle or remote repos"))
190
190
191 if os.path.exists(repo.sjoin(b"journal")):
191 if os.path.exists(repo.sjoin(b"journal")):
192 ui.warn(_(b"abandoned transaction found - run hg recover\n"))
192 ui.warn(_(b"abandoned transaction found - run hg recover\n"))
193
193
194 if ui.verbose or not self.revlogv1:
194 if ui.verbose or not self.revlogv1:
195 ui.status(
195 ui.status(
196 _(b"repository uses revlog format %d\n")
196 _(b"repository uses revlog format %d\n")
197 % (self.revlogv1 and 1 or 0)
197 % (self.revlogv1 and 1 or 0)
198 )
198 )
199
199
200 # data verification
200 # data verification
201 mflinkrevs, filelinkrevs = self._verifychangelog()
201 mflinkrevs, filelinkrevs = self._verifychangelog()
202 filenodes = self._verifymanifest(mflinkrevs)
202 filenodes = self._verifymanifest(mflinkrevs)
203 del mflinkrevs
203 del mflinkrevs
204 self._crosscheckfiles(filelinkrevs, filenodes)
204 self._crosscheckfiles(filelinkrevs, filenodes)
205 totalfiles, filerevisions = self._verifyfiles(filenodes, filelinkrevs)
205 totalfiles, filerevisions = self._verifyfiles(filenodes, filelinkrevs)
206
206
207 # final report
207 # final report
208 ui.status(
208 ui.status(
209 _(b"checked %d changesets with %d changes to %d files\n")
209 _(b"checked %d changesets with %d changes to %d files\n")
210 % (len(repo.changelog), filerevisions, totalfiles)
210 % (len(repo.changelog), filerevisions, totalfiles)
211 )
211 )
212 if self.warnings:
212 if self.warnings:
213 ui.warn(_(b"%d warnings encountered!\n") % self.warnings)
213 ui.warn(_(b"%d warnings encountered!\n") % self.warnings)
214 if self.fncachewarned:
214 if self.fncachewarned:
215 ui.warn(
215 ui.warn(
216 _(
216 _(
217 b'hint: run "hg debugrebuildfncache" to recover from '
217 b'hint: run "hg debugrebuildfncache" to recover from '
218 b'corrupt fncache\n'
218 b'corrupt fncache\n'
219 )
219 )
220 )
220 )
221 if self.errors:
221 if self.errors:
222 ui.warn(_(b"%d integrity errors encountered!\n") % self.errors)
222 ui.warn(_(b"%d integrity errors encountered!\n") % self.errors)
223 if self.badrevs:
223 if self.badrevs:
224 ui.warn(
224 ui.warn(
225 _(b"(first damaged changeset appears to be %d)\n")
225 _(b"(first damaged changeset appears to be %d)\n")
226 % min(self.badrevs)
226 % min(self.badrevs)
227 )
227 )
228 return 1
228 return 1
229 return 0
229 return 0
230
230
231 def _verifychangelog(self):
231 def _verifychangelog(self):
232 """verify the changelog of a repository
232 """verify the changelog of a repository
233
233
234 The following checks are performed:
234 The following checks are performed:
235 - all of `_checkrevlog` checks,
235 - all of `_checkrevlog` checks,
236 - all of `_checkentry` checks (for each revisions),
236 - all of `_checkentry` checks (for each revisions),
237 - each revision can be read.
237 - each revision can be read.
238
238
239 The function returns some of the data observed in the changesets as a
239 The function returns some of the data observed in the changesets as a
240 (mflinkrevs, filelinkrevs) tuples:
240 (mflinkrevs, filelinkrevs) tuples:
241 - mflinkrevs: is a { manifest-node -> [changelog-rev] } mapping
241 - mflinkrevs: is a { manifest-node -> [changelog-rev] } mapping
242 - filelinkrevs: is a { file-path -> [changelog-rev] } mapping
242 - filelinkrevs: is a { file-path -> [changelog-rev] } mapping
243
243
244 If a matcher was specified, filelinkrevs will only contains matched
244 If a matcher was specified, filelinkrevs will only contains matched
245 files.
245 files.
246 """
246 """
247 ui = self.ui
247 ui = self.ui
248 repo = self.repo
248 repo = self.repo
249 match = self.match
249 match = self.match
250 cl = repo.changelog
250 cl = repo.changelog
251
251
252 ui.status(_(b"checking changesets\n"))
252 ui.status(_(b"checking changesets\n"))
253 mflinkrevs = {}
253 mflinkrevs = {}
254 filelinkrevs = {}
254 filelinkrevs = {}
255 seen = {}
255 seen = {}
256 self._checkrevlog(cl, b"changelog", 0)
256 self._checkrevlog(cl, b"changelog", 0)
257 progress = ui.makeprogress(
257 progress = ui.makeprogress(
258 _(b'checking'), unit=_(b'changesets'), total=len(repo)
258 _(b'checking'), unit=_(b'changesets'), total=len(repo)
259 )
259 )
260 for i in repo:
260 for i in repo:
261 progress.update(i)
261 progress.update(i)
262 n = cl.node(i)
262 n = cl.node(i)
263 self._checkentry(cl, i, n, seen, [i], b"changelog")
263 self._checkentry(cl, i, n, seen, [i], b"changelog")
264
264
265 try:
265 try:
266 changes = cl.read(n)
266 changes = cl.read(n)
267 if changes[0] != nullid:
267 if changes[0] != nullid:
268 mflinkrevs.setdefault(changes[0], []).append(i)
268 mflinkrevs.setdefault(changes[0], []).append(i)
269 self.refersmf = True
269 self.refersmf = True
270 for f in changes[3]:
270 for f in changes[3]:
271 if match(f):
271 if match(f):
272 filelinkrevs.setdefault(_normpath(f), []).append(i)
272 filelinkrevs.setdefault(_normpath(f), []).append(i)
273 except Exception as inst:
273 except Exception as inst:
274 self.refersmf = True
274 self.refersmf = True
275 self._exc(i, _(b"unpacking changeset %s") % short(n), inst)
275 self._exc(i, _(b"unpacking changeset %s") % short(n), inst)
276 progress.complete()
276 progress.complete()
277 return mflinkrevs, filelinkrevs
277 return mflinkrevs, filelinkrevs
278
278
279 def _verifymanifest(
279 def _verifymanifest(
280 self, mflinkrevs, dir=b"", storefiles=None, subdirprogress=None
280 self, mflinkrevs, dir=b"", storefiles=None, subdirprogress=None
281 ):
281 ):
282 """verify the manifestlog content
282 """verify the manifestlog content
283
283
284 Inputs:
284 Inputs:
285 - mflinkrevs: a {manifest-node -> [changelog-revisions]} mapping
285 - mflinkrevs: a {manifest-node -> [changelog-revisions]} mapping
286 - dir: a subdirectory to check (for tree manifest repo)
286 - dir: a subdirectory to check (for tree manifest repo)
287 - storefiles: set of currently "orphan" files.
287 - storefiles: set of currently "orphan" files.
288 - subdirprogress: a progress object
288 - subdirprogress: a progress object
289
289
290 This function checks:
290 This function checks:
291 * all of `_checkrevlog` checks (for all manifest related revlogs)
291 * all of `_checkrevlog` checks (for all manifest related revlogs)
292 * all of `_checkentry` checks (for all manifest related revisions)
292 * all of `_checkentry` checks (for all manifest related revisions)
293 * nodes for subdirectory exists in the sub-directory manifest
293 * nodes for subdirectory exists in the sub-directory manifest
294 * each manifest entries have a file path
294 * each manifest entries have a file path
295 * each manifest node refered in mflinkrevs exist in the manifest log
295 * each manifest node refered in mflinkrevs exist in the manifest log
296
296
297 If tree manifest is in use and a matchers is specified, only the
297 If tree manifest is in use and a matchers is specified, only the
298 sub-directories matching it will be verified.
298 sub-directories matching it will be verified.
299
299
300 return a two level mapping:
300 return a two level mapping:
301 {"path" -> { filenode -> changelog-revision}}
301 {"path" -> { filenode -> changelog-revision}}
302
302
303 This mapping primarily contains entries for every files in the
303 This mapping primarily contains entries for every files in the
304 repository. In addition, when tree-manifest is used, it also contains
304 repository. In addition, when tree-manifest is used, it also contains
305 sub-directory entries.
305 sub-directory entries.
306
306
307 If a matcher is provided, only matching paths will be included.
307 If a matcher is provided, only matching paths will be included.
308 """
308 """
309 repo = self.repo
309 repo = self.repo
310 ui = self.ui
310 ui = self.ui
311 match = self.match
311 match = self.match
312 mfl = self.repo.manifestlog
312 mfl = self.repo.manifestlog
313 mf = mfl.getstorage(dir)
313 mf = mfl.getstorage(dir)
314
314
315 if not dir:
315 if not dir:
316 self.ui.status(_(b"checking manifests\n"))
316 self.ui.status(_(b"checking manifests\n"))
317
317
318 filenodes = {}
318 filenodes = {}
319 subdirnodes = {}
319 subdirnodes = {}
320 seen = {}
320 seen = {}
321 label = b"manifest"
321 label = b"manifest"
322 if dir:
322 if dir:
323 label = dir
323 label = dir
324 revlogfiles = mf.files()
324 revlogfiles = mf.files()
325 storefiles.difference_update(revlogfiles)
325 storefiles.difference_update(revlogfiles)
326 if subdirprogress: # should be true since we're in a subdirectory
326 if subdirprogress: # should be true since we're in a subdirectory
327 subdirprogress.increment()
327 subdirprogress.increment()
328 if self.refersmf:
328 if self.refersmf:
329 # Do not check manifest if there are only changelog entries with
329 # Do not check manifest if there are only changelog entries with
330 # null manifests.
330 # null manifests.
331 self._checkrevlog(mf, label, 0)
331 self._checkrevlog(mf, label, 0)
332 progress = ui.makeprogress(
332 progress = ui.makeprogress(
333 _(b'checking'), unit=_(b'manifests'), total=len(mf)
333 _(b'checking'), unit=_(b'manifests'), total=len(mf)
334 )
334 )
335 for i in mf:
335 for i in mf:
336 if not dir:
336 if not dir:
337 progress.update(i)
337 progress.update(i)
338 n = mf.node(i)
338 n = mf.node(i)
339 lr = self._checkentry(mf, i, n, seen, mflinkrevs.get(n, []), label)
339 lr = self._checkentry(mf, i, n, seen, mflinkrevs.get(n, []), label)
340 if n in mflinkrevs:
340 if n in mflinkrevs:
341 del mflinkrevs[n]
341 del mflinkrevs[n]
342 elif dir:
342 elif dir:
343 self._err(
343 self._err(
344 lr,
344 lr,
345 _(b"%s not in parent-directory manifest") % short(n),
345 _(b"%s not in parent-directory manifest") % short(n),
346 label,
346 label,
347 )
347 )
348 else:
348 else:
349 self._err(lr, _(b"%s not in changesets") % short(n), label)
349 self._err(lr, _(b"%s not in changesets") % short(n), label)
350
350
351 try:
351 try:
352 mfdelta = mfl.get(dir, n).readdelta(shallow=True)
352 mfdelta = mfl.get(dir, n).readdelta(shallow=True)
353 for f, fn, fl in mfdelta.iterentries():
353 for f, fn, fl in mfdelta.iterentries():
354 if not f:
354 if not f:
355 self._err(lr, _(b"entry without name in manifest"))
355 self._err(lr, _(b"entry without name in manifest"))
356 elif f == b"/dev/null": # ignore this in very old repos
356 elif f == b"/dev/null": # ignore this in very old repos
357 continue
357 continue
358 fullpath = dir + _normpath(f)
358 fullpath = dir + _normpath(f)
359 if fl == b't':
359 if fl == b't':
360 if not match.visitdir(fullpath):
360 if not match.visitdir(fullpath):
361 continue
361 continue
362 subdirnodes.setdefault(fullpath + b'/', {}).setdefault(
362 subdirnodes.setdefault(fullpath + b'/', {}).setdefault(
363 fn, []
363 fn, []
364 ).append(lr)
364 ).append(lr)
365 else:
365 else:
366 if not match(fullpath):
366 if not match(fullpath):
367 continue
367 continue
368 filenodes.setdefault(fullpath, {}).setdefault(fn, lr)
368 filenodes.setdefault(fullpath, {}).setdefault(fn, lr)
369 except Exception as inst:
369 except Exception as inst:
370 self._exc(lr, _(b"reading delta %s") % short(n), inst, label)
370 self._exc(lr, _(b"reading delta %s") % short(n), inst, label)
371 if self._level >= VERIFY_FULL:
371 if self._level >= VERIFY_FULL:
372 try:
372 try:
373 # Various issues can affect manifest. So we read each full
373 # Various issues can affect manifest. So we read each full
374 # text from storage. This triggers the checks from the core
374 # text from storage. This triggers the checks from the core
375 # code (eg: hash verification, filename are ordered, etc.)
375 # code (eg: hash verification, filename are ordered, etc.)
376 mfdelta = mfl.get(dir, n).read()
376 mfdelta = mfl.get(dir, n).read()
377 except Exception as inst:
377 except Exception as inst:
378 self._exc(
378 self._exc(
379 lr,
379 lr,
380 _(b"reading full manifest %s") % short(n),
380 _(b"reading full manifest %s") % short(n),
381 inst,
381 inst,
382 label,
382 label,
383 )
383 )
384
384
385 if not dir:
385 if not dir:
386 progress.complete()
386 progress.complete()
387
387
388 if self.havemf:
388 if self.havemf:
389 # since we delete entry in `mflinkrevs` during iteration, any
389 # since we delete entry in `mflinkrevs` during iteration, any
390 # remaining entries are "missing". We need to issue errors for them.
390 # remaining entries are "missing". We need to issue errors for them.
391 changesetpairs = [(c, m) for m in mflinkrevs for c in mflinkrevs[m]]
391 changesetpairs = [(c, m) for m in mflinkrevs for c in mflinkrevs[m]]
392 for c, m in sorted(changesetpairs):
392 for c, m in sorted(changesetpairs):
393 if dir:
393 if dir:
394 self._err(
394 self._err(
395 c,
395 c,
396 _(
396 _(
397 b"parent-directory manifest refers to unknown"
397 b"parent-directory manifest refers to unknown"
398 b" revision %s"
398 b" revision %s"
399 )
399 )
400 % short(m),
400 % short(m),
401 label,
401 label,
402 )
402 )
403 else:
403 else:
404 self._err(
404 self._err(
405 c,
405 c,
406 _(b"changeset refers to unknown revision %s")
406 _(b"changeset refers to unknown revision %s")
407 % short(m),
407 % short(m),
408 label,
408 label,
409 )
409 )
410
410
411 if not dir and subdirnodes:
411 if not dir and subdirnodes:
412 self.ui.status(_(b"checking directory manifests\n"))
412 self.ui.status(_(b"checking directory manifests\n"))
413 storefiles = set()
413 storefiles = set()
414 subdirs = set()
414 subdirs = set()
415 revlogv1 = self.revlogv1
415 revlogv1 = self.revlogv1
416 for f, f2, size in repo.store.datafiles():
416 for f, f2, size in repo.store.datafiles():
417 if not f:
417 if not f:
418 self._err(None, _(b"cannot decode filename '%s'") % f2)
418 self._err(None, _(b"cannot decode filename '%s'") % f2)
419 elif (size > 0 or not revlogv1) and f.startswith(b'meta/'):
419 elif (size > 0 or not revlogv1) and f.startswith(b'meta/'):
420 storefiles.add(_normpath(f))
420 storefiles.add(_normpath(f))
421 subdirs.add(os.path.dirname(f))
421 subdirs.add(os.path.dirname(f))
422 subdirprogress = ui.makeprogress(
422 subdirprogress = ui.makeprogress(
423 _(b'checking'), unit=_(b'manifests'), total=len(subdirs)
423 _(b'checking'), unit=_(b'manifests'), total=len(subdirs)
424 )
424 )
425
425
426 for subdir, linkrevs in pycompat.iteritems(subdirnodes):
426 for subdir, linkrevs in pycompat.iteritems(subdirnodes):
427 subdirfilenodes = self._verifymanifest(
427 subdirfilenodes = self._verifymanifest(
428 linkrevs, subdir, storefiles, subdirprogress
428 linkrevs, subdir, storefiles, subdirprogress
429 )
429 )
430 for f, onefilenodes in pycompat.iteritems(subdirfilenodes):
430 for f, onefilenodes in pycompat.iteritems(subdirfilenodes):
431 filenodes.setdefault(f, {}).update(onefilenodes)
431 filenodes.setdefault(f, {}).update(onefilenodes)
432
432
433 if not dir and subdirnodes:
433 if not dir and subdirnodes:
434 subdirprogress.complete()
434 subdirprogress.complete()
435 if self.warnorphanstorefiles:
435 if self.warnorphanstorefiles:
436 for f in sorted(storefiles):
436 for f in sorted(storefiles):
437 self._warn(_(b"warning: orphan data file '%s'") % f)
437 self._warn(_(b"warning: orphan data file '%s'") % f)
438
438
439 return filenodes
439 return filenodes
440
440
441 def _crosscheckfiles(self, filelinkrevs, filenodes):
441 def _crosscheckfiles(self, filelinkrevs, filenodes):
442 repo = self.repo
442 repo = self.repo
443 ui = self.ui
443 ui = self.ui
444 ui.status(_(b"crosschecking files in changesets and manifests\n"))
444 ui.status(_(b"crosschecking files in changesets and manifests\n"))
445
445
446 total = len(filelinkrevs) + len(filenodes)
446 total = len(filelinkrevs) + len(filenodes)
447 progress = ui.makeprogress(
447 progress = ui.makeprogress(
448 _(b'crosschecking'), unit=_(b'files'), total=total
448 _(b'crosschecking'), unit=_(b'files'), total=total
449 )
449 )
450 if self.havemf:
450 if self.havemf:
451 for f in sorted(filelinkrevs):
451 for f in sorted(filelinkrevs):
452 progress.increment()
452 progress.increment()
453 if f not in filenodes:
453 if f not in filenodes:
454 lr = filelinkrevs[f][0]
454 lr = filelinkrevs[f][0]
455 self._err(lr, _(b"in changeset but not in manifest"), f)
455 self._err(lr, _(b"in changeset but not in manifest"), f)
456
456
457 if self.havecl:
457 if self.havecl:
458 for f in sorted(filenodes):
458 for f in sorted(filenodes):
459 progress.increment()
459 progress.increment()
460 if f not in filelinkrevs:
460 if f not in filelinkrevs:
461 try:
461 try:
462 fl = repo.file(f)
462 fl = repo.file(f)
463 lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]])
463 lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]])
464 except Exception:
464 except Exception:
465 lr = None
465 lr = None
466 self._err(lr, _(b"in manifest but not in changeset"), f)
466 self._err(lr, _(b"in manifest but not in changeset"), f)
467
467
468 progress.complete()
468 progress.complete()
469
469
470 def _verifyfiles(self, filenodes, filelinkrevs):
470 def _verifyfiles(self, filenodes, filelinkrevs):
471 repo = self.repo
471 repo = self.repo
472 ui = self.ui
472 ui = self.ui
473 lrugetctx = self.lrugetctx
473 lrugetctx = self.lrugetctx
474 revlogv1 = self.revlogv1
474 revlogv1 = self.revlogv1
475 havemf = self.havemf
475 havemf = self.havemf
476 ui.status(_(b"checking files\n"))
476 ui.status(_(b"checking files\n"))
477
477
478 storefiles = set()
478 storefiles = set()
479 for f, f2, size in repo.store.datafiles():
479 for f, f2, size in repo.store.datafiles():
480 if not f:
480 if not f:
481 self._err(None, _(b"cannot decode filename '%s'") % f2)
481 self._err(None, _(b"cannot decode filename '%s'") % f2)
482 elif (size > 0 or not revlogv1) and f.startswith(b'data/'):
482 elif (size > 0 or not revlogv1) and f.startswith(b'data/'):
483 storefiles.add(_normpath(f))
483 storefiles.add(_normpath(f))
484
484
485 state = {
485 state = {
486 # TODO this assumes revlog storage for changelog.
486 # TODO this assumes revlog storage for changelog.
487 b'expectedversion': self.repo.changelog.version & 0xFFFF,
487 b'expectedversion': self.repo.changelog.version & 0xFFFF,
488 b'skipflags': self.skipflags,
488 b'skipflags': self.skipflags,
489 # experimental config: censor.policy
489 # experimental config: censor.policy
490 b'erroroncensored': ui.config(b'censor', b'policy') == b'abort',
490 b'erroroncensored': ui.config(b'censor', b'policy') == b'abort',
491 }
491 }
492
492
493 files = sorted(set(filenodes) | set(filelinkrevs))
493 files = sorted(set(filenodes) | set(filelinkrevs))
494 revisions = 0
494 revisions = 0
495 progress = ui.makeprogress(
495 progress = ui.makeprogress(
496 _(b'checking'), unit=_(b'files'), total=len(files)
496 _(b'checking'), unit=_(b'files'), total=len(files)
497 )
497 )
498 for i, f in enumerate(files):
498 for i, f in enumerate(files):
499 progress.update(i, item=f)
499 progress.update(i, item=f)
500 try:
500 try:
501 linkrevs = filelinkrevs[f]
501 linkrevs = filelinkrevs[f]
502 except KeyError:
502 except KeyError:
503 # in manifest but not in changelog
503 # in manifest but not in changelog
504 linkrevs = []
504 linkrevs = []
505
505
506 if linkrevs:
506 if linkrevs:
507 lr = linkrevs[0]
507 lr = linkrevs[0]
508 else:
508 else:
509 lr = None
509 lr = None
510
510
511 try:
511 try:
512 fl = repo.file(f)
512 fl = repo.file(f)
513 except error.StorageError as e:
513 except error.StorageError as e:
514 self._err(lr, _(b"broken revlog! (%s)") % e, f)
514 self._err(lr, _(b"broken revlog! (%s)") % e, f)
515 continue
515 continue
516
516
517 for ff in fl.files():
517 for ff in fl.files():
518 try:
518 try:
519 storefiles.remove(ff)
519 storefiles.remove(ff)
520 except KeyError:
520 except KeyError:
521 if self.warnorphanstorefiles:
521 if self.warnorphanstorefiles:
522 self._warn(
522 self._warn(
523 _(b" warning: revlog '%s' not in fncache!") % ff
523 _(b" warning: revlog '%s' not in fncache!") % ff
524 )
524 )
525 self.fncachewarned = True
525 self.fncachewarned = True
526
526
527 if not len(fl) and (self.havecl or self.havemf):
527 if not len(fl) and (self.havecl or self.havemf):
528 self._err(lr, _(b"empty or missing %s") % f)
528 self._err(lr, _(b"empty or missing %s") % f)
529 else:
529 else:
530 # Guard against implementations not setting this.
530 # Guard against implementations not setting this.
531 state[b'skipread'] = set()
531 state[b'skipread'] = set()
532 for problem in fl.verifyintegrity(state):
532 for problem in fl.verifyintegrity(state):
533 if problem.node is not None:
533 if problem.node is not None:
534 linkrev = fl.linkrev(fl.rev(problem.node))
534 linkrev = fl.linkrev(fl.rev(problem.node))
535 else:
535 else:
536 linkrev = None
536 linkrev = None
537
537
538 if problem.warning:
538 if problem.warning:
539 self._warn(problem.warning)
539 self._warn(problem.warning)
540 elif problem.error:
540 elif problem.error:
541 self._err(
541 self._err(
542 linkrev if linkrev is not None else lr,
542 linkrev if linkrev is not None else lr,
543 problem.error,
543 problem.error,
544 f,
544 f,
545 )
545 )
546 else:
546 else:
547 raise error.ProgrammingError(
547 raise error.ProgrammingError(
548 b'problem instance does not set warning or error '
548 b'problem instance does not set warning or error '
549 b'attribute: %s' % problem.msg
549 b'attribute: %s' % problem.msg
550 )
550 )
551
551
552 seen = {}
552 seen = {}
553 for i in fl:
553 for i in fl:
554 revisions += 1
554 revisions += 1
555 n = fl.node(i)
555 n = fl.node(i)
556 lr = self._checkentry(fl, i, n, seen, linkrevs, f)
556 lr = self._checkentry(fl, i, n, seen, linkrevs, f)
557 if f in filenodes:
557 if f in filenodes:
558 if havemf and n not in filenodes[f]:
558 if havemf and n not in filenodes[f]:
559 self._err(lr, _(b"%s not in manifests") % (short(n)), f)
559 self._err(lr, _(b"%s not in manifests") % (short(n)), f)
560 else:
560 else:
561 del filenodes[f][n]
561 del filenodes[f][n]
562
562
563 if n in state[b'skipread']:
563 if n in state[b'skipread']:
564 continue
564 continue
565
565
566 # check renames
566 # check renames
567 try:
567 try:
568 # This requires resolving fulltext (at least on revlogs). We
568 # This requires resolving fulltext (at least on revlogs,
569 # may want ``verifyintegrity()`` to pass a set of nodes with
569 # though not with LFS revisions). We may want
570 # ``verifyintegrity()`` to pass a set of nodes with
570 # rename metadata as an optimization.
571 # rename metadata as an optimization.
571 rp = fl.renamed(n)
572 rp = fl.renamed(n)
572 if rp:
573 if rp:
573 if lr is not None and ui.verbose:
574 if lr is not None and ui.verbose:
574 ctx = lrugetctx(lr)
575 ctx = lrugetctx(lr)
575 if not any(rp[0] in pctx for pctx in ctx.parents()):
576 if not any(rp[0] in pctx for pctx in ctx.parents()):
576 self._warn(
577 self._warn(
577 _(
578 _(
578 b"warning: copy source of '%s' not"
579 b"warning: copy source of '%s' not"
579 b" in parents of %s"
580 b" in parents of %s"
580 )
581 )
581 % (f, ctx)
582 % (f, ctx)
582 )
583 )
583 fl2 = repo.file(rp[0])
584 fl2 = repo.file(rp[0])
584 if not len(fl2):
585 if not len(fl2):
585 self._err(
586 self._err(
586 lr,
587 lr,
587 _(
588 _(
588 b"empty or missing copy source revlog "
589 b"empty or missing copy source revlog "
589 b"%s:%s"
590 b"%s:%s"
590 )
591 )
591 % (rp[0], short(rp[1])),
592 % (rp[0], short(rp[1])),
592 f,
593 f,
593 )
594 )
594 elif rp[1] == nullid:
595 elif rp[1] == nullid:
595 ui.note(
596 ui.note(
596 _(
597 _(
597 b"warning: %s@%s: copy source"
598 b"warning: %s@%s: copy source"
598 b" revision is nullid %s:%s\n"
599 b" revision is nullid %s:%s\n"
599 )
600 )
600 % (f, lr, rp[0], short(rp[1]))
601 % (f, lr, rp[0], short(rp[1]))
601 )
602 )
602 else:
603 else:
603 fl2.rev(rp[1])
604 fl2.rev(rp[1])
604 except Exception as inst:
605 except Exception as inst:
605 self._exc(
606 self._exc(
606 lr, _(b"checking rename of %s") % short(n), inst, f
607 lr, _(b"checking rename of %s") % short(n), inst, f
607 )
608 )
608
609
609 # cross-check
610 # cross-check
610 if f in filenodes:
611 if f in filenodes:
611 fns = [(v, k) for k, v in pycompat.iteritems(filenodes[f])]
612 fns = [(v, k) for k, v in pycompat.iteritems(filenodes[f])]
612 for lr, node in sorted(fns):
613 for lr, node in sorted(fns):
613 self._err(
614 self._err(
614 lr,
615 lr,
615 _(b"manifest refers to unknown revision %s")
616 _(b"manifest refers to unknown revision %s")
616 % short(node),
617 % short(node),
617 f,
618 f,
618 )
619 )
619 progress.complete()
620 progress.complete()
620
621
621 if self.warnorphanstorefiles:
622 if self.warnorphanstorefiles:
622 for f in sorted(storefiles):
623 for f in sorted(storefiles):
623 self._warn(_(b"warning: orphan data file '%s'") % f)
624 self._warn(_(b"warning: orphan data file '%s'") % f)
624
625
625 return len(files), revisions
626 return len(files), revisions
General Comments 0
You need to be logged in to leave comments. Login now