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
|