verify.py
281 lines
| 9.1 KiB
| text/x-python
|
PythonLexer
/ mercurial / verify.py
Matt Mackall
|
r2778 | # verify.py - repository integrity checking for Mercurial | ||
# | ||||
Thomas Arendsen Hein
|
r4635 | # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com> | ||
Matt Mackall
|
r2778 | # | ||
# This software may be used and distributed according to the terms | ||||
# of the GNU General Public License, incorporated herein by reference. | ||||
Joel Rosdahl
|
r6211 | from node import nullid, short | ||
Matt Mackall
|
r3891 | from i18n import _ | ||
Matt Mackall
|
r5175 | import revlog | ||
Matt Mackall
|
r2778 | |||
def verify(repo): | ||||
Matt Mackall
|
r4915 | lock = repo.lock() | ||
try: | ||||
return _verify(repo) | ||||
finally: | ||||
del lock | ||||
def _verify(repo): | ||||
Matt Mackall
|
r2778 | filelinkrevs = {} | ||
filenodes = {} | ||||
changesets = revisions = files = 0 | ||||
Matt Mackall
|
r5313 | firstbad = [None] | ||
Matt Mackall
|
r2778 | errors = [0] | ||
warnings = [0] | ||||
neededmanifests = {} | ||||
Matt Mackall
|
r5313 | def err(linkrev, msg, filename=None): | ||
if linkrev != None: | ||||
if firstbad[0] != None: | ||||
firstbad[0] = min(firstbad[0], linkrev) | ||||
else: | ||||
firstbad[0] = linkrev | ||||
else: | ||||
linkrev = "?" | ||||
msg = "%s: %s" % (linkrev, msg) | ||||
if filename: | ||||
msg = "%s@%s" % (filename, msg) | ||||
repo.ui.warn(" " + msg + "\n") | ||||
Matt Mackall
|
r2778 | errors[0] += 1 | ||
def warn(msg): | ||||
repo.ui.warn(msg + "\n") | ||||
warnings[0] += 1 | ||||
def checksize(obj, name): | ||||
d = obj.checksize() | ||||
if d[0]: | ||||
Matt Mackall
|
r5313 | err(None, _("data length off by %d bytes") % d[0], name) | ||
Matt Mackall
|
r2778 | if d[1]: | ||
Matt Mackall
|
r5313 | err(None, _("index contains %d extra bytes") % d[1], name) | ||
Matt Mackall
|
r2778 | |||
def checkversion(obj, name): | ||||
if obj.version != revlog.REVLOGV0: | ||||
if not revlogv1: | ||||
warn(_("warning: `%s' uses revlog format 1") % name) | ||||
elif revlogv1: | ||||
warn(_("warning: `%s' uses revlog format 0") % name) | ||||
Matt Mackall
|
r4258 | revlogv1 = repo.changelog.version != revlog.REVLOGV0 | ||
if repo.ui.verbose or not revlogv1: | ||||
Matt Mackall
|
r2778 | repo.ui.status(_("repository uses revlog format %d\n") % | ||
(revlogv1 and 1 or 0)) | ||||
Matt Mackall
|
r5541 | havecl = havemf = 1 | ||
Matt Mackall
|
r2778 | seen = {} | ||
repo.ui.status(_("checking changesets\n")) | ||||
Matt Mackall
|
r5541 | if repo.changelog.count() == 0 and repo.manifest.count() > 1: | ||
havecl = 0 | ||||
err(0, _("empty or missing 00changelog.i")) | ||||
else: | ||||
checksize(repo.changelog, "changelog") | ||||
Matt Mackall
|
r2778 | |||
Benoit Boissinot
|
r3473 | for i in xrange(repo.changelog.count()): | ||
Matt Mackall
|
r2778 | changesets += 1 | ||
n = repo.changelog.node(i) | ||||
l = repo.changelog.linkrev(n) | ||||
if l != i: | ||||
Matt Mackall
|
r5313 | err(i, _("incorrect link (%d) for changeset") %(l)) | ||
Matt Mackall
|
r2778 | if n in seen: | ||
Matt Mackall
|
r5313 | err(i, _("duplicates changeset at revision %d") % seen[n]) | ||
seen[n] = i | ||||
Matt Mackall
|
r2778 | |||
for p in repo.changelog.parents(n): | ||||
if p not in repo.changelog.nodemap: | ||||
Matt Mackall
|
r5313 | err(i, _("changeset has unknown parent %s") % short(p)) | ||
Matt Mackall
|
r2778 | try: | ||
changes = repo.changelog.read(n) | ||||
except KeyboardInterrupt: | ||||
repo.ui.warn(_("interrupted")) | ||||
raise | ||||
except Exception, inst: | ||||
Matt Mackall
|
r5313 | err(i, _("unpacking changeset: %s") % inst) | ||
Matt Mackall
|
r2778 | continue | ||
Matt Mackall
|
r5313 | if changes[0] not in neededmanifests: | ||
neededmanifests[changes[0]] = i | ||||
Matt Mackall
|
r2778 | |||
for f in changes[3]: | ||||
filelinkrevs.setdefault(f, []).append(i) | ||||
seen = {} | ||||
repo.ui.status(_("checking manifests\n")) | ||||
Matt Mackall
|
r5541 | if repo.changelog.count() > 0 and repo.manifest.count() == 0: | ||
havemf = 0 | ||||
err(0, _("empty or missing 00manifest.i")) | ||||
else: | ||||
checkversion(repo.manifest, "manifest") | ||||
checksize(repo.manifest, "manifest") | ||||
Matt Mackall
|
r2778 | |||
Benoit Boissinot
|
r3473 | for i in xrange(repo.manifest.count()): | ||
Matt Mackall
|
r2778 | n = repo.manifest.node(i) | ||
l = repo.manifest.linkrev(n) | ||||
Matt Mackall
|
r5541 | if l < 0 or (havecl and l >= repo.changelog.count()): | ||
Matt Mackall
|
r5313 | err(None, _("bad link (%d) at manifest revision %d") % (l, i)) | ||
Matt Mackall
|
r2778 | |||
if n in neededmanifests: | ||||
del neededmanifests[n] | ||||
if n in seen: | ||||
Matt Mackall
|
r5313 | err(l, _("duplicates manifest from %d") % seen[n]) | ||
Matt Mackall
|
r2778 | |||
Matt Mackall
|
r5313 | seen[n] = l | ||
Matt Mackall
|
r2778 | |||
for p in repo.manifest.parents(n): | ||||
if p not in repo.manifest.nodemap: | ||||
Matt Mackall
|
r5313 | err(l, _("manifest has unknown parent %s") % short(p)) | ||
Matt Mackall
|
r2778 | |||
try: | ||||
Brendan Cully
|
r3196 | for f, fn in repo.manifest.readdelta(n).iteritems(): | ||
Matt Mackall
|
r5313 | fns = filenodes.setdefault(f, {}) | ||
if fn not in fns: | ||||
fns[fn] = n | ||||
Matt Mackall
|
r2778 | except KeyboardInterrupt: | ||
repo.ui.warn(_("interrupted")) | ||||
raise | ||||
except Exception, inst: | ||||
Matt Mackall
|
r5313 | err(l, _("reading manifest delta: %s") % inst) | ||
Matt Mackall
|
r2778 | continue | ||
repo.ui.status(_("crosschecking files in changesets and manifests\n")) | ||||
Matt Mackall
|
r5541 | if havemf > 0: | ||
nm = [(c, m) for m, c in neededmanifests.items()] | ||||
nm.sort() | ||||
for c, m in nm: | ||||
err(c, _("changeset refers to unknown manifest %s") % short(m)) | ||||
del neededmanifests, nm | ||||
Matt Mackall
|
r2778 | |||
Matt Mackall
|
r5541 | if havecl: | ||
fl = filenodes.keys() | ||||
fl.sort() | ||||
for f in fl: | ||||
if f not in filelinkrevs: | ||||
lrs = [repo.manifest.linkrev(n) for n in filenodes[f]] | ||||
lrs.sort() | ||||
err(lrs[0], _("in manifest but not in changeset"), f) | ||||
del fl | ||||
Matt Mackall
|
r2778 | |||
Matt Mackall
|
r5541 | if havemf: | ||
fl = filelinkrevs.keys() | ||||
fl.sort() | ||||
for f in fl: | ||||
if f not in filenodes: | ||||
lr = filelinkrevs[f][0] | ||||
err(lr, _("in changeset but not in manifest"), f) | ||||
del fl | ||||
Matt Mackall
|
r2778 | |||
repo.ui.status(_("checking files\n")) | ||||
Matt Mackall
|
r5541 | ff = dict.fromkeys(filenodes.keys() + filelinkrevs.keys()).keys() | ||
Matt Mackall
|
r2778 | ff.sort() | ||
for f in ff: | ||||
if f == "/dev/null": | ||||
continue | ||||
files += 1 | ||||
if not f: | ||||
Matt Mackall
|
r5541 | lr = filelinkrevs[f][0] | ||
err(lr, _("file without name in manifest")) | ||||
Matt Mackall
|
r2778 | continue | ||
fl = repo.file(f) | ||||
checkversion(fl, f) | ||||
checksize(fl, f) | ||||
Matt Mackall
|
r5541 | if fl.count() == 0: | ||
err(filelinkrevs[f][0], _("empty or missing revlog"), f) | ||||
continue | ||||
Matt Mackall
|
r5313 | seen = {} | ||
Matt Mackall
|
r2778 | nodes = {nullid: 1} | ||
Benoit Boissinot
|
r3473 | for i in xrange(fl.count()): | ||
Matt Mackall
|
r2778 | revisions += 1 | ||
n = fl.node(i) | ||||
Matt Mackall
|
r5313 | flr = fl.linkrev(n) | ||
Matt Mackall
|
r5541 | if flr < 0 or (havecl and flr not in filelinkrevs.get(f, [])): | ||
Matt Mackall
|
r5313 | if flr < 0 or flr >= repo.changelog.count(): | ||
err(None, _("rev %d point to nonexistent changeset %d") | ||||
% (i, flr), f) | ||||
else: | ||||
err(None, _("rev %d points to unexpected changeset %d") | ||||
% (i, flr), f) | ||||
if f in filelinkrevs: | ||||
warn(_(" (expected %s)") % filelinkrevs[f][0]) | ||||
flr = None # can't be trusted | ||||
else: | ||||
Matt Mackall
|
r5541 | if havecl: | ||
filelinkrevs[f].remove(flr) | ||||
Matt Mackall
|
r2778 | |||
if n in seen: | ||||
Matt Mackall
|
r5313 | err(flr, _("duplicate revision %d") % i, f) | ||
Matt Mackall
|
r5541 | if f in filenodes: | ||
if havemf and n not in filenodes[f]: | ||||
err(flr, _("%s not in manifests") % (short(n)), f) | ||||
else: | ||||
del filenodes[f][n] | ||||
Matt Mackall
|
r2778 | |||
# verify contents | ||||
try: | ||||
t = fl.read(n) | ||||
except KeyboardInterrupt: | ||||
repo.ui.warn(_("interrupted")) | ||||
raise | ||||
except Exception, inst: | ||||
Matt Mackall
|
r5313 | err(flr, _("unpacking %s: %s") % (short(n), inst), f) | ||
Matt Mackall
|
r2778 | |||
# verify parents | ||||
Matt Mackall
|
r5313 | try: | ||
(p1, p2) = fl.parents(n) | ||||
if p1 not in nodes: | ||||
err(flr, _("unknown parent 1 %s of %s") % | ||||
(short(p1), short(n)), f) | ||||
if p2 not in nodes: | ||||
err(flr, _("unknown parent 2 %s of %s") % | ||||
(short(p2), short(p1)), f) | ||||
except KeyboardInterrupt: | ||||
repo.ui.warn(_("interrupted")) | ||||
raise | ||||
except Exception, inst: | ||||
err(flr, _("checking parents of %s: %s") % (short(n), inst), f) | ||||
Matt Mackall
|
r2778 | nodes[n] = 1 | ||
Matt Mackall
|
r3744 | # check renames | ||
try: | ||||
rp = fl.renamed(n) | ||||
if rp: | ||||
fl2 = repo.file(rp[0]) | ||||
Patrick Mezard
|
r6534 | if fl2.count() == 0: | ||
err(flr, _("empty or missing copy source revlog %s:%s") | ||||
% (rp[0], short(rp[1])), f) | ||||
elif rp[1] == nullid: | ||||
err(flr, _("copy source revision is nullid %s:%s") | ||||
% (rp[0], short(rp[1])), f) | ||||
else: | ||||
rev = fl2.rev(rp[1]) | ||||
Matt Mackall
|
r3744 | except KeyboardInterrupt: | ||
repo.ui.warn(_("interrupted")) | ||||
raise | ||||
except Exception, inst: | ||||
Matt Mackall
|
r5313 | err(flr, _("checking rename of %s: %s") % | ||
(short(n), inst), f) | ||||
Matt Mackall
|
r3744 | |||
Matt Mackall
|
r2778 | # cross-check | ||
Matt Mackall
|
r5541 | if f in filenodes: | ||
fns = [(repo.manifest.linkrev(filenodes[f][n]), n) | ||||
for n in filenodes[f]] | ||||
fns.sort() | ||||
for lr, node in fns: | ||||
err(lr, _("%s in manifests not found") % short(node), f) | ||||
Matt Mackall
|
r2778 | |||
repo.ui.status(_("%d files, %d changesets, %d total revisions\n") % | ||||
(files, changesets, revisions)) | ||||
if warnings[0]: | ||||
repo.ui.warn(_("%d warnings encountered!\n") % warnings[0]) | ||||
if errors[0]: | ||||
repo.ui.warn(_("%d integrity errors encountered!\n") % errors[0]) | ||||
Matt Mackall
|
r5313 | if firstbad[0]: | ||
repo.ui.warn(_("(first damaged changeset appears to be %d)\n") | ||||
% firstbad[0]) | ||||
Matt Mackall
|
r2778 | return 1 | ||