##// END OF EJS Templates
manifest: use read_delta_new_entries in verify too...
marmoute -
r52680:73b9558a default
parent child Browse files
Show More
@@ -1,629 +1,629 b''
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: bytes) -> bytes:
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 273 with cl.reading():
274 274 for i in repo:
275 275 progress.update(i)
276 276 n = cl.node(i)
277 277 self._checkentry(cl, i, n, seen, [i], b"changelog")
278 278
279 279 try:
280 280 changes = cl.read(n)
281 281 if changes[0] != self.repo.nullid:
282 282 mflinkrevs.setdefault(changes[0], []).append(i)
283 283 self.refersmf = True
284 284 for f in changes[3]:
285 285 if match(f):
286 286 filelinkrevs.setdefault(_normpath(f), []).append(i)
287 287 except Exception as inst:
288 288 self.refersmf = True
289 289 self._exc(i, _(b"unpacking changeset %s") % short(n), inst)
290 290 progress.complete()
291 291 return mflinkrevs, filelinkrevs
292 292
293 293 def _verifymanifest(
294 294 self, mflinkrevs, dir=b"", storefiles=None, subdirprogress=None
295 295 ):
296 296 """verify the manifestlog content
297 297
298 298 Inputs:
299 299 - mflinkrevs: a {manifest-node -> [changelog-revisions]} mapping
300 300 - dir: a subdirectory to check (for tree manifest repo)
301 301 - storefiles: set of currently "orphan" files.
302 302 - subdirprogress: a progress object
303 303
304 304 This function checks:
305 305 * all of `_checkrevlog` checks (for all manifest related revlogs)
306 306 * all of `_checkentry` checks (for all manifest related revisions)
307 307 * nodes for subdirectory exists in the sub-directory manifest
308 308 * each manifest entries have a file path
309 309 * each manifest node refered in mflinkrevs exist in the manifest log
310 310
311 311 If tree manifest is in use and a matchers is specified, only the
312 312 sub-directories matching it will be verified.
313 313
314 314 return a two level mapping:
315 315 {"path" -> { filenode -> changelog-revision}}
316 316
317 317 This mapping primarily contains entries for every files in the
318 318 repository. In addition, when tree-manifest is used, it also contains
319 319 sub-directory entries.
320 320
321 321 If a matcher is provided, only matching paths will be included.
322 322 """
323 323 repo = self.repo
324 324 ui = self.ui
325 325 match = self.match
326 326 mfl = self.repo.manifestlog
327 327 mf = mfl.getstorage(dir)
328 328
329 329 if not dir:
330 330 self.ui.status(_(b"checking manifests\n"))
331 331
332 332 filenodes = {}
333 333 subdirnodes = {}
334 334 seen = {}
335 335 label = b"manifest"
336 336 if dir:
337 337 label = dir
338 338 revlogfiles = mf.files()
339 339 storefiles.difference_update(revlogfiles)
340 340 if subdirprogress: # should be true since we're in a subdirectory
341 341 subdirprogress.increment()
342 342 if self.refersmf:
343 343 # Do not check manifest if there are only changelog entries with
344 344 # null manifests.
345 345 self._checkrevlog(mf._revlog, label, 0)
346 346 progress = ui.makeprogress(
347 347 _(b'checking'), unit=_(b'manifests'), total=len(mf)
348 348 )
349 349 for i in mf:
350 350 if not dir:
351 351 progress.update(i)
352 352 n = mf.node(i)
353 353 lr = self._checkentry(mf, i, n, seen, mflinkrevs.get(n, []), label)
354 354 if n in mflinkrevs:
355 355 del mflinkrevs[n]
356 356 elif dir:
357 357 msg = _(b"%s not in parent-directory manifest") % short(n)
358 358 self._err(lr, msg, label)
359 359 else:
360 360 self._err(lr, _(b"%s not in changesets") % short(n), label)
361 361
362 362 try:
363 mfdelta = mfl.get(dir, n).readdelta(shallow=True)
363 mfdelta = mfl.get(dir, n).read_delta_new_entries(shallow=True)
364 364 for f, fn, fl in mfdelta.iterentries():
365 365 if not f:
366 366 self._err(lr, _(b"entry without name in manifest"))
367 367 elif f == b"/dev/null": # ignore this in very old repos
368 368 continue
369 369 fullpath = dir + _normpath(f)
370 370 if fl == b't':
371 371 if not match.visitdir(fullpath):
372 372 continue
373 373 sdn = subdirnodes.setdefault(fullpath + b'/', {})
374 374 sdn.setdefault(fn, []).append(lr)
375 375 else:
376 376 if not match(fullpath):
377 377 continue
378 378 filenodes.setdefault(fullpath, {}).setdefault(fn, lr)
379 379 except Exception as inst:
380 380 self._exc(lr, _(b"reading delta %s") % short(n), inst, label)
381 381 if self._level >= VERIFY_FULL:
382 382 try:
383 383 # Various issues can affect manifest. So we read each full
384 384 # text from storage. This triggers the checks from the core
385 385 # code (eg: hash verification, filename are ordered, etc.)
386 386 mfdelta = mfl.get(dir, n).read()
387 387 except Exception as inst:
388 388 msg = _(b"reading full manifest %s") % short(n)
389 389 self._exc(lr, msg, inst, label)
390 390
391 391 if not dir:
392 392 progress.complete()
393 393
394 394 if self.havemf:
395 395 # since we delete entry in `mflinkrevs` during iteration, any
396 396 # remaining entries are "missing". We need to issue errors for them.
397 397 changesetpairs = [(c, m) for m in mflinkrevs for c in mflinkrevs[m]]
398 398 for c, m in sorted(changesetpairs):
399 399 if dir:
400 400 self._err(c, WARN_PARENT_DIR_UNKNOWN_REV % short(m), label)
401 401 else:
402 402 msg = _(b"changeset refers to unknown revision %s")
403 403 msg %= short(m)
404 404 self._err(c, msg, label)
405 405
406 406 if not dir and subdirnodes:
407 407 self.ui.status(_(b"checking directory manifests\n"))
408 408 storefiles = set()
409 409 subdirs = set()
410 410 revlogv1 = self.revlogv1
411 411 undecodable = []
412 412 for entry in repo.store.data_entries(undecodable=undecodable):
413 413 for file_ in entry.files():
414 414 f = file_.unencoded_path
415 415 size = file_.file_size(repo.store.vfs)
416 416 if (size > 0 or not revlogv1) and f.startswith(b'meta/'):
417 417 storefiles.add(_normpath(f))
418 418 subdirs.add(os.path.dirname(f))
419 419 for f in undecodable:
420 420 self._err(None, _(b"cannot decode filename '%s'") % f)
421 421 subdirprogress = ui.makeprogress(
422 422 _(b'checking'), unit=_(b'manifests'), total=len(subdirs)
423 423 )
424 424
425 425 for subdir, linkrevs in subdirnodes.items():
426 426 subdirfilenodes = self._verifymanifest(
427 427 linkrevs, subdir, storefiles, subdirprogress
428 428 )
429 429 for f, onefilenodes in subdirfilenodes.items():
430 430 filenodes.setdefault(f, {}).update(onefilenodes)
431 431
432 432 if not dir and subdirnodes:
433 433 assert subdirprogress is not None # help pytype
434 434 subdirprogress.complete()
435 435 if self.warnorphanstorefiles:
436 436 for f in sorted(storefiles):
437 437 self._warn(_(b"warning: orphan data file '%s'") % f)
438 438
439 439 return filenodes
440 440
441 441 def _crosscheckfiles(self, filelinkrevs, filenodes):
442 442 repo = self.repo
443 443 ui = self.ui
444 444 ui.status(_(b"crosschecking files in changesets and manifests\n"))
445 445
446 446 total = len(filelinkrevs) + len(filenodes)
447 447 progress = ui.makeprogress(
448 448 _(b'crosschecking'), unit=_(b'files'), total=total
449 449 )
450 450 if self.havemf:
451 451 for f in sorted(filelinkrevs):
452 452 progress.increment()
453 453 if f not in filenodes:
454 454 lr = filelinkrevs[f][0]
455 455 self._err(lr, _(b"in changeset but not in manifest"), f)
456 456
457 457 if self.havecl:
458 458 for f in sorted(filenodes):
459 459 progress.increment()
460 460 if f not in filelinkrevs:
461 461 try:
462 462 fl = repo.file(f)
463 463 lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]])
464 464 except Exception:
465 465 lr = None
466 466 self._err(lr, _(b"in manifest but not in changeset"), f)
467 467
468 468 progress.complete()
469 469
470 470 def _verifyfiles(self, filenodes, filelinkrevs):
471 471 repo = self.repo
472 472 ui = self.ui
473 473 lrugetctx = self.lrugetctx
474 474 revlogv1 = self.revlogv1
475 475 havemf = self.havemf
476 476 ui.status(_(b"checking files\n"))
477 477
478 478 storefiles = set()
479 479 undecodable = []
480 480 for entry in repo.store.data_entries(undecodable=undecodable):
481 481 for file_ in entry.files():
482 482 size = file_.file_size(repo.store.vfs)
483 483 f = file_.unencoded_path
484 484 if (size > 0 or not revlogv1) and f.startswith(b'data/'):
485 485 storefiles.add(_normpath(f))
486 486 for f in undecodable:
487 487 self._err(None, _(b"cannot decode filename '%s'") % f)
488 488
489 489 state = {
490 490 # TODO this assumes revlog storage for changelog.
491 491 b'expectedversion': self.repo.changelog._format_version,
492 492 b'skipflags': self.skipflags,
493 493 # experimental config: censor.policy
494 494 b'erroroncensored': ui.config(b'censor', b'policy') == b'abort',
495 495 }
496 496
497 497 files = sorted(set(filenodes) | set(filelinkrevs))
498 498 revisions = 0
499 499 progress = ui.makeprogress(
500 500 _(b'checking'), unit=_(b'files'), total=len(files)
501 501 )
502 502 for i, f in enumerate(files):
503 503 progress.update(i, item=f)
504 504 try:
505 505 linkrevs = filelinkrevs[f]
506 506 except KeyError:
507 507 # in manifest but not in changelog
508 508 linkrevs = []
509 509
510 510 if linkrevs:
511 511 lr = linkrevs[0]
512 512 else:
513 513 lr = None
514 514
515 515 try:
516 516 fl = repo.file(f)
517 517 except error.StorageError as e:
518 518 self._err(lr, _(b"broken revlog! (%s)") % e, f)
519 519 continue
520 520
521 521 for ff in fl.files():
522 522 try:
523 523 storefiles.remove(ff)
524 524 except KeyError:
525 525 if self.warnorphanstorefiles:
526 526 msg = _(b" warning: revlog '%s' not in fncache!")
527 527 self._warn(msg % ff)
528 528 self.fncachewarned = True
529 529
530 530 if not len(fl) and (self.havecl or self.havemf):
531 531 self._err(lr, _(b"empty or missing %s") % f)
532 532 else:
533 533 # Guard against implementations not setting this.
534 534 state[b'skipread'] = set()
535 535 state[b'safe_renamed'] = set()
536 536
537 537 for problem in fl.verifyintegrity(state):
538 538 if problem.node is not None:
539 539 linkrev = fl.linkrev(fl.rev(problem.node))
540 540 else:
541 541 linkrev = None
542 542
543 543 if problem.warning:
544 544 self._warn(problem.warning)
545 545 elif problem.error:
546 546 linkrev_msg = linkrev if linkrev is not None else lr
547 547 self._err(linkrev_msg, problem.error, f)
548 548 else:
549 549 raise error.ProgrammingError(
550 550 b'problem instance does not set warning or error '
551 551 b'attribute: %s' % problem.msg
552 552 )
553 553
554 554 seen = {}
555 555 for i in fl:
556 556 revisions += 1
557 557 n = fl.node(i)
558 558 lr = self._checkentry(fl, i, n, seen, linkrevs, f)
559 559 if f in filenodes:
560 560 if havemf and n not in filenodes[f]:
561 561 self._err(lr, _(b"%s not in manifests") % (short(n)), f)
562 562 else:
563 563 del filenodes[f][n]
564 564
565 565 if n in state[b'skipread'] and n not in state[b'safe_renamed']:
566 566 continue
567 567
568 568 # check renames
569 569 try:
570 570 # This requires resolving fulltext (at least on revlogs,
571 571 # though not with LFS revisions). We may want
572 572 # ``verifyintegrity()`` to pass a set of nodes with
573 573 # rename metadata as an optimization.
574 574 rp = fl.renamed(n)
575 575 if rp:
576 576 if lr is not None and ui.verbose:
577 577 ctx = lrugetctx(lr)
578 578 if not any(rp[0] in pctx for pctx in ctx.parents()):
579 579 self._warn(WARN_UNKNOWN_COPY_SOURCE % (f, ctx))
580 580 fl2 = repo.file(rp[0])
581 581 if not len(fl2):
582 582 m = _(b"empty or missing copy source revlog %s:%s")
583 583 self._err(lr, m % (rp[0], short(rp[1])), f)
584 584 elif rp[1] == self.repo.nullid:
585 585 msg = WARN_NULLID_COPY_SOURCE
586 586 msg %= (f, lr, rp[0], short(rp[1]))
587 587 ui.note(msg)
588 588 else:
589 589 fl2.rev(rp[1])
590 590 except Exception as inst:
591 591 self._exc(
592 592 lr, _(b"checking rename of %s") % short(n), inst, f
593 593 )
594 594
595 595 # cross-check
596 596 if f in filenodes:
597 597 fns = [(v, k) for k, v in filenodes[f].items()]
598 598 for lr, node in sorted(fns):
599 599 msg = _(b"manifest refers to unknown revision %s")
600 600 self._err(lr, msg % short(node), f)
601 601 progress.complete()
602 602
603 603 if self.warnorphanstorefiles:
604 604 for f in sorted(storefiles):
605 605 self._warn(_(b"warning: orphan data file '%s'") % f)
606 606
607 607 return len(files), revisions
608 608
609 609 def _verify_dirstate(self):
610 610 """Check that the dirstate is consistent with the parent's manifest"""
611 611 repo = self.repo
612 612 ui = self.ui
613 613 ui.status(_(b"checking dirstate\n"))
614 614
615 615 parent1, parent2 = repo.dirstate.parents()
616 616 m1 = repo[parent1].manifest()
617 617 m2 = repo[parent2].manifest()
618 618 dirstate_errors = 0
619 619
620 620 is_narrow = requirements.NARROW_REQUIREMENT in repo.requirements
621 621 narrow_matcher = repo.narrowmatch() if is_narrow else None
622 622
623 623 for err in repo.dirstate.verify(m1, m2, parent1, narrow_matcher):
624 624 ui.error(err)
625 625 dirstate_errors += 1
626 626
627 627 if dirstate_errors:
628 628 self.errors += dirstate_errors
629 629 return dirstate_errors
General Comments 0
You need to be logged in to leave comments. Login now