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