##// END OF EJS Templates
merge with -stable
Benoit Boissinot -
r7837:db39b6c3 merge default
parent child Browse files
Show More
@@ -1,313 +1,313 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, mimetypes
9 import os, mimetypes
10 from mercurial.node import hex, nullid
10 from mercurial.node import hex, nullid
11 from mercurial import ui, hg, util, hook, error
11 from mercurial import ui, hg, util, hook, error
12 from mercurial import templater, templatefilters
12 from mercurial import templater, templatefilters
13 from common import get_mtime, style_map, ErrorResponse
13 from common import get_mtime, style_map, ErrorResponse
14 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from common import HTTP_UNAUTHORIZED, HTTP_METHOD_NOT_ALLOWED
15 from common import HTTP_UNAUTHORIZED, HTTP_METHOD_NOT_ALLOWED
16 from request import wsgirequest
16 from request import wsgirequest
17 import webcommands, protocol, webutil
17 import webcommands, protocol, webutil
18
18
19 perms = {
19 perms = {
20 'changegroup': 'pull',
20 'changegroup': 'pull',
21 'changegroupsubset': 'pull',
21 'changegroupsubset': 'pull',
22 'unbundle': 'push',
22 'unbundle': 'push',
23 'stream_out': 'pull',
23 'stream_out': 'pull',
24 }
24 }
25
25
26 class hgweb(object):
26 class hgweb(object):
27 def __init__(self, repo, name=None):
27 def __init__(self, repo, name=None):
28 if isinstance(repo, str):
28 if isinstance(repo, str):
29 parentui = ui.ui(report_untrusted=False, interactive=False)
29 parentui = ui.ui(report_untrusted=False, interactive=False)
30 self.repo = hg.repository(parentui, repo)
30 self.repo = hg.repository(parentui, repo)
31 else:
31 else:
32 self.repo = repo
32 self.repo = repo
33
33
34 hook.redirect(True)
34 hook.redirect(True)
35 self.mtime = -1
35 self.mtime = -1
36 self.reponame = name
36 self.reponame = name
37 self.archives = 'zip', 'gz', 'bz2'
37 self.archives = 'zip', 'gz', 'bz2'
38 self.stripecount = 1
38 self.stripecount = 1
39 # a repo owner may set web.templates in .hg/hgrc to get any file
39 # a repo owner may set web.templates in .hg/hgrc to get any file
40 # readable by the user running the CGI script
40 # readable by the user running the CGI script
41 self.templatepath = self.config("web", "templates",
41 self.templatepath = self.config("web", "templates",
42 templater.templatepath(),
42 templater.templatepath(),
43 untrusted=False)
43 untrusted=False)
44
44
45 # The CGI scripts are often run by a user different from the repo owner.
45 # The CGI scripts are often run by a user different from the repo owner.
46 # Trust the settings from the .hg/hgrc files by default.
46 # Trust the settings from the .hg/hgrc files by default.
47 def config(self, section, name, default=None, untrusted=True):
47 def config(self, section, name, default=None, untrusted=True):
48 return self.repo.ui.config(section, name, default,
48 return self.repo.ui.config(section, name, default,
49 untrusted=untrusted)
49 untrusted=untrusted)
50
50
51 def configbool(self, section, name, default=False, untrusted=True):
51 def configbool(self, section, name, default=False, untrusted=True):
52 return self.repo.ui.configbool(section, name, default,
52 return self.repo.ui.configbool(section, name, default,
53 untrusted=untrusted)
53 untrusted=untrusted)
54
54
55 def configlist(self, section, name, default=None, untrusted=True):
55 def configlist(self, section, name, default=None, untrusted=True):
56 return self.repo.ui.configlist(section, name, default,
56 return self.repo.ui.configlist(section, name, default,
57 untrusted=untrusted)
57 untrusted=untrusted)
58
58
59 def refresh(self):
59 def refresh(self):
60 mtime = get_mtime(self.repo.root)
60 mtime = get_mtime(self.repo.root)
61 if mtime != self.mtime:
61 if mtime != self.mtime:
62 self.mtime = mtime
62 self.mtime = mtime
63 self.repo = hg.repository(self.repo.ui, self.repo.root)
63 self.repo = hg.repository(self.repo.ui, self.repo.root)
64 self.maxchanges = int(self.config("web", "maxchanges", 10))
64 self.maxchanges = int(self.config("web", "maxchanges", 10))
65 self.stripecount = int(self.config("web", "stripes", 1))
65 self.stripecount = int(self.config("web", "stripes", 1))
66 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
66 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
67 self.maxfiles = int(self.config("web", "maxfiles", 10))
67 self.maxfiles = int(self.config("web", "maxfiles", 10))
68 self.allowpull = self.configbool("web", "allowpull", True)
68 self.allowpull = self.configbool("web", "allowpull", True)
69 self.encoding = self.config("web", "encoding", util._encoding)
69 self.encoding = self.config("web", "encoding", util._encoding)
70
70
71 def run(self):
71 def run(self):
72 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
72 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
73 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
73 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
74 import mercurial.hgweb.wsgicgi as wsgicgi
74 import mercurial.hgweb.wsgicgi as wsgicgi
75 wsgicgi.launch(self)
75 wsgicgi.launch(self)
76
76
77 def __call__(self, env, respond):
77 def __call__(self, env, respond):
78 req = wsgirequest(env, respond)
78 req = wsgirequest(env, respond)
79 return self.run_wsgi(req)
79 return self.run_wsgi(req)
80
80
81 def run_wsgi(self, req):
81 def run_wsgi(self, req):
82
82
83 self.refresh()
83 self.refresh()
84
84
85 # process this if it's a protocol request
85 # process this if it's a protocol request
86 # protocol bits don't need to create any URLs
86 # protocol bits don't need to create any URLs
87 # and the clients always use the old URL structure
87 # and the clients always use the old URL structure
88
88
89 cmd = req.form.get('cmd', [''])[0]
89 cmd = req.form.get('cmd', [''])[0]
90 if cmd and cmd in protocol.__all__:
90 if cmd and cmd in protocol.__all__:
91 try:
91 try:
92 if cmd in perms:
92 if cmd in perms:
93 try:
93 try:
94 self.check_perm(req, perms[cmd])
94 self.check_perm(req, perms[cmd])
95 except ErrorResponse, inst:
95 except ErrorResponse, inst:
96 if cmd == 'unbundle':
96 if cmd == 'unbundle':
97 req.drain()
97 req.drain()
98 raise
98 raise
99 method = getattr(protocol, cmd)
99 method = getattr(protocol, cmd)
100 return method(self.repo, req)
100 return method(self.repo, req)
101 except ErrorResponse, inst:
101 except ErrorResponse, inst:
102 req.respond(inst, protocol.HGTYPE)
102 req.respond(inst, protocol.HGTYPE)
103 if not inst.message:
103 if not inst.message:
104 return []
104 return []
105 return '0\n%s\n' % inst.message,
105 return '0\n%s\n' % inst.message,
106
106
107 # work with CGI variables to create coherent structure
107 # work with CGI variables to create coherent structure
108 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
108 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
109
109
110 req.url = req.env['SCRIPT_NAME']
110 req.url = req.env['SCRIPT_NAME']
111 if not req.url.endswith('/'):
111 if not req.url.endswith('/'):
112 req.url += '/'
112 req.url += '/'
113 if 'REPO_NAME' in req.env:
113 if 'REPO_NAME' in req.env:
114 req.url += req.env['REPO_NAME'] + '/'
114 req.url += req.env['REPO_NAME'] + '/'
115
115
116 if 'PATH_INFO' in req.env:
116 if 'PATH_INFO' in req.env:
117 parts = req.env['PATH_INFO'].strip('/').split('/')
117 parts = req.env['PATH_INFO'].strip('/').split('/')
118 repo_parts = req.env.get('REPO_NAME', '').split('/')
118 repo_parts = req.env.get('REPO_NAME', '').split('/')
119 if parts[:len(repo_parts)] == repo_parts:
119 if parts[:len(repo_parts)] == repo_parts:
120 parts = parts[len(repo_parts):]
120 parts = parts[len(repo_parts):]
121 query = '/'.join(parts)
121 query = '/'.join(parts)
122 else:
122 else:
123 query = req.env['QUERY_STRING'].split('&', 1)[0]
123 query = req.env['QUERY_STRING'].split('&', 1)[0]
124 query = query.split(';', 1)[0]
124 query = query.split(';', 1)[0]
125
125
126 # translate user-visible url structure to internal structure
126 # translate user-visible url structure to internal structure
127
127
128 args = query.split('/', 2)
128 args = query.split('/', 2)
129 if 'cmd' not in req.form and args and args[0]:
129 if 'cmd' not in req.form and args and args[0]:
130
130
131 cmd = args.pop(0)
131 cmd = args.pop(0)
132 style = cmd.rfind('-')
132 style = cmd.rfind('-')
133 if style != -1:
133 if style != -1:
134 req.form['style'] = [cmd[:style]]
134 req.form['style'] = [cmd[:style]]
135 cmd = cmd[style+1:]
135 cmd = cmd[style+1:]
136
136
137 # avoid accepting e.g. style parameter as command
137 # avoid accepting e.g. style parameter as command
138 if hasattr(webcommands, cmd):
138 if hasattr(webcommands, cmd):
139 req.form['cmd'] = [cmd]
139 req.form['cmd'] = [cmd]
140 else:
140 else:
141 cmd = ''
141 cmd = ''
142
142
143 if cmd == 'static':
143 if cmd == 'static':
144 req.form['file'] = ['/'.join(args)]
144 req.form['file'] = ['/'.join(args)]
145 else:
145 else:
146 if args and args[0]:
146 if args and args[0]:
147 node = args.pop(0)
147 node = args.pop(0)
148 req.form['node'] = [node]
148 req.form['node'] = [node]
149 if args:
149 if args:
150 req.form['file'] = args
150 req.form['file'] = args
151
151
152 if cmd == 'archive':
152 if cmd == 'archive':
153 fn = req.form['node'][0]
153 fn = req.form['node'][0]
154 for type_, spec in self.archive_specs.iteritems():
154 for type_, spec in self.archive_specs.iteritems():
155 ext = spec[2]
155 ext = spec[2]
156 if fn.endswith(ext):
156 if fn.endswith(ext):
157 req.form['node'] = [fn[:-len(ext)]]
157 req.form['node'] = [fn[:-len(ext)]]
158 req.form['type'] = [type_]
158 req.form['type'] = [type_]
159
159
160 # process the web interface request
160 # process the web interface request
161
161
162 try:
162 try:
163 tmpl = self.templater(req)
163 tmpl = self.templater(req)
164 ctype = tmpl('mimetype', encoding=self.encoding)
164 ctype = tmpl('mimetype', encoding=self.encoding)
165 ctype = templater.stringify(ctype)
165 ctype = templater.stringify(ctype)
166
166
167 # check read permissions non-static content
167 # check read permissions non-static content
168 if cmd != 'static':
168 if cmd != 'static':
169 self.check_perm(req, None)
169 self.check_perm(req, None)
170
170
171 if cmd == '':
171 if cmd == '':
172 req.form['cmd'] = [tmpl.cache['default']]
172 req.form['cmd'] = [tmpl.cache['default']]
173 cmd = req.form['cmd'][0]
173 cmd = req.form['cmd'][0]
174
174
175 if cmd not in webcommands.__all__:
175 if cmd not in webcommands.__all__:
176 msg = 'no such method: %s' % cmd
176 msg = 'no such method: %s' % cmd
177 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
177 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
178 elif cmd == 'file' and 'raw' in req.form.get('style', []):
178 elif cmd == 'file' and 'raw' in req.form.get('style', []):
179 self.ctype = ctype
179 self.ctype = ctype
180 content = webcommands.rawfile(self, req, tmpl)
180 content = webcommands.rawfile(self, req, tmpl)
181 else:
181 else:
182 content = getattr(webcommands, cmd)(self, req, tmpl)
182 content = getattr(webcommands, cmd)(self, req, tmpl)
183 req.respond(HTTP_OK, ctype)
183 req.respond(HTTP_OK, ctype)
184
184
185 return content
185 return content
186
186
187 except error.LookupError, err:
187 except error.LookupError, err:
188 req.respond(HTTP_NOT_FOUND, ctype)
188 req.respond(HTTP_NOT_FOUND, ctype)
189 msg = str(err)
189 msg = str(err)
190 if 'manifest' not in msg:
190 if 'manifest' not in msg:
191 msg = 'revision not found: %s' % err.name
191 msg = 'revision not found: %s' % err.name
192 return tmpl('error', error=msg)
192 return tmpl('error', error=msg)
193 except (error.RepoError, error.RevlogError), inst:
193 except (error.RepoError, error.RevlogError), inst:
194 req.respond(HTTP_SERVER_ERROR, ctype)
194 req.respond(HTTP_SERVER_ERROR, ctype)
195 return tmpl('error', error=str(inst))
195 return tmpl('error', error=str(inst))
196 except ErrorResponse, inst:
196 except ErrorResponse, inst:
197 req.respond(inst, ctype)
197 req.respond(inst, ctype)
198 return tmpl('error', error=inst.message)
198 return tmpl('error', error=inst.message)
199
199
200 def templater(self, req):
200 def templater(self, req):
201
201
202 # determine scheme, port and server name
202 # determine scheme, port and server name
203 # this is needed to create absolute urls
203 # this is needed to create absolute urls
204
204
205 proto = req.env.get('wsgi.url_scheme')
205 proto = req.env.get('wsgi.url_scheme')
206 if proto == 'https':
206 if proto == 'https':
207 proto = 'https'
207 proto = 'https'
208 default_port = "443"
208 default_port = "443"
209 else:
209 else:
210 proto = 'http'
210 proto = 'http'
211 default_port = "80"
211 default_port = "80"
212
212
213 port = req.env["SERVER_PORT"]
213 port = req.env["SERVER_PORT"]
214 port = port != default_port and (":" + port) or ""
214 port = port != default_port and (":" + port) or ""
215 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
215 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
216 staticurl = self.config("web", "staticurl") or req.url + 'static/'
216 staticurl = self.config("web", "staticurl") or req.url + 'static/'
217 if not staticurl.endswith('/'):
217 if not staticurl.endswith('/'):
218 staticurl += '/'
218 staticurl += '/'
219
219
220 # some functions for the templater
220 # some functions for the templater
221
221
222 def header(**map):
222 def header(**map):
223 yield tmpl('header', encoding=self.encoding, **map)
223 yield tmpl('header', encoding=self.encoding, **map)
224
224
225 def footer(**map):
225 def footer(**map):
226 yield tmpl("footer", **map)
226 yield tmpl("footer", **map)
227
227
228 def motd(**map):
228 def motd(**map):
229 yield self.config("web", "motd", "")
229 yield self.config("web", "motd", "")
230
230
231 # figure out which style to use
231 # figure out which style to use
232
232
233 vars = {}
233 vars = {}
234 style = self.config("web", "style", "paper")
234 style = self.config("web", "style", "paper")
235 if 'style' in req.form:
235 if 'style' in req.form:
236 style = req.form['style'][0]
236 style = req.form['style'][0]
237 vars['style'] = style
237 vars['style'] = style
238
238
239 start = req.url[-1] == '?' and '&' or '?'
239 start = req.url[-1] == '?' and '&' or '?'
240 sessionvars = webutil.sessionvars(vars, start)
240 sessionvars = webutil.sessionvars(vars, start)
241 mapfile = style_map(self.templatepath, style)
241 mapfile = style_map(self.templatepath, style)
242
242
243 if not self.reponame:
243 if not self.reponame:
244 self.reponame = (self.config("web", "name")
244 self.reponame = (self.config("web", "name")
245 or req.env.get('REPO_NAME')
245 or req.env.get('REPO_NAME')
246 or req.url.strip('/') or self.repo.root)
246 or req.url.strip('/') or self.repo.root)
247
247
248 # create the templater
248 # create the templater
249
249
250 tmpl = templater.templater(mapfile, templatefilters.filters,
250 tmpl = templater.templater(mapfile, templatefilters.filters,
251 defaults={"url": req.url,
251 defaults={"url": req.url,
252 "staticurl": staticurl,
252 "staticurl": staticurl,
253 "urlbase": urlbase,
253 "urlbase": urlbase,
254 "repo": self.reponame,
254 "repo": self.reponame,
255 "header": header,
255 "header": header,
256 "footer": footer,
256 "footer": footer,
257 "motd": motd,
257 "motd": motd,
258 "sessionvars": sessionvars
258 "sessionvars": sessionvars
259 })
259 })
260 return tmpl
260 return tmpl
261
261
262 def archivelist(self, nodeid):
262 def archivelist(self, nodeid):
263 allowed = self.configlist("web", "allow_archive")
263 allowed = self.configlist("web", "allow_archive")
264 for i, spec in self.archive_specs.iteritems():
264 for i, spec in self.archive_specs.iteritems():
265 if i in allowed or self.configbool("web", "allow" + i):
265 if i in allowed or self.configbool("web", "allow" + i):
266 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
266 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
267
267
268 archive_specs = {
268 archive_specs = {
269 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
269 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
270 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
270 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
271 'zip': ('application/zip', 'zip', '.zip', None),
271 'zip': ('application/zip', 'zip', '.zip', None),
272 }
272 }
273
273
274 def check_perm(self, req, op):
274 def check_perm(self, req, op):
275 '''Check permission for operation based on request data (including
275 '''Check permission for operation based on request data (including
276 authentication info). Return if op allowed, else raise an ErrorResponse
276 authentication info). Return if op allowed, else raise an ErrorResponse
277 exception.'''
277 exception.'''
278
278
279 user = req.env.get('REMOTE_USER')
279 user = req.env.get('REMOTE_USER')
280
280
281 deny_read = self.configlist('web', 'deny_read')
281 deny_read = self.configlist('web', 'deny_read')
282 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
282 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
283 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
283 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
284
284
285 allow_read = self.configlist('web', 'allow_read')
285 allow_read = self.configlist('web', 'allow_read')
286 result = (not allow_read) or (allow_read == ['*'])
286 result = (not allow_read) or (allow_read == ['*'])
287 if not result or user in allow_read:
287 if not (result or user in allow_read):
288 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
288 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
289
289
290 if op == 'pull' and not self.allowpull:
290 if op == 'pull' and not self.allowpull:
291 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
291 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
292 elif op == 'pull' or op is None: # op is None for interface requests
292 elif op == 'pull' or op is None: # op is None for interface requests
293 return
293 return
294
294
295 # enforce that you can only push using POST requests
295 # enforce that you can only push using POST requests
296 if req.env['REQUEST_METHOD'] != 'POST':
296 if req.env['REQUEST_METHOD'] != 'POST':
297 msg = 'push requires POST request'
297 msg = 'push requires POST request'
298 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
298 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
299
299
300 # require ssl by default for pushing, auth info cannot be sniffed
300 # require ssl by default for pushing, auth info cannot be sniffed
301 # and replayed
301 # and replayed
302 scheme = req.env.get('wsgi.url_scheme')
302 scheme = req.env.get('wsgi.url_scheme')
303 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
303 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
304 raise ErrorResponse(HTTP_OK, 'ssl required')
304 raise ErrorResponse(HTTP_OK, 'ssl required')
305
305
306 deny = self.configlist('web', 'deny_push')
306 deny = self.configlist('web', 'deny_push')
307 if deny and (not user or deny == ['*'] or user in deny):
307 if deny and (not user or deny == ['*'] or user in deny):
308 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
308 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
309
309
310 allow = self.configlist('web', 'allow_push')
310 allow = self.configlist('web', 'allow_push')
311 result = allow and (allow == ['*'] or user in allow)
311 result = allow and (allow == ['*'] or user in allow)
312 if not result:
312 if not result:
313 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
313 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
@@ -1,239 +1,244 b''
1 # verify.py - repository integrity checking for Mercurial
1 # verify.py - repository integrity checking for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import nullid, short
8 from node import nullid, short
9 from i18n import _
9 from i18n import _
10 import revlog, util
10 import revlog, util, error
11
11
12 def verify(repo):
12 def verify(repo):
13 lock = repo.lock()
13 lock = repo.lock()
14 try:
14 try:
15 return _verify(repo)
15 return _verify(repo)
16 finally:
16 finally:
17 del lock
17 del lock
18
18
19 def _verify(repo):
19 def _verify(repo):
20 mflinkrevs = {}
20 mflinkrevs = {}
21 filelinkrevs = {}
21 filelinkrevs = {}
22 filenodes = {}
22 filenodes = {}
23 revisions = 0
23 revisions = 0
24 badrevs = {}
24 badrevs = {}
25 errors = [0]
25 errors = [0]
26 warnings = [0]
26 warnings = [0]
27 ui = repo.ui
27 ui = repo.ui
28 cl = repo.changelog
28 cl = repo.changelog
29 mf = repo.manifest
29 mf = repo.manifest
30
30
31 if not repo.cancopy():
31 if not repo.cancopy():
32 raise util.Abort(_("cannot verify bundle or remote repos"))
32 raise util.Abort(_("cannot verify bundle or remote repos"))
33
33
34 def err(linkrev, msg, filename=None):
34 def err(linkrev, msg, filename=None):
35 if linkrev != None:
35 if linkrev != None:
36 badrevs[linkrev] = True
36 badrevs[linkrev] = True
37 else:
37 else:
38 linkrev = '?'
38 linkrev = '?'
39 msg = "%s: %s" % (linkrev, msg)
39 msg = "%s: %s" % (linkrev, msg)
40 if filename:
40 if filename:
41 msg = "%s@%s" % (filename, msg)
41 msg = "%s@%s" % (filename, msg)
42 ui.warn(" " + msg + "\n")
42 ui.warn(" " + msg + "\n")
43 errors[0] += 1
43 errors[0] += 1
44
44
45 def exc(linkrev, msg, inst, filename=None):
45 def exc(linkrev, msg, inst, filename=None):
46 if isinstance(inst, KeyboardInterrupt):
46 if isinstance(inst, KeyboardInterrupt):
47 ui.warn(_("interrupted"))
47 ui.warn(_("interrupted"))
48 raise
48 raise
49 err(linkrev, "%s: %s" % (msg, inst), filename)
49 err(linkrev, "%s: %s" % (msg, inst), filename)
50
50
51 def warn(msg):
51 def warn(msg):
52 ui.warn(msg + "\n")
52 ui.warn(msg + "\n")
53 warnings[0] += 1
53 warnings[0] += 1
54
54
55 def checklog(obj, name):
55 def checklog(obj, name):
56 if not len(obj) and (havecl or havemf):
56 if not len(obj) and (havecl or havemf):
57 err(0, _("empty or missing %s") % name)
57 err(0, _("empty or missing %s") % name)
58 return
58 return
59
59
60 d = obj.checksize()
60 d = obj.checksize()
61 if d[0]:
61 if d[0]:
62 err(None, _("data length off by %d bytes") % d[0], name)
62 err(None, _("data length off by %d bytes") % d[0], name)
63 if d[1]:
63 if d[1]:
64 err(None, _("index contains %d extra bytes") % d[1], name)
64 err(None, _("index contains %d extra bytes") % d[1], name)
65
65
66 if obj.version != revlog.REVLOGV0:
66 if obj.version != revlog.REVLOGV0:
67 if not revlogv1:
67 if not revlogv1:
68 warn(_("warning: `%s' uses revlog format 1") % name)
68 warn(_("warning: `%s' uses revlog format 1") % name)
69 elif revlogv1:
69 elif revlogv1:
70 warn(_("warning: `%s' uses revlog format 0") % name)
70 warn(_("warning: `%s' uses revlog format 0") % name)
71
71
72 def checkentry(obj, i, node, seen, linkrevs, f):
72 def checkentry(obj, i, node, seen, linkrevs, f):
73 lr = obj.linkrev(obj.rev(node))
73 lr = obj.linkrev(obj.rev(node))
74 if lr < 0 or (havecl and lr not in linkrevs):
74 if lr < 0 or (havecl and lr not in linkrevs):
75 t = "unexpected"
75 t = "unexpected"
76 if lr < 0 or lr >= len(cl):
76 if lr < 0 or lr >= len(cl):
77 t = "nonexistent"
77 t = "nonexistent"
78 err(None, _("rev %d point to %s changeset %d") % (i, t, lr), f)
78 err(None, _("rev %d point to %s changeset %d") % (i, t, lr), f)
79 if linkrevs:
79 if linkrevs:
80 warn(_(" (expected %s)") % " ".join(map(str,linkrevs)))
80 warn(_(" (expected %s)") % " ".join(map(str,linkrevs)))
81 lr = None # can't be trusted
81 lr = None # can't be trusted
82
82
83 try:
83 try:
84 p1, p2 = obj.parents(node)
84 p1, p2 = obj.parents(node)
85 if p1 not in seen and p1 != nullid:
85 if p1 not in seen and p1 != nullid:
86 err(lr, _("unknown parent 1 %s of %s") %
86 err(lr, _("unknown parent 1 %s of %s") %
87 (short(p1), short(n)), f)
87 (short(p1), short(n)), f)
88 if p2 not in seen and p2 != nullid:
88 if p2 not in seen and p2 != nullid:
89 err(lr, _("unknown parent 2 %s of %s") %
89 err(lr, _("unknown parent 2 %s of %s") %
90 (short(p2), short(p1)), f)
90 (short(p2), short(p1)), f)
91 except Exception, inst:
91 except Exception, inst:
92 exc(lr, _("checking parents of %s") % short(node), inst, f)
92 exc(lr, _("checking parents of %s") % short(node), inst, f)
93
93
94 if node in seen:
94 if node in seen:
95 err(lr, _("duplicate revision %d (%d)") % (i, seen[n]), f)
95 err(lr, _("duplicate revision %d (%d)") % (i, seen[n]), f)
96 seen[n] = i
96 seen[n] = i
97 return lr
97 return lr
98
98
99 revlogv1 = cl.version != revlog.REVLOGV0
99 revlogv1 = cl.version != revlog.REVLOGV0
100 if ui.verbose or not revlogv1:
100 if ui.verbose or not revlogv1:
101 ui.status(_("repository uses revlog format %d\n") %
101 ui.status(_("repository uses revlog format %d\n") %
102 (revlogv1 and 1 or 0))
102 (revlogv1 and 1 or 0))
103
103
104 havecl = len(cl) > 0
104 havecl = len(cl) > 0
105 havemf = len(mf) > 0
105 havemf = len(mf) > 0
106
106
107 ui.status(_("checking changesets\n"))
107 ui.status(_("checking changesets\n"))
108 seen = {}
108 seen = {}
109 checklog(cl, "changelog")
109 checklog(cl, "changelog")
110 for i in repo:
110 for i in repo:
111 n = cl.node(i)
111 n = cl.node(i)
112 checkentry(cl, i, n, seen, [i], "changelog")
112 checkentry(cl, i, n, seen, [i], "changelog")
113
113
114 try:
114 try:
115 changes = cl.read(n)
115 changes = cl.read(n)
116 mflinkrevs.setdefault(changes[0], []).append(i)
116 mflinkrevs.setdefault(changes[0], []).append(i)
117 for f in changes[3]:
117 for f in changes[3]:
118 filelinkrevs.setdefault(f, []).append(i)
118 filelinkrevs.setdefault(f, []).append(i)
119 except Exception, inst:
119 except Exception, inst:
120 exc(i, _("unpacking changeset %s") % short(n), inst)
120 exc(i, _("unpacking changeset %s") % short(n), inst)
121
121
122 ui.status(_("checking manifests\n"))
122 ui.status(_("checking manifests\n"))
123 seen = {}
123 seen = {}
124 checklog(mf, "manifest")
124 checklog(mf, "manifest")
125 for i in mf:
125 for i in mf:
126 n = mf.node(i)
126 n = mf.node(i)
127 lr = checkentry(mf, i, n, seen, mflinkrevs.get(n, []), "manifest")
127 lr = checkentry(mf, i, n, seen, mflinkrevs.get(n, []), "manifest")
128 if n in mflinkrevs:
128 if n in mflinkrevs:
129 del mflinkrevs[n]
129 del mflinkrevs[n]
130
130
131 try:
131 try:
132 for f, fn in mf.readdelta(n).iteritems():
132 for f, fn in mf.readdelta(n).iteritems():
133 if not f:
133 if not f:
134 err(lr, _("file without name in manifest"))
134 err(lr, _("file without name in manifest"))
135 elif f != "/dev/null":
135 elif f != "/dev/null":
136 fns = filenodes.setdefault(f, {})
136 fns = filenodes.setdefault(f, {})
137 if fn not in fns:
137 if fn not in fns:
138 fns[fn] = i
138 fns[fn] = i
139 except Exception, inst:
139 except Exception, inst:
140 exc(lr, _("reading manifest delta %s") % short(n), inst)
140 exc(lr, _("reading manifest delta %s") % short(n), inst)
141
141
142 ui.status(_("crosschecking files in changesets and manifests\n"))
142 ui.status(_("crosschecking files in changesets and manifests\n"))
143
143
144 if havemf:
144 if havemf:
145 for c, m in util.sort([(c, m) for m in mflinkrevs for c in mflinkrevs[m]]):
145 for c, m in util.sort([(c, m) for m in mflinkrevs for c in mflinkrevs[m]]):
146 err(c, _("changeset refers to unknown manifest %s") % short(m))
146 err(c, _("changeset refers to unknown manifest %s") % short(m))
147 del mflinkrevs
147 del mflinkrevs
148
148
149 for f in util.sort(filelinkrevs):
149 for f in util.sort(filelinkrevs):
150 if f not in filenodes:
150 if f not in filenodes:
151 lr = filelinkrevs[f][0]
151 lr = filelinkrevs[f][0]
152 err(lr, _("in changeset but not in manifest"), f)
152 err(lr, _("in changeset but not in manifest"), f)
153
153
154 if havecl:
154 if havecl:
155 for f in util.sort(filenodes):
155 for f in util.sort(filenodes):
156 if f not in filelinkrevs:
156 if f not in filelinkrevs:
157 try:
157 try:
158 fl = repo.file(f)
158 fl = repo.file(f)
159 lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]])
159 lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]])
160 except:
160 except:
161 lr = None
161 lr = None
162 err(lr, _("in manifest but not in changeset"), f)
162 err(lr, _("in manifest but not in changeset"), f)
163
163
164 ui.status(_("checking files\n"))
164 ui.status(_("checking files\n"))
165
165
166 storefiles = {}
166 storefiles = {}
167 for f, f2, size in repo.store.datafiles():
167 for f, f2, size in repo.store.datafiles():
168 if not f:
168 if not f:
169 err(None, _("cannot decode filename '%s'") % f2)
169 err(None, _("cannot decode filename '%s'") % f2)
170 elif size > 0:
170 elif size > 0:
171 storefiles[f] = True
171 storefiles[f] = True
172
172
173 files = util.sort(util.unique(filenodes.keys() + filelinkrevs.keys()))
173 files = util.sort(util.unique(filenodes.keys() + filelinkrevs.keys()))
174 for f in files:
174 for f in files:
175 lr = filelinkrevs[f][0]
176 try:
175 fl = repo.file(f)
177 fl = repo.file(f)
178 except error.RevlogError, e:
179 err(lr, _("broken revlog! (%s)") % e, f)
180 continue
176
181
177 for ff in fl.files():
182 for ff in fl.files():
178 try:
183 try:
179 del storefiles[ff]
184 del storefiles[ff]
180 except KeyError:
185 except KeyError:
181 err(0, _("missing revlog!"), ff)
186 err(lr, _("missing revlog!"), ff)
182
187
183 checklog(fl, f)
188 checklog(fl, f)
184 seen = {}
189 seen = {}
185 for i in fl:
190 for i in fl:
186 revisions += 1
191 revisions += 1
187 n = fl.node(i)
192 n = fl.node(i)
188 lr = checkentry(fl, i, n, seen, filelinkrevs.get(f, []), f)
193 lr = checkentry(fl, i, n, seen, filelinkrevs.get(f, []), f)
189 if f in filenodes:
194 if f in filenodes:
190 if havemf and n not in filenodes[f]:
195 if havemf and n not in filenodes[f]:
191 err(lr, _("%s not in manifests") % (short(n)), f)
196 err(lr, _("%s not in manifests") % (short(n)), f)
192 else:
197 else:
193 del filenodes[f][n]
198 del filenodes[f][n]
194
199
195 # verify contents
200 # verify contents
196 try:
201 try:
197 t = fl.read(n)
202 t = fl.read(n)
198 rp = fl.renamed(n)
203 rp = fl.renamed(n)
199 if len(t) != fl.size(i):
204 if len(t) != fl.size(i):
200 if len(fl.revision(n)) != fl.size(i):
205 if len(fl.revision(n)) != fl.size(i):
201 err(lr, _("unpacked size is %s, %s expected") %
206 err(lr, _("unpacked size is %s, %s expected") %
202 (len(t), fl.size(i)), f)
207 (len(t), fl.size(i)), f)
203 except Exception, inst:
208 except Exception, inst:
204 exc(lr, _("unpacking %s") % short(n), inst, f)
209 exc(lr, _("unpacking %s") % short(n), inst, f)
205
210
206 # check renames
211 # check renames
207 try:
212 try:
208 if rp:
213 if rp:
209 fl2 = repo.file(rp[0])
214 fl2 = repo.file(rp[0])
210 if not len(fl2):
215 if not len(fl2):
211 err(lr, _("empty or missing copy source revlog %s:%s")
216 err(lr, _("empty or missing copy source revlog %s:%s")
212 % (rp[0], short(rp[1])), f)
217 % (rp[0], short(rp[1])), f)
213 elif rp[1] == nullid:
218 elif rp[1] == nullid:
214 warn(_("warning: %s@%s: copy source revision is nullid %s:%s")
219 warn(_("warning: %s@%s: copy source revision is nullid %s:%s")
215 % (f, lr, rp[0], short(rp[1])))
220 % (f, lr, rp[0], short(rp[1])))
216 else:
221 else:
217 rev = fl2.rev(rp[1])
222 rev = fl2.rev(rp[1])
218 except Exception, inst:
223 except Exception, inst:
219 exc(lr, _("checking rename of %s") % short(n), inst, f)
224 exc(lr, _("checking rename of %s") % short(n), inst, f)
220
225
221 # cross-check
226 # cross-check
222 if f in filenodes:
227 if f in filenodes:
223 fns = [(mf.linkrev(l), n) for n,l in filenodes[f].iteritems()]
228 fns = [(mf.linkrev(l), n) for n,l in filenodes[f].iteritems()]
224 for lr, node in util.sort(fns):
229 for lr, node in util.sort(fns):
225 err(lr, _("%s in manifests not found") % short(node), f)
230 err(lr, _("%s in manifests not found") % short(node), f)
226
231
227 for f in storefiles:
232 for f in storefiles:
228 warn(_("warning: orphan revlog '%s'") % f)
233 warn(_("warning: orphan revlog '%s'") % f)
229
234
230 ui.status(_("%d files, %d changesets, %d total revisions\n") %
235 ui.status(_("%d files, %d changesets, %d total revisions\n") %
231 (len(files), len(cl), revisions))
236 (len(files), len(cl), revisions))
232 if warnings[0]:
237 if warnings[0]:
233 ui.warn(_("%d warnings encountered!\n") % warnings[0])
238 ui.warn(_("%d warnings encountered!\n") % warnings[0])
234 if errors[0]:
239 if errors[0]:
235 ui.warn(_("%d integrity errors encountered!\n") % errors[0])
240 ui.warn(_("%d integrity errors encountered!\n") % errors[0])
236 if badrevs:
241 if badrevs:
237 ui.warn(_("(first damaged changeset appears to be %d)\n")
242 ui.warn(_("(first damaged changeset appears to be %d)\n")
238 % min(badrevs))
243 % min(badrevs))
239 return 1
244 return 1
@@ -1,43 +1,43 b''
1 % init repo1
1 % init repo1
2
2
3 % add a; ci
3 % add a; ci
4 adding a
4 adding a
5
5
6 % cat .hg/store/fncache
6 % cat .hg/store/fncache
7 data/a.i
7 data/a.i
8
8
9 % add a.i/b; ci
9 % add a.i/b; ci
10 adding a.i/b
10 adding a.i/b
11
11
12 % cat .hg/store/fncache
12 % cat .hg/store/fncache
13 data/a.i
13 data/a.i
14 data/a.i.hg/b.i
14 data/a.i.hg/b.i
15
15
16 % add a.i.hg/c; ci
16 % add a.i.hg/c; ci
17 adding a.i.hg/c
17 adding a.i.hg/c
18
18
19 % cat .hg/store/fncache
19 % cat .hg/store/fncache
20 data/a.i
20 data/a.i
21 data/a.i.hg/b.i
21 data/a.i.hg/b.i
22 data/a.i.hg.hg/c.i
22 data/a.i.hg.hg/c.i
23
23
24 % hg verify
24 % hg verify
25 checking changesets
25 checking changesets
26 checking manifests
26 checking manifests
27 crosschecking files in changesets and manifests
27 crosschecking files in changesets and manifests
28 checking files
28 checking files
29 3 files, 3 changesets, 3 total revisions
29 3 files, 3 changesets, 3 total revisions
30
30
31 % rm .hg/store/fncache
31 % rm .hg/store/fncache
32
32
33 % hg verify
33 % hg verify
34 checking changesets
34 checking changesets
35 checking manifests
35 checking manifests
36 crosschecking files in changesets and manifests
36 crosschecking files in changesets and manifests
37 checking files
37 checking files
38 data/a.i@0: missing revlog!
38 data/a.i@0: missing revlog!
39 data/a.i.hg.hg/c.i@0: missing revlog!
39 data/a.i.hg.hg/c.i@2: missing revlog!
40 data/a.i.hg/b.i@0: missing revlog!
40 data/a.i.hg/b.i@1: missing revlog!
41 3 files, 3 changesets, 3 total revisions
41 3 files, 3 changesets, 3 total revisions
42 3 integrity errors encountered!
42 3 integrity errors encountered!
43 (first damaged changeset appears to be 0)
43 (first damaged changeset appears to be 0)
General Comments 0
You need to be logged in to leave comments. Login now