2778
|
1 |
# verify.py - repository integrity checking for Mercurial
|
|
2 |
#
|
|
3 |
# Copyright 2006 Matt Mackall <mpm@selenic.com>
|
|
4 |
#
|
|
5 |
# This software may be used and distributed according to the terms
|
|
6 |
# of the GNU General Public License, incorporated herein by reference.
|
|
7 |
|
|
8 |
from node import *
|
|
9 |
from i18n import gettext as _
|
|
10 |
import revlog, mdiff
|
|
11 |
|
|
12 |
def verify(repo):
|
|
13 |
filelinkrevs = {}
|
|
14 |
filenodes = {}
|
|
15 |
changesets = revisions = files = 0
|
|
16 |
errors = [0]
|
|
17 |
warnings = [0]
|
|
18 |
neededmanifests = {}
|
|
19 |
|
|
20 |
def err(msg):
|
|
21 |
repo.ui.warn(msg + "\n")
|
|
22 |
errors[0] += 1
|
|
23 |
|
|
24 |
def warn(msg):
|
|
25 |
repo.ui.warn(msg + "\n")
|
|
26 |
warnings[0] += 1
|
|
27 |
|
|
28 |
def checksize(obj, name):
|
|
29 |
d = obj.checksize()
|
|
30 |
if d[0]:
|
|
31 |
err(_("%s data length off by %d bytes") % (name, d[0]))
|
|
32 |
if d[1]:
|
|
33 |
err(_("%s index contains %d extra bytes") % (name, d[1]))
|
|
34 |
|
|
35 |
def checkversion(obj, name):
|
|
36 |
if obj.version != revlog.REVLOGV0:
|
|
37 |
if not revlogv1:
|
|
38 |
warn(_("warning: `%s' uses revlog format 1") % name)
|
|
39 |
elif revlogv1:
|
|
40 |
warn(_("warning: `%s' uses revlog format 0") % name)
|
|
41 |
|
|
42 |
revlogv1 = repo.revlogversion != revlog.REVLOGV0
|
|
43 |
if repo.ui.verbose or revlogv1 != repo.revlogv1:
|
|
44 |
repo.ui.status(_("repository uses revlog format %d\n") %
|
|
45 |
(revlogv1 and 1 or 0))
|
|
46 |
|
|
47 |
seen = {}
|
|
48 |
repo.ui.status(_("checking changesets\n"))
|
|
49 |
checksize(repo.changelog, "changelog")
|
|
50 |
|
|
51 |
for i in range(repo.changelog.count()):
|
|
52 |
changesets += 1
|
|
53 |
n = repo.changelog.node(i)
|
|
54 |
l = repo.changelog.linkrev(n)
|
|
55 |
if l != i:
|
|
56 |
err(_("incorrect link (%d) for changeset revision %d") %(l, i))
|
|
57 |
if n in seen:
|
|
58 |
err(_("duplicate changeset at revision %d") % i)
|
|
59 |
seen[n] = 1
|
|
60 |
|
|
61 |
for p in repo.changelog.parents(n):
|
|
62 |
if p not in repo.changelog.nodemap:
|
|
63 |
err(_("changeset %s has unknown parent %s") %
|
|
64 |
(short(n), short(p)))
|
|
65 |
try:
|
|
66 |
changes = repo.changelog.read(n)
|
|
67 |
except KeyboardInterrupt:
|
|
68 |
repo.ui.warn(_("interrupted"))
|
|
69 |
raise
|
|
70 |
except Exception, inst:
|
|
71 |
err(_("unpacking changeset %s: %s") % (short(n), inst))
|
|
72 |
continue
|
|
73 |
|
|
74 |
neededmanifests[changes[0]] = n
|
|
75 |
|
|
76 |
for f in changes[3]:
|
|
77 |
filelinkrevs.setdefault(f, []).append(i)
|
|
78 |
|
|
79 |
seen = {}
|
|
80 |
repo.ui.status(_("checking manifests\n"))
|
|
81 |
checkversion(repo.manifest, "manifest")
|
|
82 |
checksize(repo.manifest, "manifest")
|
|
83 |
|
|
84 |
for i in range(repo.manifest.count()):
|
|
85 |
n = repo.manifest.node(i)
|
|
86 |
l = repo.manifest.linkrev(n)
|
|
87 |
|
|
88 |
if l < 0 or l >= repo.changelog.count():
|
|
89 |
err(_("bad manifest link (%d) at revision %d") % (l, i))
|
|
90 |
|
|
91 |
if n in neededmanifests:
|
|
92 |
del neededmanifests[n]
|
|
93 |
|
|
94 |
if n in seen:
|
|
95 |
err(_("duplicate manifest at revision %d") % i)
|
|
96 |
|
|
97 |
seen[n] = 1
|
|
98 |
|
|
99 |
for p in repo.manifest.parents(n):
|
|
100 |
if p not in repo.manifest.nodemap:
|
|
101 |
err(_("manifest %s has unknown parent %s") %
|
|
102 |
(short(n), short(p)))
|
|
103 |
|
|
104 |
try:
|
|
105 |
delta = mdiff.patchtext(repo.manifest.delta(n))
|
|
106 |
except KeyboardInterrupt:
|
|
107 |
repo.ui.warn(_("interrupted"))
|
|
108 |
raise
|
|
109 |
except Exception, inst:
|
|
110 |
err(_("unpacking manifest %s: %s") % (short(n), inst))
|
|
111 |
continue
|
|
112 |
|
|
113 |
try:
|
|
114 |
ff = [ l.split('\0') for l in delta.splitlines() ]
|
|
115 |
for f, fn in ff:
|
|
116 |
filenodes.setdefault(f, {})[bin(fn[:40])] = 1
|
|
117 |
except (ValueError, TypeError), inst:
|
|
118 |
err(_("broken delta in manifest %s: %s") % (short(n), inst))
|
|
119 |
|
|
120 |
repo.ui.status(_("crosschecking files in changesets and manifests\n"))
|
|
121 |
|
|
122 |
for m, c in neededmanifests.items():
|
|
123 |
err(_("Changeset %s refers to unknown manifest %s") %
|
|
124 |
(short(m), short(c)))
|
|
125 |
del neededmanifests
|
|
126 |
|
|
127 |
for f in filenodes:
|
|
128 |
if f not in filelinkrevs:
|
|
129 |
err(_("file %s in manifest but not in changesets") % f)
|
|
130 |
|
|
131 |
for f in filelinkrevs:
|
|
132 |
if f not in filenodes:
|
|
133 |
err(_("file %s in changeset but not in manifest") % f)
|
|
134 |
|
|
135 |
repo.ui.status(_("checking files\n"))
|
|
136 |
ff = filenodes.keys()
|
|
137 |
ff.sort()
|
|
138 |
for f in ff:
|
|
139 |
if f == "/dev/null":
|
|
140 |
continue
|
|
141 |
files += 1
|
|
142 |
if not f:
|
|
143 |
err(_("file without name in manifest %s") % short(n))
|
|
144 |
continue
|
|
145 |
fl = repo.file(f)
|
|
146 |
checkversion(fl, f)
|
|
147 |
checksize(fl, f)
|
|
148 |
|
|
149 |
nodes = {nullid: 1}
|
|
150 |
seen = {}
|
|
151 |
for i in range(fl.count()):
|
|
152 |
revisions += 1
|
|
153 |
n = fl.node(i)
|
|
154 |
|
|
155 |
if n in seen:
|
|
156 |
err(_("%s: duplicate revision %d") % (f, i))
|
|
157 |
if n not in filenodes[f]:
|
|
158 |
err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
|
|
159 |
else:
|
|
160 |
del filenodes[f][n]
|
|
161 |
|
|
162 |
flr = fl.linkrev(n)
|
|
163 |
if flr not in filelinkrevs.get(f, []):
|
|
164 |
err(_("%s:%s points to unexpected changeset %d")
|
|
165 |
% (f, short(n), flr))
|
|
166 |
else:
|
|
167 |
filelinkrevs[f].remove(flr)
|
|
168 |
|
|
169 |
# verify contents
|
|
170 |
try:
|
|
171 |
t = fl.read(n)
|
|
172 |
except KeyboardInterrupt:
|
|
173 |
repo.ui.warn(_("interrupted"))
|
|
174 |
raise
|
|
175 |
except Exception, inst:
|
|
176 |
err(_("unpacking file %s %s: %s") % (f, short(n), inst))
|
|
177 |
|
|
178 |
# verify parents
|
|
179 |
(p1, p2) = fl.parents(n)
|
|
180 |
if p1 not in nodes:
|
|
181 |
err(_("file %s:%s unknown parent 1 %s") %
|
|
182 |
(f, short(n), short(p1)))
|
|
183 |
if p2 not in nodes:
|
|
184 |
err(_("file %s:%s unknown parent 2 %s") %
|
|
185 |
(f, short(n), short(p1)))
|
|
186 |
nodes[n] = 1
|
|
187 |
|
|
188 |
# cross-check
|
|
189 |
for node in filenodes[f]:
|
|
190 |
err(_("node %s in manifests not in %s") % (hex(node), f))
|
|
191 |
|
|
192 |
repo.ui.status(_("%d files, %d changesets, %d total revisions\n") %
|
|
193 |
(files, changesets, revisions))
|
|
194 |
|
|
195 |
if warnings[0]:
|
|
196 |
repo.ui.warn(_("%d warnings encountered!\n") % warnings[0])
|
|
197 |
if errors[0]:
|
|
198 |
repo.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
|
|
199 |
return 1
|
|
200 |
|