|
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 |