diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py --- a/mercurial/debugcommands.py +++ b/mercurial/debugcommands.py @@ -2987,10 +2987,22 @@ dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles) -@command(b'debugrebuildfncache', [], b'') -def debugrebuildfncache(ui, repo): +@command( + b'debugrebuildfncache', + [ + ( + b'', + b'only-data', + False, + _(b'only look for wrong .d files (much faster)'), + ) + ], + b'', +) +def debugrebuildfncache(ui, repo, **opts): """rebuild the fncache file""" - repair.rebuildfncache(ui, repo) + opts = pycompat.byteskwargs(opts) + repair.rebuildfncache(ui, repo, opts.get(b"only_data")) @command( diff --git a/mercurial/repair.py b/mercurial/repair.py --- a/mercurial/repair.py +++ b/mercurial/repair.py @@ -441,7 +441,7 @@ yield repo.manifestlog.getstorage(dir) -def rebuildfncache(ui, repo): +def rebuildfncache(ui, repo, only_data=False): """Rebuilds the fncache file from repo history. Missing entries will be added. Extra entries will be removed. @@ -465,28 +465,40 @@ newentries = set() seenfiles = set() - progress = ui.makeprogress( - _(b'rebuilding'), unit=_(b'changesets'), total=len(repo) - ) - for rev in repo: - progress.update(rev) + if only_data: + # Trust the listing of .i from the fncache, but not the .d. This is + # much faster, because we only need to stat every possible .d files, + # instead of reading the full changelog + for f in fnc: + if f[:5] == b'data/' and f[-2:] == b'.i': + seenfiles.add(f[5:-2]) + newentries.add(f) + dataf = f[:-2] + b'.d' + if repo.store._exists(dataf): + newentries.add(dataf) + else: + progress = ui.makeprogress( + _(b'rebuilding'), unit=_(b'changesets'), total=len(repo) + ) + for rev in repo: + progress.update(rev) - ctx = repo[rev] - for f in ctx.files(): - # This is to minimize I/O. - if f in seenfiles: - continue - seenfiles.add(f) + ctx = repo[rev] + for f in ctx.files(): + # This is to minimize I/O. + if f in seenfiles: + continue + seenfiles.add(f) - i = b'data/%s.i' % f - d = b'data/%s.d' % f + i = b'data/%s.i' % f + d = b'data/%s.d' % f - if repo.store._exists(i): - newentries.add(i) - if repo.store._exists(d): - newentries.add(d) + if repo.store._exists(i): + newentries.add(i) + if repo.store._exists(d): + newentries.add(d) - progress.complete() + progress.complete() if requirements.TREEMANIFEST_REQUIREMENT in repo.requirements: # This logic is safe if treemanifest isn't enabled, but also diff --git a/tests/test-completion.t b/tests/test-completion.t --- a/tests/test-completion.t +++ b/tests/test-completion.t @@ -316,7 +316,7 @@ debugpushkey: debugpvec: debugrebuilddirstate: rev, minimal - debugrebuildfncache: + debugrebuildfncache: only-data debugrename: rev debugrequires: debugrevlog: changelog, manifest, dir, dump diff --git a/tests/test-transaction-rollback-on-revlog-split.t b/tests/test-transaction-rollback-on-revlog-split.t --- a/tests/test-transaction-rollback-on-revlog-split.t +++ b/tests/test-transaction-rollback-on-revlog-split.t @@ -86,6 +86,10 @@ warning: revlog 'data/file.d' not in fncache! 1 warnings encountered! hint: run "hg debugrebuildfncache" to recover from corrupt fncache + $ hg debugrebuildfncache --only-data + adding data/file.d + 1 items added, 0 removed from fncache + $ hg verify -q $ cd ..