##// END OF EJS Templates
hgweb: better error messages
Dirkjan Ochtman -
r6368:2c370f08 default
parent child Browse files
Show More
@@ -1,975 +1,979 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, re
9 import os, mimetypes, re
10 from mercurial.node import hex, nullid, short
10 from mercurial.node import hex, nullid, short
11 from mercurial.repo import RepoError
11 from mercurial.repo import RepoError
12 from mercurial import mdiff, ui, hg, util, archival, patch, hook
12 from mercurial import mdiff, ui, hg, util, archival, patch, hook
13 from mercurial import revlog, templater, templatefilters, changegroup
13 from mercurial import revlog, templater, templatefilters, changegroup
14 from common import get_mtime, style_map, paritygen, countgen, get_contact
14 from common import get_mtime, style_map, paritygen, countgen, get_contact
15 from common import ErrorResponse
15 from common import ErrorResponse
16 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
17 from request import wsgirequest
17 from request import wsgirequest
18 import webcommands, protocol
18 import webcommands, protocol
19
19
20 shortcuts = {
20 shortcuts = {
21 'cl': [('cmd', ['changelog']), ('rev', None)],
21 'cl': [('cmd', ['changelog']), ('rev', None)],
22 'sl': [('cmd', ['shortlog']), ('rev', None)],
22 'sl': [('cmd', ['shortlog']), ('rev', None)],
23 'cs': [('cmd', ['changeset']), ('node', None)],
23 'cs': [('cmd', ['changeset']), ('node', None)],
24 'f': [('cmd', ['file']), ('filenode', None)],
24 'f': [('cmd', ['file']), ('filenode', None)],
25 'fl': [('cmd', ['filelog']), ('filenode', None)],
25 'fl': [('cmd', ['filelog']), ('filenode', None)],
26 'fd': [('cmd', ['filediff']), ('node', None)],
26 'fd': [('cmd', ['filediff']), ('node', None)],
27 'fa': [('cmd', ['annotate']), ('filenode', None)],
27 'fa': [('cmd', ['annotate']), ('filenode', None)],
28 'mf': [('cmd', ['manifest']), ('manifest', None)],
28 'mf': [('cmd', ['manifest']), ('manifest', None)],
29 'ca': [('cmd', ['archive']), ('node', None)],
29 'ca': [('cmd', ['archive']), ('node', None)],
30 'tags': [('cmd', ['tags'])],
30 'tags': [('cmd', ['tags'])],
31 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
31 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
32 'static': [('cmd', ['static']), ('file', None)]
32 'static': [('cmd', ['static']), ('file', None)]
33 }
33 }
34
34
35 def _up(p):
35 def _up(p):
36 if p[0] != "/":
36 if p[0] != "/":
37 p = "/" + p
37 p = "/" + p
38 if p[-1] == "/":
38 if p[-1] == "/":
39 p = p[:-1]
39 p = p[:-1]
40 up = os.path.dirname(p)
40 up = os.path.dirname(p)
41 if up == "/":
41 if up == "/":
42 return "/"
42 return "/"
43 return up + "/"
43 return up + "/"
44
44
45 def revnavgen(pos, pagelen, limit, nodefunc):
45 def revnavgen(pos, pagelen, limit, nodefunc):
46 def seq(factor, limit=None):
46 def seq(factor, limit=None):
47 if limit:
47 if limit:
48 yield limit
48 yield limit
49 if limit >= 20 and limit <= 40:
49 if limit >= 20 and limit <= 40:
50 yield 50
50 yield 50
51 else:
51 else:
52 yield 1 * factor
52 yield 1 * factor
53 yield 3 * factor
53 yield 3 * factor
54 for f in seq(factor * 10):
54 for f in seq(factor * 10):
55 yield f
55 yield f
56
56
57 def nav(**map):
57 def nav(**map):
58 l = []
58 l = []
59 last = 0
59 last = 0
60 for f in seq(1, pagelen):
60 for f in seq(1, pagelen):
61 if f < pagelen or f <= last:
61 if f < pagelen or f <= last:
62 continue
62 continue
63 if f > limit:
63 if f > limit:
64 break
64 break
65 last = f
65 last = f
66 if pos + f < limit:
66 if pos + f < limit:
67 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
67 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
68 if pos - f >= 0:
68 if pos - f >= 0:
69 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
69 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
70
70
71 try:
71 try:
72 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
72 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
73
73
74 for label, node in l:
74 for label, node in l:
75 yield {"label": label, "node": node}
75 yield {"label": label, "node": node}
76
76
77 yield {"label": "tip", "node": "tip"}
77 yield {"label": "tip", "node": "tip"}
78 except RepoError:
78 except RepoError:
79 pass
79 pass
80
80
81 return nav
81 return nav
82
82
83 class hgweb(object):
83 class hgweb(object):
84 def __init__(self, repo, name=None):
84 def __init__(self, repo, name=None):
85 if isinstance(repo, str):
85 if isinstance(repo, str):
86 parentui = ui.ui(report_untrusted=False, interactive=False)
86 parentui = ui.ui(report_untrusted=False, interactive=False)
87 self.repo = hg.repository(parentui, repo)
87 self.repo = hg.repository(parentui, repo)
88 else:
88 else:
89 self.repo = repo
89 self.repo = repo
90
90
91 hook.redirect(True)
91 hook.redirect(True)
92 self.mtime = -1
92 self.mtime = -1
93 self.reponame = name
93 self.reponame = name
94 self.archives = 'zip', 'gz', 'bz2'
94 self.archives = 'zip', 'gz', 'bz2'
95 self.stripecount = 1
95 self.stripecount = 1
96 self._capabilities = None
96 self._capabilities = None
97 # a repo owner may set web.templates in .hg/hgrc to get any file
97 # a repo owner may set web.templates in .hg/hgrc to get any file
98 # readable by the user running the CGI script
98 # readable by the user running the CGI script
99 self.templatepath = self.config("web", "templates",
99 self.templatepath = self.config("web", "templates",
100 templater.templatepath(),
100 templater.templatepath(),
101 untrusted=False)
101 untrusted=False)
102
102
103 # The CGI scripts are often run by a user different from the repo owner.
103 # The CGI scripts are often run by a user different from the repo owner.
104 # Trust the settings from the .hg/hgrc files by default.
104 # Trust the settings from the .hg/hgrc files by default.
105 def config(self, section, name, default=None, untrusted=True):
105 def config(self, section, name, default=None, untrusted=True):
106 return self.repo.ui.config(section, name, default,
106 return self.repo.ui.config(section, name, default,
107 untrusted=untrusted)
107 untrusted=untrusted)
108
108
109 def configbool(self, section, name, default=False, untrusted=True):
109 def configbool(self, section, name, default=False, untrusted=True):
110 return self.repo.ui.configbool(section, name, default,
110 return self.repo.ui.configbool(section, name, default,
111 untrusted=untrusted)
111 untrusted=untrusted)
112
112
113 def configlist(self, section, name, default=None, untrusted=True):
113 def configlist(self, section, name, default=None, untrusted=True):
114 return self.repo.ui.configlist(section, name, default,
114 return self.repo.ui.configlist(section, name, default,
115 untrusted=untrusted)
115 untrusted=untrusted)
116
116
117 def refresh(self):
117 def refresh(self):
118 mtime = get_mtime(self.repo.root)
118 mtime = get_mtime(self.repo.root)
119 if mtime != self.mtime:
119 if mtime != self.mtime:
120 self.mtime = mtime
120 self.mtime = mtime
121 self.repo = hg.repository(self.repo.ui, self.repo.root)
121 self.repo = hg.repository(self.repo.ui, self.repo.root)
122 self.maxchanges = int(self.config("web", "maxchanges", 10))
122 self.maxchanges = int(self.config("web", "maxchanges", 10))
123 self.stripecount = int(self.config("web", "stripes", 1))
123 self.stripecount = int(self.config("web", "stripes", 1))
124 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
124 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
125 self.maxfiles = int(self.config("web", "maxfiles", 10))
125 self.maxfiles = int(self.config("web", "maxfiles", 10))
126 self.allowpull = self.configbool("web", "allowpull", True)
126 self.allowpull = self.configbool("web", "allowpull", True)
127 self.encoding = self.config("web", "encoding", util._encoding)
127 self.encoding = self.config("web", "encoding", util._encoding)
128 self._capabilities = None
128 self._capabilities = None
129
129
130 def capabilities(self):
130 def capabilities(self):
131 if self._capabilities is not None:
131 if self._capabilities is not None:
132 return self._capabilities
132 return self._capabilities
133 caps = ['lookup', 'changegroupsubset']
133 caps = ['lookup', 'changegroupsubset']
134 if self.configbool('server', 'uncompressed'):
134 if self.configbool('server', 'uncompressed'):
135 caps.append('stream=%d' % self.repo.changelog.version)
135 caps.append('stream=%d' % self.repo.changelog.version)
136 if changegroup.bundlepriority:
136 if changegroup.bundlepriority:
137 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
137 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
138 self._capabilities = caps
138 self._capabilities = caps
139 return caps
139 return caps
140
140
141 def run(self):
141 def run(self):
142 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
142 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
143 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
143 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
144 import mercurial.hgweb.wsgicgi as wsgicgi
144 import mercurial.hgweb.wsgicgi as wsgicgi
145 wsgicgi.launch(self)
145 wsgicgi.launch(self)
146
146
147 def __call__(self, env, respond):
147 def __call__(self, env, respond):
148 req = wsgirequest(env, respond)
148 req = wsgirequest(env, respond)
149 self.run_wsgi(req)
149 self.run_wsgi(req)
150 return req
150 return req
151
151
152 def run_wsgi(self, req):
152 def run_wsgi(self, req):
153
153
154 self.refresh()
154 self.refresh()
155
155
156 # expand form shortcuts
156 # expand form shortcuts
157
157
158 for k in shortcuts.iterkeys():
158 for k in shortcuts.iterkeys():
159 if k in req.form:
159 if k in req.form:
160 for name, value in shortcuts[k]:
160 for name, value in shortcuts[k]:
161 if value is None:
161 if value is None:
162 value = req.form[k]
162 value = req.form[k]
163 req.form[name] = value
163 req.form[name] = value
164 del req.form[k]
164 del req.form[k]
165
165
166 # work with CGI variables to create coherent structure
166 # work with CGI variables to create coherent structure
167 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
167 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
168
168
169 req.url = req.env['SCRIPT_NAME']
169 req.url = req.env['SCRIPT_NAME']
170 if not req.url.endswith('/'):
170 if not req.url.endswith('/'):
171 req.url += '/'
171 req.url += '/'
172 if 'REPO_NAME' in req.env:
172 if 'REPO_NAME' in req.env:
173 req.url += req.env['REPO_NAME'] + '/'
173 req.url += req.env['REPO_NAME'] + '/'
174
174
175 if req.env.get('PATH_INFO'):
175 if req.env.get('PATH_INFO'):
176 parts = req.env.get('PATH_INFO').strip('/').split('/')
176 parts = req.env.get('PATH_INFO').strip('/').split('/')
177 repo_parts = req.env.get('REPO_NAME', '').split('/')
177 repo_parts = req.env.get('REPO_NAME', '').split('/')
178 if parts[:len(repo_parts)] == repo_parts:
178 if parts[:len(repo_parts)] == repo_parts:
179 parts = parts[len(repo_parts):]
179 parts = parts[len(repo_parts):]
180 query = '/'.join(parts)
180 query = '/'.join(parts)
181 else:
181 else:
182 query = req.env['QUERY_STRING'].split('&', 1)[0]
182 query = req.env['QUERY_STRING'].split('&', 1)[0]
183 query = query.split(';', 1)[0]
183 query = query.split(';', 1)[0]
184
184
185 # translate user-visible url structure to internal structure
185 # translate user-visible url structure to internal structure
186
186
187 args = query.split('/', 2)
187 args = query.split('/', 2)
188 if 'cmd' not in req.form and args and args[0]:
188 if 'cmd' not in req.form and args and args[0]:
189
189
190 cmd = args.pop(0)
190 cmd = args.pop(0)
191 style = cmd.rfind('-')
191 style = cmd.rfind('-')
192 if style != -1:
192 if style != -1:
193 req.form['style'] = [cmd[:style]]
193 req.form['style'] = [cmd[:style]]
194 cmd = cmd[style+1:]
194 cmd = cmd[style+1:]
195
195
196 # avoid accepting e.g. style parameter as command
196 # avoid accepting e.g. style parameter as command
197 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
197 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
198 req.form['cmd'] = [cmd]
198 req.form['cmd'] = [cmd]
199
199
200 if args and args[0]:
200 if args and args[0]:
201 node = args.pop(0)
201 node = args.pop(0)
202 req.form['node'] = [node]
202 req.form['node'] = [node]
203 if args:
203 if args:
204 req.form['file'] = args
204 req.form['file'] = args
205
205
206 if cmd == 'static':
206 if cmd == 'static':
207 req.form['file'] = req.form['node']
207 req.form['file'] = req.form['node']
208 elif cmd == 'archive':
208 elif cmd == 'archive':
209 fn = req.form['node'][0]
209 fn = req.form['node'][0]
210 for type_, spec in self.archive_specs.iteritems():
210 for type_, spec in self.archive_specs.iteritems():
211 ext = spec[2]
211 ext = spec[2]
212 if fn.endswith(ext):
212 if fn.endswith(ext):
213 req.form['node'] = [fn[:-len(ext)]]
213 req.form['node'] = [fn[:-len(ext)]]
214 req.form['type'] = [type_]
214 req.form['type'] = [type_]
215
215
216 # process this if it's a protocol request
216 # process this if it's a protocol request
217
217
218 cmd = req.form.get('cmd', [''])[0]
218 cmd = req.form.get('cmd', [''])[0]
219 if cmd in protocol.__all__:
219 if cmd in protocol.__all__:
220 method = getattr(protocol, cmd)
220 method = getattr(protocol, cmd)
221 method(self, req)
221 method(self, req)
222 return
222 return
223
223
224 # process the web interface request
224 # process the web interface request
225
225
226 try:
226 try:
227
227
228 tmpl = self.templater(req)
228 tmpl = self.templater(req)
229 ctype = tmpl('mimetype', encoding=self.encoding)
229 ctype = tmpl('mimetype', encoding=self.encoding)
230 ctype = templater.stringify(ctype)
230 ctype = templater.stringify(ctype)
231
231
232 if cmd == '':
232 if cmd == '':
233 req.form['cmd'] = [tmpl.cache['default']]
233 req.form['cmd'] = [tmpl.cache['default']]
234 cmd = req.form['cmd'][0]
234 cmd = req.form['cmd'][0]
235
235
236 if cmd not in webcommands.__all__:
236 if cmd not in webcommands.__all__:
237 msg = 'No such method: %s' % cmd
237 msg = 'no such method: %s' % cmd
238 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
238 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
239 elif cmd == 'file' and 'raw' in req.form.get('style', []):
239 elif cmd == 'file' and 'raw' in req.form.get('style', []):
240 self.ctype = ctype
240 self.ctype = ctype
241 content = webcommands.rawfile(self, req, tmpl)
241 content = webcommands.rawfile(self, req, tmpl)
242 else:
242 else:
243 content = getattr(webcommands, cmd)(self, req, tmpl)
243 content = getattr(webcommands, cmd)(self, req, tmpl)
244 req.respond(HTTP_OK, ctype)
244 req.respond(HTTP_OK, ctype)
245
245
246 req.write(content)
246 req.write(content)
247 del tmpl
247 del tmpl
248
248
249 except revlog.LookupError, err:
249 except revlog.LookupError, err:
250 req.respond(HTTP_NOT_FOUND, ctype)
250 req.respond(HTTP_NOT_FOUND, ctype)
251 req.write(tmpl('error', error='revision not found: %s' % err.name))
251 if 'manifest' in err.message:
252 msg = str(err)
253 else:
254 msg = 'revision not found: %s' % err.name
255 req.write(tmpl('error', error=msg))
252 except (RepoError, revlog.RevlogError), inst:
256 except (RepoError, revlog.RevlogError), inst:
253 req.respond(HTTP_SERVER_ERROR, ctype)
257 req.respond(HTTP_SERVER_ERROR, ctype)
254 req.write(tmpl('error', error=str(inst)))
258 req.write(tmpl('error', error=str(inst)))
255 except ErrorResponse, inst:
259 except ErrorResponse, inst:
256 req.respond(inst.code, ctype)
260 req.respond(inst.code, ctype)
257 req.write(tmpl('error', error=inst.message))
261 req.write(tmpl('error', error=inst.message))
258
262
259 def templater(self, req):
263 def templater(self, req):
260
264
261 # determine scheme, port and server name
265 # determine scheme, port and server name
262 # this is needed to create absolute urls
266 # this is needed to create absolute urls
263
267
264 proto = req.env.get('wsgi.url_scheme')
268 proto = req.env.get('wsgi.url_scheme')
265 if proto == 'https':
269 if proto == 'https':
266 proto = 'https'
270 proto = 'https'
267 default_port = "443"
271 default_port = "443"
268 else:
272 else:
269 proto = 'http'
273 proto = 'http'
270 default_port = "80"
274 default_port = "80"
271
275
272 port = req.env["SERVER_PORT"]
276 port = req.env["SERVER_PORT"]
273 port = port != default_port and (":" + port) or ""
277 port = port != default_port and (":" + port) or ""
274 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
278 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
275 staticurl = self.config("web", "staticurl") or req.url + 'static/'
279 staticurl = self.config("web", "staticurl") or req.url + 'static/'
276 if not staticurl.endswith('/'):
280 if not staticurl.endswith('/'):
277 staticurl += '/'
281 staticurl += '/'
278
282
279 # some functions for the templater
283 # some functions for the templater
280
284
281 def header(**map):
285 def header(**map):
282 yield tmpl('header', encoding=self.encoding, **map)
286 yield tmpl('header', encoding=self.encoding, **map)
283
287
284 def footer(**map):
288 def footer(**map):
285 yield tmpl("footer", **map)
289 yield tmpl("footer", **map)
286
290
287 def motd(**map):
291 def motd(**map):
288 yield self.config("web", "motd", "")
292 yield self.config("web", "motd", "")
289
293
290 def sessionvars(**map):
294 def sessionvars(**map):
291 fields = []
295 fields = []
292 if 'style' in req.form:
296 if 'style' in req.form:
293 style = req.form['style'][0]
297 style = req.form['style'][0]
294 if style != self.config('web', 'style', ''):
298 if style != self.config('web', 'style', ''):
295 fields.append(('style', style))
299 fields.append(('style', style))
296
300
297 separator = req.url[-1] == '?' and ';' or '?'
301 separator = req.url[-1] == '?' and ';' or '?'
298 for name, value in fields:
302 for name, value in fields:
299 yield dict(name=name, value=value, separator=separator)
303 yield dict(name=name, value=value, separator=separator)
300 separator = ';'
304 separator = ';'
301
305
302 # figure out which style to use
306 # figure out which style to use
303
307
304 style = self.config("web", "style", "")
308 style = self.config("web", "style", "")
305 if 'style' in req.form:
309 if 'style' in req.form:
306 style = req.form['style'][0]
310 style = req.form['style'][0]
307 mapfile = style_map(self.templatepath, style)
311 mapfile = style_map(self.templatepath, style)
308
312
309 if not self.reponame:
313 if not self.reponame:
310 self.reponame = (self.config("web", "name")
314 self.reponame = (self.config("web", "name")
311 or req.env.get('REPO_NAME')
315 or req.env.get('REPO_NAME')
312 or req.url.strip('/') or self.repo.root)
316 or req.url.strip('/') or self.repo.root)
313
317
314 # create the templater
318 # create the templater
315
319
316 tmpl = templater.templater(mapfile, templatefilters.filters,
320 tmpl = templater.templater(mapfile, templatefilters.filters,
317 defaults={"url": req.url,
321 defaults={"url": req.url,
318 "staticurl": staticurl,
322 "staticurl": staticurl,
319 "urlbase": urlbase,
323 "urlbase": urlbase,
320 "repo": self.reponame,
324 "repo": self.reponame,
321 "header": header,
325 "header": header,
322 "footer": footer,
326 "footer": footer,
323 "motd": motd,
327 "motd": motd,
324 "sessionvars": sessionvars
328 "sessionvars": sessionvars
325 })
329 })
326 return tmpl
330 return tmpl
327
331
328 def archivelist(self, nodeid):
332 def archivelist(self, nodeid):
329 allowed = self.configlist("web", "allow_archive")
333 allowed = self.configlist("web", "allow_archive")
330 for i, spec in self.archive_specs.iteritems():
334 for i, spec in self.archive_specs.iteritems():
331 if i in allowed or self.configbool("web", "allow" + i):
335 if i in allowed or self.configbool("web", "allow" + i):
332 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
336 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
333
337
334 def listfilediffs(self, tmpl, files, changeset):
338 def listfilediffs(self, tmpl, files, changeset):
335 for f in files[:self.maxfiles]:
339 for f in files[:self.maxfiles]:
336 yield tmpl("filedifflink", node=hex(changeset), file=f)
340 yield tmpl("filedifflink", node=hex(changeset), file=f)
337 if len(files) > self.maxfiles:
341 if len(files) > self.maxfiles:
338 yield tmpl("fileellipses")
342 yield tmpl("fileellipses")
339
343
340 def siblings(self, siblings=[], hiderev=None, **args):
344 def siblings(self, siblings=[], hiderev=None, **args):
341 siblings = [s for s in siblings if s.node() != nullid]
345 siblings = [s for s in siblings if s.node() != nullid]
342 if len(siblings) == 1 and siblings[0].rev() == hiderev:
346 if len(siblings) == 1 and siblings[0].rev() == hiderev:
343 return
347 return
344 for s in siblings:
348 for s in siblings:
345 d = {'node': hex(s.node()), 'rev': s.rev()}
349 d = {'node': hex(s.node()), 'rev': s.rev()}
346 if hasattr(s, 'path'):
350 if hasattr(s, 'path'):
347 d['file'] = s.path()
351 d['file'] = s.path()
348 d.update(args)
352 d.update(args)
349 yield d
353 yield d
350
354
351 def renamelink(self, fl, node):
355 def renamelink(self, fl, node):
352 r = fl.renamed(node)
356 r = fl.renamed(node)
353 if r:
357 if r:
354 return [dict(file=r[0], node=hex(r[1]))]
358 return [dict(file=r[0], node=hex(r[1]))]
355 return []
359 return []
356
360
357 def nodetagsdict(self, node):
361 def nodetagsdict(self, node):
358 return [{"name": i} for i in self.repo.nodetags(node)]
362 return [{"name": i} for i in self.repo.nodetags(node)]
359
363
360 def nodebranchdict(self, ctx):
364 def nodebranchdict(self, ctx):
361 branches = []
365 branches = []
362 branch = ctx.branch()
366 branch = ctx.branch()
363 # If this is an empty repo, ctx.node() == nullid,
367 # If this is an empty repo, ctx.node() == nullid,
364 # ctx.branch() == 'default', but branchtags() is
368 # ctx.branch() == 'default', but branchtags() is
365 # an empty dict. Using dict.get avoids a traceback.
369 # an empty dict. Using dict.get avoids a traceback.
366 if self.repo.branchtags().get(branch) == ctx.node():
370 if self.repo.branchtags().get(branch) == ctx.node():
367 branches.append({"name": branch})
371 branches.append({"name": branch})
368 return branches
372 return branches
369
373
370 def nodeinbranch(self, ctx):
374 def nodeinbranch(self, ctx):
371 branches = []
375 branches = []
372 branch = ctx.branch()
376 branch = ctx.branch()
373 if branch != 'default' and self.repo.branchtags().get(branch) != ctx.node():
377 if branch != 'default' and self.repo.branchtags().get(branch) != ctx.node():
374 branches.append({"name": branch})
378 branches.append({"name": branch})
375 return branches
379 return branches
376
380
377 def nodebranchnodefault(self, ctx):
381 def nodebranchnodefault(self, ctx):
378 branches = []
382 branches = []
379 branch = ctx.branch()
383 branch = ctx.branch()
380 if branch != 'default':
384 if branch != 'default':
381 branches.append({"name": branch})
385 branches.append({"name": branch})
382 return branches
386 return branches
383
387
384 def showtag(self, tmpl, t1, node=nullid, **args):
388 def showtag(self, tmpl, t1, node=nullid, **args):
385 for t in self.repo.nodetags(node):
389 for t in self.repo.nodetags(node):
386 yield tmpl(t1, tag=t, **args)
390 yield tmpl(t1, tag=t, **args)
387
391
388 def diff(self, tmpl, node1, node2, files):
392 def diff(self, tmpl, node1, node2, files):
389 def filterfiles(filters, files):
393 def filterfiles(filters, files):
390 l = [x for x in files if x in filters]
394 l = [x for x in files if x in filters]
391
395
392 for t in filters:
396 for t in filters:
393 if t and t[-1] != os.sep:
397 if t and t[-1] != os.sep:
394 t += os.sep
398 t += os.sep
395 l += [x for x in files if x.startswith(t)]
399 l += [x for x in files if x.startswith(t)]
396 return l
400 return l
397
401
398 parity = paritygen(self.stripecount)
402 parity = paritygen(self.stripecount)
399 def diffblock(diff, f, fn):
403 def diffblock(diff, f, fn):
400 yield tmpl("diffblock",
404 yield tmpl("diffblock",
401 lines=prettyprintlines(diff),
405 lines=prettyprintlines(diff),
402 parity=parity.next(),
406 parity=parity.next(),
403 file=f,
407 file=f,
404 filenode=hex(fn or nullid))
408 filenode=hex(fn or nullid))
405
409
406 blockcount = countgen()
410 blockcount = countgen()
407 def prettyprintlines(diff):
411 def prettyprintlines(diff):
408 blockno = blockcount.next()
412 blockno = blockcount.next()
409 for lineno, l in enumerate(diff.splitlines(1)):
413 for lineno, l in enumerate(diff.splitlines(1)):
410 if blockno == 0:
414 if blockno == 0:
411 lineno = lineno + 1
415 lineno = lineno + 1
412 else:
416 else:
413 lineno = "%d.%d" % (blockno, lineno + 1)
417 lineno = "%d.%d" % (blockno, lineno + 1)
414 if l.startswith('+'):
418 if l.startswith('+'):
415 ltype = "difflineplus"
419 ltype = "difflineplus"
416 elif l.startswith('-'):
420 elif l.startswith('-'):
417 ltype = "difflineminus"
421 ltype = "difflineminus"
418 elif l.startswith('@'):
422 elif l.startswith('@'):
419 ltype = "difflineat"
423 ltype = "difflineat"
420 else:
424 else:
421 ltype = "diffline"
425 ltype = "diffline"
422 yield tmpl(ltype,
426 yield tmpl(ltype,
423 line=l,
427 line=l,
424 lineid="l%s" % lineno,
428 lineid="l%s" % lineno,
425 linenumber="% 8s" % lineno)
429 linenumber="% 8s" % lineno)
426
430
427 r = self.repo
431 r = self.repo
428 c1 = r.changectx(node1)
432 c1 = r.changectx(node1)
429 c2 = r.changectx(node2)
433 c2 = r.changectx(node2)
430 date1 = util.datestr(c1.date())
434 date1 = util.datestr(c1.date())
431 date2 = util.datestr(c2.date())
435 date2 = util.datestr(c2.date())
432
436
433 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
437 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
434 if files:
438 if files:
435 modified, added, removed = map(lambda x: filterfiles(files, x),
439 modified, added, removed = map(lambda x: filterfiles(files, x),
436 (modified, added, removed))
440 (modified, added, removed))
437
441
438 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
442 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
439 for f in modified:
443 for f in modified:
440 to = c1.filectx(f).data()
444 to = c1.filectx(f).data()
441 tn = c2.filectx(f).data()
445 tn = c2.filectx(f).data()
442 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
446 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
443 opts=diffopts), f, tn)
447 opts=diffopts), f, tn)
444 for f in added:
448 for f in added:
445 to = None
449 to = None
446 tn = c2.filectx(f).data()
450 tn = c2.filectx(f).data()
447 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
451 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
448 opts=diffopts), f, tn)
452 opts=diffopts), f, tn)
449 for f in removed:
453 for f in removed:
450 to = c1.filectx(f).data()
454 to = c1.filectx(f).data()
451 tn = None
455 tn = None
452 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
456 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
453 opts=diffopts), f, tn)
457 opts=diffopts), f, tn)
454
458
455 def changelog(self, tmpl, ctx, shortlog=False):
459 def changelog(self, tmpl, ctx, shortlog=False):
456 def changelist(limit=0,**map):
460 def changelist(limit=0,**map):
457 cl = self.repo.changelog
461 cl = self.repo.changelog
458 l = [] # build a list in forward order for efficiency
462 l = [] # build a list in forward order for efficiency
459 for i in xrange(start, end):
463 for i in xrange(start, end):
460 ctx = self.repo.changectx(i)
464 ctx = self.repo.changectx(i)
461 n = ctx.node()
465 n = ctx.node()
462 showtags = self.showtag(tmpl, 'changelogtag', n)
466 showtags = self.showtag(tmpl, 'changelogtag', n)
463
467
464 l.insert(0, {"parity": parity.next(),
468 l.insert(0, {"parity": parity.next(),
465 "author": ctx.user(),
469 "author": ctx.user(),
466 "parent": self.siblings(ctx.parents(), i - 1),
470 "parent": self.siblings(ctx.parents(), i - 1),
467 "child": self.siblings(ctx.children(), i + 1),
471 "child": self.siblings(ctx.children(), i + 1),
468 "changelogtag": showtags,
472 "changelogtag": showtags,
469 "desc": ctx.description(),
473 "desc": ctx.description(),
470 "date": ctx.date(),
474 "date": ctx.date(),
471 "files": self.listfilediffs(tmpl, ctx.files(), n),
475 "files": self.listfilediffs(tmpl, ctx.files(), n),
472 "rev": i,
476 "rev": i,
473 "node": hex(n),
477 "node": hex(n),
474 "tags": self.nodetagsdict(n),
478 "tags": self.nodetagsdict(n),
475 "inbranch": self.nodeinbranch(ctx),
479 "inbranch": self.nodeinbranch(ctx),
476 "branches": self.nodebranchdict(ctx)})
480 "branches": self.nodebranchdict(ctx)})
477
481
478 if limit > 0:
482 if limit > 0:
479 l = l[:limit]
483 l = l[:limit]
480
484
481 for e in l:
485 for e in l:
482 yield e
486 yield e
483
487
484 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
488 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
485 cl = self.repo.changelog
489 cl = self.repo.changelog
486 count = cl.count()
490 count = cl.count()
487 pos = ctx.rev()
491 pos = ctx.rev()
488 start = max(0, pos - maxchanges + 1)
492 start = max(0, pos - maxchanges + 1)
489 end = min(count, start + maxchanges)
493 end = min(count, start + maxchanges)
490 pos = end - 1
494 pos = end - 1
491 parity = paritygen(self.stripecount, offset=start-end)
495 parity = paritygen(self.stripecount, offset=start-end)
492
496
493 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
497 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
494
498
495 return tmpl(shortlog and 'shortlog' or 'changelog',
499 return tmpl(shortlog and 'shortlog' or 'changelog',
496 changenav=changenav,
500 changenav=changenav,
497 node=hex(cl.tip()),
501 node=hex(cl.tip()),
498 rev=pos, changesets=count,
502 rev=pos, changesets=count,
499 entries=lambda **x: changelist(limit=0,**x),
503 entries=lambda **x: changelist(limit=0,**x),
500 latestentry=lambda **x: changelist(limit=1,**x),
504 latestentry=lambda **x: changelist(limit=1,**x),
501 archives=self.archivelist("tip"))
505 archives=self.archivelist("tip"))
502
506
503 def search(self, tmpl, query):
507 def search(self, tmpl, query):
504
508
505 def changelist(**map):
509 def changelist(**map):
506 cl = self.repo.changelog
510 cl = self.repo.changelog
507 count = 0
511 count = 0
508 qw = query.lower().split()
512 qw = query.lower().split()
509
513
510 def revgen():
514 def revgen():
511 for i in xrange(cl.count() - 1, 0, -100):
515 for i in xrange(cl.count() - 1, 0, -100):
512 l = []
516 l = []
513 for j in xrange(max(0, i - 100), i + 1):
517 for j in xrange(max(0, i - 100), i + 1):
514 ctx = self.repo.changectx(j)
518 ctx = self.repo.changectx(j)
515 l.append(ctx)
519 l.append(ctx)
516 l.reverse()
520 l.reverse()
517 for e in l:
521 for e in l:
518 yield e
522 yield e
519
523
520 for ctx in revgen():
524 for ctx in revgen():
521 miss = 0
525 miss = 0
522 for q in qw:
526 for q in qw:
523 if not (q in ctx.user().lower() or
527 if not (q in ctx.user().lower() or
524 q in ctx.description().lower() or
528 q in ctx.description().lower() or
525 q in " ".join(ctx.files()).lower()):
529 q in " ".join(ctx.files()).lower()):
526 miss = 1
530 miss = 1
527 break
531 break
528 if miss:
532 if miss:
529 continue
533 continue
530
534
531 count += 1
535 count += 1
532 n = ctx.node()
536 n = ctx.node()
533 showtags = self.showtag(tmpl, 'changelogtag', n)
537 showtags = self.showtag(tmpl, 'changelogtag', n)
534
538
535 yield tmpl('searchentry',
539 yield tmpl('searchentry',
536 parity=parity.next(),
540 parity=parity.next(),
537 author=ctx.user(),
541 author=ctx.user(),
538 parent=self.siblings(ctx.parents()),
542 parent=self.siblings(ctx.parents()),
539 child=self.siblings(ctx.children()),
543 child=self.siblings(ctx.children()),
540 changelogtag=showtags,
544 changelogtag=showtags,
541 desc=ctx.description(),
545 desc=ctx.description(),
542 date=ctx.date(),
546 date=ctx.date(),
543 files=self.listfilediffs(tmpl, ctx.files(), n),
547 files=self.listfilediffs(tmpl, ctx.files(), n),
544 rev=ctx.rev(),
548 rev=ctx.rev(),
545 node=hex(n),
549 node=hex(n),
546 tags=self.nodetagsdict(n),
550 tags=self.nodetagsdict(n),
547 inbranch=self.nodeinbranch(ctx),
551 inbranch=self.nodeinbranch(ctx),
548 branches=self.nodebranchdict(ctx))
552 branches=self.nodebranchdict(ctx))
549
553
550 if count >= self.maxchanges:
554 if count >= self.maxchanges:
551 break
555 break
552
556
553 cl = self.repo.changelog
557 cl = self.repo.changelog
554 parity = paritygen(self.stripecount)
558 parity = paritygen(self.stripecount)
555
559
556 return tmpl('search',
560 return tmpl('search',
557 query=query,
561 query=query,
558 node=hex(cl.tip()),
562 node=hex(cl.tip()),
559 entries=changelist,
563 entries=changelist,
560 archives=self.archivelist("tip"))
564 archives=self.archivelist("tip"))
561
565
562 def changeset(self, tmpl, ctx):
566 def changeset(self, tmpl, ctx):
563 n = ctx.node()
567 n = ctx.node()
564 showtags = self.showtag(tmpl, 'changesettag', n)
568 showtags = self.showtag(tmpl, 'changesettag', n)
565 parents = ctx.parents()
569 parents = ctx.parents()
566 p1 = parents[0].node()
570 p1 = parents[0].node()
567
571
568 files = []
572 files = []
569 parity = paritygen(self.stripecount)
573 parity = paritygen(self.stripecount)
570 for f in ctx.files():
574 for f in ctx.files():
571 files.append(tmpl("filenodelink",
575 files.append(tmpl("filenodelink",
572 node=hex(n), file=f,
576 node=hex(n), file=f,
573 parity=parity.next()))
577 parity=parity.next()))
574
578
575 def diff(**map):
579 def diff(**map):
576 yield self.diff(tmpl, p1, n, None)
580 yield self.diff(tmpl, p1, n, None)
577
581
578 return tmpl('changeset',
582 return tmpl('changeset',
579 diff=diff,
583 diff=diff,
580 rev=ctx.rev(),
584 rev=ctx.rev(),
581 node=hex(n),
585 node=hex(n),
582 parent=self.siblings(parents),
586 parent=self.siblings(parents),
583 child=self.siblings(ctx.children()),
587 child=self.siblings(ctx.children()),
584 changesettag=showtags,
588 changesettag=showtags,
585 author=ctx.user(),
589 author=ctx.user(),
586 desc=ctx.description(),
590 desc=ctx.description(),
587 date=ctx.date(),
591 date=ctx.date(),
588 files=files,
592 files=files,
589 archives=self.archivelist(hex(n)),
593 archives=self.archivelist(hex(n)),
590 tags=self.nodetagsdict(n),
594 tags=self.nodetagsdict(n),
591 branch=self.nodebranchnodefault(ctx),
595 branch=self.nodebranchnodefault(ctx),
592 inbranch=self.nodeinbranch(ctx),
596 inbranch=self.nodeinbranch(ctx),
593 branches=self.nodebranchdict(ctx))
597 branches=self.nodebranchdict(ctx))
594
598
595 def filelog(self, tmpl, fctx):
599 def filelog(self, tmpl, fctx):
596 f = fctx.path()
600 f = fctx.path()
597 fl = fctx.filelog()
601 fl = fctx.filelog()
598 count = fl.count()
602 count = fl.count()
599 pagelen = self.maxshortchanges
603 pagelen = self.maxshortchanges
600 pos = fctx.filerev()
604 pos = fctx.filerev()
601 start = max(0, pos - pagelen + 1)
605 start = max(0, pos - pagelen + 1)
602 end = min(count, start + pagelen)
606 end = min(count, start + pagelen)
603 pos = end - 1
607 pos = end - 1
604 parity = paritygen(self.stripecount, offset=start-end)
608 parity = paritygen(self.stripecount, offset=start-end)
605
609
606 def entries(limit=0, **map):
610 def entries(limit=0, **map):
607 l = []
611 l = []
608
612
609 for i in xrange(start, end):
613 for i in xrange(start, end):
610 ctx = fctx.filectx(i)
614 ctx = fctx.filectx(i)
611 n = fl.node(i)
615 n = fl.node(i)
612
616
613 l.insert(0, {"parity": parity.next(),
617 l.insert(0, {"parity": parity.next(),
614 "filerev": i,
618 "filerev": i,
615 "file": f,
619 "file": f,
616 "node": hex(ctx.node()),
620 "node": hex(ctx.node()),
617 "author": ctx.user(),
621 "author": ctx.user(),
618 "date": ctx.date(),
622 "date": ctx.date(),
619 "rename": self.renamelink(fl, n),
623 "rename": self.renamelink(fl, n),
620 "parent": self.siblings(fctx.parents()),
624 "parent": self.siblings(fctx.parents()),
621 "child": self.siblings(fctx.children()),
625 "child": self.siblings(fctx.children()),
622 "desc": ctx.description()})
626 "desc": ctx.description()})
623
627
624 if limit > 0:
628 if limit > 0:
625 l = l[:limit]
629 l = l[:limit]
626
630
627 for e in l:
631 for e in l:
628 yield e
632 yield e
629
633
630 nodefunc = lambda x: fctx.filectx(fileid=x)
634 nodefunc = lambda x: fctx.filectx(fileid=x)
631 nav = revnavgen(pos, pagelen, count, nodefunc)
635 nav = revnavgen(pos, pagelen, count, nodefunc)
632 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
636 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
633 entries=lambda **x: entries(limit=0, **x),
637 entries=lambda **x: entries(limit=0, **x),
634 latestentry=lambda **x: entries(limit=1, **x))
638 latestentry=lambda **x: entries(limit=1, **x))
635
639
636 def filerevision(self, tmpl, fctx):
640 def filerevision(self, tmpl, fctx):
637 f = fctx.path()
641 f = fctx.path()
638 text = fctx.data()
642 text = fctx.data()
639 fl = fctx.filelog()
643 fl = fctx.filelog()
640 n = fctx.filenode()
644 n = fctx.filenode()
641 parity = paritygen(self.stripecount)
645 parity = paritygen(self.stripecount)
642
646
643 if util.binary(text):
647 if util.binary(text):
644 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
648 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
645 text = '(binary:%s)' % mt
649 text = '(binary:%s)' % mt
646
650
647 def lines():
651 def lines():
648 for lineno, t in enumerate(text.splitlines(1)):
652 for lineno, t in enumerate(text.splitlines(1)):
649 yield {"line": t,
653 yield {"line": t,
650 "lineid": "l%d" % (lineno + 1),
654 "lineid": "l%d" % (lineno + 1),
651 "linenumber": "% 6d" % (lineno + 1),
655 "linenumber": "% 6d" % (lineno + 1),
652 "parity": parity.next()}
656 "parity": parity.next()}
653
657
654 return tmpl("filerevision",
658 return tmpl("filerevision",
655 file=f,
659 file=f,
656 path=_up(f),
660 path=_up(f),
657 text=lines(),
661 text=lines(),
658 rev=fctx.rev(),
662 rev=fctx.rev(),
659 node=hex(fctx.node()),
663 node=hex(fctx.node()),
660 author=fctx.user(),
664 author=fctx.user(),
661 date=fctx.date(),
665 date=fctx.date(),
662 desc=fctx.description(),
666 desc=fctx.description(),
663 branch=self.nodebranchnodefault(fctx),
667 branch=self.nodebranchnodefault(fctx),
664 parent=self.siblings(fctx.parents()),
668 parent=self.siblings(fctx.parents()),
665 child=self.siblings(fctx.children()),
669 child=self.siblings(fctx.children()),
666 rename=self.renamelink(fl, n),
670 rename=self.renamelink(fl, n),
667 permissions=fctx.manifest().flags(f))
671 permissions=fctx.manifest().flags(f))
668
672
669 def fileannotate(self, tmpl, fctx):
673 def fileannotate(self, tmpl, fctx):
670 f = fctx.path()
674 f = fctx.path()
671 n = fctx.filenode()
675 n = fctx.filenode()
672 fl = fctx.filelog()
676 fl = fctx.filelog()
673 parity = paritygen(self.stripecount)
677 parity = paritygen(self.stripecount)
674
678
675 def annotate(**map):
679 def annotate(**map):
676 last = None
680 last = None
677 if util.binary(fctx.data()):
681 if util.binary(fctx.data()):
678 mt = (mimetypes.guess_type(fctx.path())[0]
682 mt = (mimetypes.guess_type(fctx.path())[0]
679 or 'application/octet-stream')
683 or 'application/octet-stream')
680 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
684 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
681 '(binary:%s)' % mt)])
685 '(binary:%s)' % mt)])
682 else:
686 else:
683 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
687 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
684 for lineno, ((f, targetline), l) in lines:
688 for lineno, ((f, targetline), l) in lines:
685 fnode = f.filenode()
689 fnode = f.filenode()
686 name = self.repo.ui.shortuser(f.user())
690 name = self.repo.ui.shortuser(f.user())
687
691
688 if last != fnode:
692 if last != fnode:
689 last = fnode
693 last = fnode
690
694
691 yield {"parity": parity.next(),
695 yield {"parity": parity.next(),
692 "node": hex(f.node()),
696 "node": hex(f.node()),
693 "rev": f.rev(),
697 "rev": f.rev(),
694 "author": name,
698 "author": name,
695 "file": f.path(),
699 "file": f.path(),
696 "targetline": targetline,
700 "targetline": targetline,
697 "line": l,
701 "line": l,
698 "lineid": "l%d" % (lineno + 1),
702 "lineid": "l%d" % (lineno + 1),
699 "linenumber": "% 6d" % (lineno + 1)}
703 "linenumber": "% 6d" % (lineno + 1)}
700
704
701 return tmpl("fileannotate",
705 return tmpl("fileannotate",
702 file=f,
706 file=f,
703 annotate=annotate,
707 annotate=annotate,
704 path=_up(f),
708 path=_up(f),
705 rev=fctx.rev(),
709 rev=fctx.rev(),
706 node=hex(fctx.node()),
710 node=hex(fctx.node()),
707 author=fctx.user(),
711 author=fctx.user(),
708 date=fctx.date(),
712 date=fctx.date(),
709 desc=fctx.description(),
713 desc=fctx.description(),
710 rename=self.renamelink(fl, n),
714 rename=self.renamelink(fl, n),
711 branch=self.nodebranchnodefault(fctx),
715 branch=self.nodebranchnodefault(fctx),
712 parent=self.siblings(fctx.parents()),
716 parent=self.siblings(fctx.parents()),
713 child=self.siblings(fctx.children()),
717 child=self.siblings(fctx.children()),
714 permissions=fctx.manifest().flags(f))
718 permissions=fctx.manifest().flags(f))
715
719
716 def manifest(self, tmpl, ctx, path):
720 def manifest(self, tmpl, ctx, path):
717 mf = ctx.manifest()
721 mf = ctx.manifest()
718 node = ctx.node()
722 node = ctx.node()
719
723
720 files = {}
724 files = {}
721 parity = paritygen(self.stripecount)
725 parity = paritygen(self.stripecount)
722
726
723 if path and path[-1] != "/":
727 if path and path[-1] != "/":
724 path += "/"
728 path += "/"
725 l = len(path)
729 l = len(path)
726 abspath = "/" + path
730 abspath = "/" + path
727
731
728 for f, n in mf.items():
732 for f, n in mf.items():
729 if f[:l] != path:
733 if f[:l] != path:
730 continue
734 continue
731 remain = f[l:]
735 remain = f[l:]
732 if "/" in remain:
736 if "/" in remain:
733 short = remain[:remain.index("/") + 1] # bleah
737 short = remain[:remain.index("/") + 1] # bleah
734 files[short] = (f, None)
738 files[short] = (f, None)
735 else:
739 else:
736 short = os.path.basename(remain)
740 short = os.path.basename(remain)
737 files[short] = (f, n)
741 files[short] = (f, n)
738
742
739 if not files:
743 if not files:
740 raise ErrorResponse(HTTP_NOT_FOUND, 'Path not found: ' + path)
744 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
741
745
742 def filelist(**map):
746 def filelist(**map):
743 fl = files.keys()
747 fl = files.keys()
744 fl.sort()
748 fl.sort()
745 for f in fl:
749 for f in fl:
746 full, fnode = files[f]
750 full, fnode = files[f]
747 if not fnode:
751 if not fnode:
748 continue
752 continue
749
753
750 fctx = ctx.filectx(full)
754 fctx = ctx.filectx(full)
751 yield {"file": full,
755 yield {"file": full,
752 "parity": parity.next(),
756 "parity": parity.next(),
753 "basename": f,
757 "basename": f,
754 "date": fctx.changectx().date(),
758 "date": fctx.changectx().date(),
755 "size": fctx.size(),
759 "size": fctx.size(),
756 "permissions": mf.flags(full)}
760 "permissions": mf.flags(full)}
757
761
758 def dirlist(**map):
762 def dirlist(**map):
759 fl = files.keys()
763 fl = files.keys()
760 fl.sort()
764 fl.sort()
761 for f in fl:
765 for f in fl:
762 full, fnode = files[f]
766 full, fnode = files[f]
763 if fnode:
767 if fnode:
764 continue
768 continue
765
769
766 yield {"parity": parity.next(),
770 yield {"parity": parity.next(),
767 "path": "%s%s" % (abspath, f),
771 "path": "%s%s" % (abspath, f),
768 "basename": f[:-1]}
772 "basename": f[:-1]}
769
773
770 return tmpl("manifest",
774 return tmpl("manifest",
771 rev=ctx.rev(),
775 rev=ctx.rev(),
772 node=hex(node),
776 node=hex(node),
773 path=abspath,
777 path=abspath,
774 up=_up(abspath),
778 up=_up(abspath),
775 upparity=parity.next(),
779 upparity=parity.next(),
776 fentries=filelist,
780 fentries=filelist,
777 dentries=dirlist,
781 dentries=dirlist,
778 archives=self.archivelist(hex(node)),
782 archives=self.archivelist(hex(node)),
779 tags=self.nodetagsdict(node),
783 tags=self.nodetagsdict(node),
780 inbranch=self.nodeinbranch(ctx),
784 inbranch=self.nodeinbranch(ctx),
781 branches=self.nodebranchdict(ctx))
785 branches=self.nodebranchdict(ctx))
782
786
783 def tags(self, tmpl):
787 def tags(self, tmpl):
784 i = self.repo.tagslist()
788 i = self.repo.tagslist()
785 i.reverse()
789 i.reverse()
786 parity = paritygen(self.stripecount)
790 parity = paritygen(self.stripecount)
787
791
788 def entries(notip=False,limit=0, **map):
792 def entries(notip=False,limit=0, **map):
789 count = 0
793 count = 0
790 for k, n in i:
794 for k, n in i:
791 if notip and k == "tip":
795 if notip and k == "tip":
792 continue
796 continue
793 if limit > 0 and count >= limit:
797 if limit > 0 and count >= limit:
794 continue
798 continue
795 count = count + 1
799 count = count + 1
796 yield {"parity": parity.next(),
800 yield {"parity": parity.next(),
797 "tag": k,
801 "tag": k,
798 "date": self.repo.changectx(n).date(),
802 "date": self.repo.changectx(n).date(),
799 "node": hex(n)}
803 "node": hex(n)}
800
804
801 return tmpl("tags",
805 return tmpl("tags",
802 node=hex(self.repo.changelog.tip()),
806 node=hex(self.repo.changelog.tip()),
803 entries=lambda **x: entries(False,0, **x),
807 entries=lambda **x: entries(False,0, **x),
804 entriesnotip=lambda **x: entries(True,0, **x),
808 entriesnotip=lambda **x: entries(True,0, **x),
805 latestentry=lambda **x: entries(True,1, **x))
809 latestentry=lambda **x: entries(True,1, **x))
806
810
807 def summary(self, tmpl):
811 def summary(self, tmpl):
808 i = self.repo.tagslist()
812 i = self.repo.tagslist()
809 i.reverse()
813 i.reverse()
810
814
811 def tagentries(**map):
815 def tagentries(**map):
812 parity = paritygen(self.stripecount)
816 parity = paritygen(self.stripecount)
813 count = 0
817 count = 0
814 for k, n in i:
818 for k, n in i:
815 if k == "tip": # skip tip
819 if k == "tip": # skip tip
816 continue;
820 continue;
817
821
818 count += 1
822 count += 1
819 if count > 10: # limit to 10 tags
823 if count > 10: # limit to 10 tags
820 break;
824 break;
821
825
822 yield tmpl("tagentry",
826 yield tmpl("tagentry",
823 parity=parity.next(),
827 parity=parity.next(),
824 tag=k,
828 tag=k,
825 node=hex(n),
829 node=hex(n),
826 date=self.repo.changectx(n).date())
830 date=self.repo.changectx(n).date())
827
831
828
832
829 def branches(**map):
833 def branches(**map):
830 parity = paritygen(self.stripecount)
834 parity = paritygen(self.stripecount)
831
835
832 b = self.repo.branchtags()
836 b = self.repo.branchtags()
833 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
837 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
834 l.sort()
838 l.sort()
835
839
836 for r,n,t in l:
840 for r,n,t in l:
837 ctx = self.repo.changectx(n)
841 ctx = self.repo.changectx(n)
838
842
839 yield {'parity': parity.next(),
843 yield {'parity': parity.next(),
840 'branch': t,
844 'branch': t,
841 'node': hex(n),
845 'node': hex(n),
842 'date': ctx.date()}
846 'date': ctx.date()}
843
847
844 def changelist(**map):
848 def changelist(**map):
845 parity = paritygen(self.stripecount, offset=start-end)
849 parity = paritygen(self.stripecount, offset=start-end)
846 l = [] # build a list in forward order for efficiency
850 l = [] # build a list in forward order for efficiency
847 for i in xrange(start, end):
851 for i in xrange(start, end):
848 ctx = self.repo.changectx(i)
852 ctx = self.repo.changectx(i)
849 n = ctx.node()
853 n = ctx.node()
850 hn = hex(n)
854 hn = hex(n)
851
855
852 l.insert(0, tmpl(
856 l.insert(0, tmpl(
853 'shortlogentry',
857 'shortlogentry',
854 parity=parity.next(),
858 parity=parity.next(),
855 author=ctx.user(),
859 author=ctx.user(),
856 desc=ctx.description(),
860 desc=ctx.description(),
857 date=ctx.date(),
861 date=ctx.date(),
858 rev=i,
862 rev=i,
859 node=hn,
863 node=hn,
860 tags=self.nodetagsdict(n),
864 tags=self.nodetagsdict(n),
861 inbranch=self.nodeinbranch(ctx),
865 inbranch=self.nodeinbranch(ctx),
862 branches=self.nodebranchdict(ctx)))
866 branches=self.nodebranchdict(ctx)))
863
867
864 yield l
868 yield l
865
869
866 cl = self.repo.changelog
870 cl = self.repo.changelog
867 count = cl.count()
871 count = cl.count()
868 start = max(0, count - self.maxchanges)
872 start = max(0, count - self.maxchanges)
869 end = min(count, start + self.maxchanges)
873 end = min(count, start + self.maxchanges)
870
874
871 return tmpl("summary",
875 return tmpl("summary",
872 desc=self.config("web", "description", "unknown"),
876 desc=self.config("web", "description", "unknown"),
873 owner=get_contact(self.config) or "unknown",
877 owner=get_contact(self.config) or "unknown",
874 lastchange=cl.read(cl.tip())[2],
878 lastchange=cl.read(cl.tip())[2],
875 tags=tagentries,
879 tags=tagentries,
876 branches=branches,
880 branches=branches,
877 shortlog=changelist,
881 shortlog=changelist,
878 node=hex(cl.tip()),
882 node=hex(cl.tip()),
879 archives=self.archivelist("tip"))
883 archives=self.archivelist("tip"))
880
884
881 def filediff(self, tmpl, fctx):
885 def filediff(self, tmpl, fctx):
882 n = fctx.node()
886 n = fctx.node()
883 path = fctx.path()
887 path = fctx.path()
884 parents = fctx.parents()
888 parents = fctx.parents()
885 p1 = parents and parents[0].node() or nullid
889 p1 = parents and parents[0].node() or nullid
886
890
887 def diff(**map):
891 def diff(**map):
888 yield self.diff(tmpl, p1, n, [path])
892 yield self.diff(tmpl, p1, n, [path])
889
893
890 return tmpl("filediff",
894 return tmpl("filediff",
891 file=path,
895 file=path,
892 node=hex(n),
896 node=hex(n),
893 rev=fctx.rev(),
897 rev=fctx.rev(),
894 branch=self.nodebranchnodefault(fctx),
898 branch=self.nodebranchnodefault(fctx),
895 parent=self.siblings(parents),
899 parent=self.siblings(parents),
896 child=self.siblings(fctx.children()),
900 child=self.siblings(fctx.children()),
897 diff=diff)
901 diff=diff)
898
902
899 archive_specs = {
903 archive_specs = {
900 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
904 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
901 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
905 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
902 'zip': ('application/zip', 'zip', '.zip', None),
906 'zip': ('application/zip', 'zip', '.zip', None),
903 }
907 }
904
908
905 def archive(self, tmpl, req, key, type_):
909 def archive(self, tmpl, req, key, type_):
906 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
910 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
907 cnode = self.repo.lookup(key)
911 cnode = self.repo.lookup(key)
908 arch_version = key
912 arch_version = key
909 if cnode == key or key == 'tip':
913 if cnode == key or key == 'tip':
910 arch_version = short(cnode)
914 arch_version = short(cnode)
911 name = "%s-%s" % (reponame, arch_version)
915 name = "%s-%s" % (reponame, arch_version)
912 mimetype, artype, extension, encoding = self.archive_specs[type_]
916 mimetype, artype, extension, encoding = self.archive_specs[type_]
913 headers = [
917 headers = [
914 ('Content-Type', mimetype),
918 ('Content-Type', mimetype),
915 ('Content-Disposition', 'attachment; filename=%s%s' %
919 ('Content-Disposition', 'attachment; filename=%s%s' %
916 (name, extension))
920 (name, extension))
917 ]
921 ]
918 if encoding:
922 if encoding:
919 headers.append(('Content-Encoding', encoding))
923 headers.append(('Content-Encoding', encoding))
920 req.header(headers)
924 req.header(headers)
921 req.respond(HTTP_OK)
925 req.respond(HTTP_OK)
922 archival.archive(self.repo, req, cnode, artype, prefix=name)
926 archival.archive(self.repo, req, cnode, artype, prefix=name)
923
927
924 # add tags to things
928 # add tags to things
925 # tags -> list of changesets corresponding to tags
929 # tags -> list of changesets corresponding to tags
926 # find tag, changeset, file
930 # find tag, changeset, file
927
931
928 def cleanpath(self, path):
932 def cleanpath(self, path):
929 path = path.lstrip('/')
933 path = path.lstrip('/')
930 return util.canonpath(self.repo.root, '', path)
934 return util.canonpath(self.repo.root, '', path)
931
935
932 def changectx(self, req):
936 def changectx(self, req):
933 if 'node' in req.form:
937 if 'node' in req.form:
934 changeid = req.form['node'][0]
938 changeid = req.form['node'][0]
935 elif 'manifest' in req.form:
939 elif 'manifest' in req.form:
936 changeid = req.form['manifest'][0]
940 changeid = req.form['manifest'][0]
937 else:
941 else:
938 changeid = self.repo.changelog.count() - 1
942 changeid = self.repo.changelog.count() - 1
939
943
940 try:
944 try:
941 ctx = self.repo.changectx(changeid)
945 ctx = self.repo.changectx(changeid)
942 except RepoError:
946 except RepoError:
943 man = self.repo.manifest
947 man = self.repo.manifest
944 mn = man.lookup(changeid)
948 mn = man.lookup(changeid)
945 ctx = self.repo.changectx(man.linkrev(mn))
949 ctx = self.repo.changectx(man.linkrev(mn))
946
950
947 return ctx
951 return ctx
948
952
949 def filectx(self, req):
953 def filectx(self, req):
950 path = self.cleanpath(req.form['file'][0])
954 path = self.cleanpath(req.form['file'][0])
951 if 'node' in req.form:
955 if 'node' in req.form:
952 changeid = req.form['node'][0]
956 changeid = req.form['node'][0]
953 else:
957 else:
954 changeid = req.form['filenode'][0]
958 changeid = req.form['filenode'][0]
955 try:
959 try:
956 ctx = self.repo.changectx(changeid)
960 ctx = self.repo.changectx(changeid)
957 fctx = ctx.filectx(path)
961 fctx = ctx.filectx(path)
958 except RepoError:
962 except RepoError:
959 fctx = self.repo.filectx(path, fileid=changeid)
963 fctx = self.repo.filectx(path, fileid=changeid)
960
964
961 return fctx
965 return fctx
962
966
963 def check_perm(self, req, op, default):
967 def check_perm(self, req, op, default):
964 '''check permission for operation based on user auth.
968 '''check permission for operation based on user auth.
965 return true if op allowed, else false.
969 return true if op allowed, else false.
966 default is policy to use if no config given.'''
970 default is policy to use if no config given.'''
967
971
968 user = req.env.get('REMOTE_USER')
972 user = req.env.get('REMOTE_USER')
969
973
970 deny = self.configlist('web', 'deny_' + op)
974 deny = self.configlist('web', 'deny_' + op)
971 if deny and (not user or deny == ['*'] or user in deny):
975 if deny and (not user or deny == ['*'] or user in deny):
972 return False
976 return False
973
977
974 allow = self.configlist('web', 'allow_' + op)
978 allow = self.configlist('web', 'allow_' + op)
975 return (allow and (allow == ['*'] or user in allow)) or default
979 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,121 +1,127 b''
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-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 import os, mimetypes
8 import os, mimetypes
9 from mercurial import revlog, util
9 from mercurial import revlog, util
10 from mercurial.repo import RepoError
10 from mercurial.repo import RepoError
11 from common import staticfile, ErrorResponse, HTTP_OK, HTTP_NOT_FOUND
11 from common import staticfile, ErrorResponse, HTTP_OK, HTTP_NOT_FOUND
12
12
13 # __all__ is populated with the allowed commands. Be sure to add to it if
13 # __all__ is populated with the allowed commands. Be sure to add to it if
14 # you're adding a new command, or the new command won't work.
14 # you're adding a new command, or the new command won't work.
15
15
16 __all__ = [
16 __all__ = [
17 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
17 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
18 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
18 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
19 'archive', 'static',
19 'archive', 'static',
20 ]
20 ]
21
21
22 def log(web, req, tmpl):
22 def log(web, req, tmpl):
23 if 'file' in req.form and req.form['file'][0]:
23 if 'file' in req.form and req.form['file'][0]:
24 return filelog(web, req, tmpl)
24 return filelog(web, req, tmpl)
25 else:
25 else:
26 return changelog(web, req, tmpl)
26 return changelog(web, req, tmpl)
27
27
28 def rawfile(web, req, tmpl):
28 def rawfile(web, req, tmpl):
29 path = web.cleanpath(req.form.get('file', [''])[0])
29 path = web.cleanpath(req.form.get('file', [''])[0])
30 if not path:
30 if not path:
31 content = web.manifest(tmpl, web.changectx(req), path)
31 content = web.manifest(tmpl, web.changectx(req), path)
32 req.respond(HTTP_OK, web.ctype)
32 req.respond(HTTP_OK, web.ctype)
33 return content
33 return content
34
34
35 try:
35 try:
36 fctx = web.filectx(req)
36 fctx = web.filectx(req)
37 except revlog.LookupError:
37 except revlog.LookupError, inst:
38 content = web.manifest(tmpl, web.changectx(req), path)
38 try:
39 req.respond(HTTP_OK, web.ctype)
39 content = web.manifest(tmpl, web.changectx(req), path)
40 return content
40 req.respond(HTTP_OK, web.ctype)
41 return content
42 except ErrorResponse:
43 raise inst
41
44
42 path = fctx.path()
45 path = fctx.path()
43 text = fctx.data()
46 text = fctx.data()
44 mt = mimetypes.guess_type(path)[0]
47 mt = mimetypes.guess_type(path)[0]
45 if mt is None or util.binary(text):
48 if mt is None or util.binary(text):
46 mt = mt or 'application/octet-stream'
49 mt = mt or 'application/octet-stream'
47
50
48 req.respond(HTTP_OK, mt, path, len(text))
51 req.respond(HTTP_OK, mt, path, len(text))
49 return [text]
52 return [text]
50
53
51 def file(web, req, tmpl):
54 def file(web, req, tmpl):
52 path = web.cleanpath(req.form.get('file', [''])[0])
55 path = web.cleanpath(req.form.get('file', [''])[0])
53 if path:
56 if path:
54 try:
57 try:
55 return web.filerevision(tmpl, web.filectx(req))
58 return web.filerevision(tmpl, web.filectx(req))
56 except revlog.LookupError:
59 except revlog.LookupError, inst:
57 pass
60 pass
58
61
59 return web.manifest(tmpl, web.changectx(req), path)
62 try:
63 return web.manifest(tmpl, web.changectx(req), path)
64 except ErrorResponse:
65 raise inst
60
66
61 def changelog(web, req, tmpl, shortlog = False):
67 def changelog(web, req, tmpl, shortlog = False):
62 if 'node' in req.form:
68 if 'node' in req.form:
63 ctx = web.changectx(req)
69 ctx = web.changectx(req)
64 else:
70 else:
65 if 'rev' in req.form:
71 if 'rev' in req.form:
66 hi = req.form['rev'][0]
72 hi = req.form['rev'][0]
67 else:
73 else:
68 hi = web.repo.changelog.count() - 1
74 hi = web.repo.changelog.count() - 1
69 try:
75 try:
70 ctx = web.repo.changectx(hi)
76 ctx = web.repo.changectx(hi)
71 except RepoError:
77 except RepoError:
72 return web.search(tmpl, hi) # XXX redirect to 404 page?
78 return web.search(tmpl, hi) # XXX redirect to 404 page?
73
79
74 return web.changelog(tmpl, ctx, shortlog = shortlog)
80 return web.changelog(tmpl, ctx, shortlog = shortlog)
75
81
76 def shortlog(web, req, tmpl):
82 def shortlog(web, req, tmpl):
77 return changelog(web, req, tmpl, shortlog = True)
83 return changelog(web, req, tmpl, shortlog = True)
78
84
79 def changeset(web, req, tmpl):
85 def changeset(web, req, tmpl):
80 return web.changeset(tmpl, web.changectx(req))
86 return web.changeset(tmpl, web.changectx(req))
81
87
82 rev = changeset
88 rev = changeset
83
89
84 def manifest(web, req, tmpl):
90 def manifest(web, req, tmpl):
85 return web.manifest(tmpl, web.changectx(req),
91 return web.manifest(tmpl, web.changectx(req),
86 web.cleanpath(req.form['path'][0]))
92 web.cleanpath(req.form['path'][0]))
87
93
88 def tags(web, req, tmpl):
94 def tags(web, req, tmpl):
89 return web.tags(tmpl)
95 return web.tags(tmpl)
90
96
91 def summary(web, req, tmpl):
97 def summary(web, req, tmpl):
92 return web.summary(tmpl)
98 return web.summary(tmpl)
93
99
94 def filediff(web, req, tmpl):
100 def filediff(web, req, tmpl):
95 return web.filediff(tmpl, web.filectx(req))
101 return web.filediff(tmpl, web.filectx(req))
96
102
97 diff = filediff
103 diff = filediff
98
104
99 def annotate(web, req, tmpl):
105 def annotate(web, req, tmpl):
100 return web.fileannotate(tmpl, web.filectx(req))
106 return web.fileannotate(tmpl, web.filectx(req))
101
107
102 def filelog(web, req, tmpl):
108 def filelog(web, req, tmpl):
103 return web.filelog(tmpl, web.filectx(req))
109 return web.filelog(tmpl, web.filectx(req))
104
110
105 def archive(web, req, tmpl):
111 def archive(web, req, tmpl):
106 type_ = req.form['type'][0]
112 type_ = req.form['type'][0]
107 allowed = web.configlist("web", "allow_archive")
113 allowed = web.configlist("web", "allow_archive")
108 if (type_ in web.archives and (type_ in allowed or
114 if (type_ in web.archives and (type_ in allowed or
109 web.configbool("web", "allow" + type_, False))):
115 web.configbool("web", "allow" + type_, False))):
110 web.archive(tmpl, req, req.form['node'][0], type_)
116 web.archive(tmpl, req, req.form['node'][0], type_)
111 return []
117 return []
112 raise ErrorResponse(HTTP_NOT_FOUND, 'Unsupported archive type: %s' % type_)
118 raise ErrorResponse(HTTP_NOT_FOUND, 'unsupported archive type: %s' % type_)
113
119
114 def static(web, req, tmpl):
120 def static(web, req, tmpl):
115 fname = req.form['file'][0]
121 fname = req.form['file'][0]
116 # a repo owner may set web.static in .hg/hgrc to get any file
122 # a repo owner may set web.static in .hg/hgrc to get any file
117 # readable by the user running the CGI script
123 # readable by the user running the CGI script
118 static = web.config("web", "static",
124 static = web.config("web", "static",
119 os.path.join(web.templatepath, "static"),
125 os.path.join(web.templatepath, "static"),
120 untrusted=False)
126 untrusted=False)
121 return [staticfile(static, fname, req)]
127 return [staticfile(static, fname, req)]
@@ -1,42 +1,44 b''
1 #!/bin/sh
1 #!/bin/sh
2 # Some tests for hgweb. Tests static files, plain files and different 404's.
2 # Some tests for hgweb. Tests static files, plain files and different 404's.
3
3
4 hg init test
4 hg init test
5 cd test
5 cd test
6 mkdir da
6 mkdir da
7 echo foo > da/foo
7 echo foo > da/foo
8 echo foo > foo
8 echo foo > foo
9 hg ci -Ambase -d '0 0'
9 hg ci -Ambase -d '0 0'
10 hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
10 hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
11 cat hg.pid >> $DAEMON_PIDS
11 cat hg.pid >> $DAEMON_PIDS
12 echo % manifest
12 echo % manifest
13 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/?style=raw')
13 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/?style=raw')
14 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/da?style=raw')
14 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/da?style=raw')
15
15
16 echo % plain file
16 echo % plain file
17 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/foo?style=raw'
17 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/foo?style=raw'
18
18
19 echo % should give a 404 - static file that does not exist
19 echo % should give a 404 - static file that does not exist
20 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/static/bogus'
20 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/static/bogus'
21
21
22 echo % should give a 404 - bad revision
22 echo % should give a 404 - bad revision
23 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/spam/foo?style=raw'
23 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/spam/foo?style=raw'
24
24
25 echo % should give a 400 - bad command
25 echo % should give a 400 - bad command
26 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/foo?cmd=spam&style=raw' | sed 's/400.*/400/'
26 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/foo?cmd=spam&style=raw' | sed 's/400.*/400/'
27
27
28 echo % should give a 404 - file does not exist
28 echo % should give a 404 - file does not exist
29 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/bork?style=raw'
29 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/bork?style=raw'
30 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/bork'
31 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/diff/tip/bork?style=raw'
30
32
31 echo % stop and restart
33 echo % stop and restart
32 kill `cat hg.pid`
34 kill `cat hg.pid`
33 hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
35 hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
34 cat hg.pid >> $DAEMON_PIDS
36 cat hg.pid >> $DAEMON_PIDS
35 # Test the access/error files are opened in append mode
37 # Test the access/error files are opened in append mode
36 python -c "print len(file('access.log').readlines()), 'log lines written'"
38 python -c "print len(file('access.log').readlines()), 'log lines written'"
37
39
38 echo % static file
40 echo % static file
39 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/static/style-gitweb.css'
41 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/static/style-gitweb.css'
40
42
41 echo % errors
43 echo % errors
42 cat errors.log
44 cat errors.log
@@ -1,154 +1,189 b''
1 adding da/foo
1 adding da/foo
2 adding foo
2 adding foo
3 % manifest
3 % manifest
4 200 Script output follows
4 200 Script output follows
5
5
6
6
7 drwxr-xr-x da
7 drwxr-xr-x da
8 -rw-r--r-- 4 foo
8 -rw-r--r-- 4 foo
9
9
10
10
11 200 Script output follows
11 200 Script output follows
12
12
13
13
14 -rw-r--r-- 4 foo
14 -rw-r--r-- 4 foo
15
15
16
16
17 % plain file
17 % plain file
18 200 Script output follows
18 200 Script output follows
19
19
20 foo
20 foo
21 % should give a 404 - static file that does not exist
21 % should give a 404 - static file that does not exist
22 404 Not Found
22 404 Not Found
23
23
24 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
24 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
25 <html>
25 <html>
26 <head>
26 <head>
27 <link rel="icon" href="/static/hgicon.png" type="image/png">
27 <link rel="icon" href="/static/hgicon.png" type="image/png">
28 <meta name="robots" content="index, nofollow" />
28 <meta name="robots" content="index, nofollow" />
29 <link rel="stylesheet" href="/static/style.css" type="text/css" />
29 <link rel="stylesheet" href="/static/style.css" type="text/css" />
30
30
31 <title>Mercurial Error</title>
31 <title>Mercurial Error</title>
32 </head>
32 </head>
33 <body>
33 <body>
34
34
35 <h2>Mercurial Error</h2>
35 <h2>Mercurial Error</h2>
36
36
37 <p>
37 <p>
38 An error occurred while processing your request:
38 An error occurred while processing your request:
39 </p>
39 </p>
40 <p>
40 <p>
41 Not Found
41 Not Found
42 </p>
42 </p>
43
43
44
44
45 <div class="logo">
45 <div class="logo">
46 <a href="http://www.selenic.com/mercurial/">
46 <a href="http://www.selenic.com/mercurial/">
47 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
47 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
48 </div>
48 </div>
49
49
50 </body>
50 </body>
51 </html>
51 </html>
52
52
53 % should give a 404 - bad revision
53 % should give a 404 - bad revision
54 404 Not Found
54 404 Not Found
55
55
56
56
57 error: revision not found: spam
57 error: revision not found: spam
58 % should give a 400 - bad command
58 % should give a 400 - bad command
59 400
59 400
60
60
61
61
62 error: No such method: spam
62 error: no such method: spam
63 % should give a 404 - file does not exist
63 % should give a 404 - file does not exist
64 404 Not Found
64 404 Not Found
65
65
66
66
67 error: Path not found: bork/
67 error: bork@2ef0ac749a14: not found in manifest
68 404 Not Found
69
70 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
71 <html>
72 <head>
73 <link rel="icon" href="/static/hgicon.png" type="image/png">
74 <meta name="robots" content="index, nofollow" />
75 <link rel="stylesheet" href="/static/style.css" type="text/css" />
76
77 <title>Mercurial Error</title>
78 </head>
79 <body>
80
81 <h2>Mercurial Error</h2>
82
83 <p>
84 An error occurred while processing your request:
85 </p>
86 <p>
87 bork@2ef0ac749a14: not found in manifest
88 </p>
89
90
91 <div class="logo">
92 <a href="http://www.selenic.com/mercurial/">
93 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
94 </div>
95
96 </body>
97 </html>
98
99 404 Not Found
100
101
102 error: bork@2ef0ac749a14: not found in manifest
68 % stop and restart
103 % stop and restart
69 7 log lines written
104 9 log lines written
70 % static file
105 % static file
71 200 Script output follows
106 200 Script output follows
72
107
73 body { font-family: sans-serif; font-size: 12px; margin:0px; border:solid #d9d8d1; border-width:1px; margin:10px; }
108 body { font-family: sans-serif; font-size: 12px; margin:0px; border:solid #d9d8d1; border-width:1px; margin:10px; }
74 a { color:#0000cc; }
109 a { color:#0000cc; }
75 a:hover, a:visited, a:active { color:#880000; }
110 a:hover, a:visited, a:active { color:#880000; }
76 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
111 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
77 div.page_header a:visited { color:#0000cc; }
112 div.page_header a:visited { color:#0000cc; }
78 div.page_header a:hover { color:#880000; }
113 div.page_header a:hover { color:#880000; }
79 div.page_nav { padding:8px; }
114 div.page_nav { padding:8px; }
80 div.page_nav a:visited { color:#0000cc; }
115 div.page_nav a:visited { color:#0000cc; }
81 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
116 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
82 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
117 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
83 div.page_footer_text { float:left; color:#555555; font-style:italic; }
118 div.page_footer_text { float:left; color:#555555; font-style:italic; }
84 div.page_body { padding:8px; }
119 div.page_body { padding:8px; }
85 div.title, a.title {
120 div.title, a.title {
86 display:block; padding:6px 8px;
121 display:block; padding:6px 8px;
87 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
122 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
88 }
123 }
89 a.title:hover { background-color: #d9d8d1; }
124 a.title:hover { background-color: #d9d8d1; }
90 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
125 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
91 div.log_body { padding:8px 8px 8px 150px; }
126 div.log_body { padding:8px 8px 8px 150px; }
92 .age { white-space:nowrap; }
127 .age { white-space:nowrap; }
93 span.age { position:relative; float:left; width:142px; font-style:italic; }
128 span.age { position:relative; float:left; width:142px; font-style:italic; }
94 div.log_link {
129 div.log_link {
95 padding:0px 8px;
130 padding:0px 8px;
96 font-size:10px; font-family:sans-serif; font-style:normal;
131 font-size:10px; font-family:sans-serif; font-style:normal;
97 position:relative; float:left; width:136px;
132 position:relative; float:left; width:136px;
98 }
133 }
99 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
134 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
100 a.list { text-decoration:none; color:#000000; }
135 a.list { text-decoration:none; color:#000000; }
101 a.list:hover { text-decoration:underline; color:#880000; }
136 a.list:hover { text-decoration:underline; color:#880000; }
102 table { padding:8px 4px; }
137 table { padding:8px 4px; }
103 th { padding:2px 5px; font-size:12px; text-align:left; }
138 th { padding:2px 5px; font-size:12px; text-align:left; }
104 tr.light:hover, .parity0:hover { background-color:#edece6; }
139 tr.light:hover, .parity0:hover { background-color:#edece6; }
105 tr.dark, .parity1 { background-color:#f6f6f0; }
140 tr.dark, .parity1 { background-color:#f6f6f0; }
106 tr.dark:hover, .parity1:hover { background-color:#edece6; }
141 tr.dark:hover, .parity1:hover { background-color:#edece6; }
107 td { padding:2px 5px; font-size:12px; vertical-align:top; }
142 td { padding:2px 5px; font-size:12px; vertical-align:top; }
108 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
143 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
109 td.indexlinks { white-space: nowrap; }
144 td.indexlinks { white-space: nowrap; }
110 td.indexlinks a {
145 td.indexlinks a {
111 padding: 2px 5px; line-height: 10px;
146 padding: 2px 5px; line-height: 10px;
112 border: 1px solid;
147 border: 1px solid;
113 color: #ffffff; background-color: #7777bb;
148 color: #ffffff; background-color: #7777bb;
114 border-color: #aaaadd #333366 #333366 #aaaadd;
149 border-color: #aaaadd #333366 #333366 #aaaadd;
115 font-weight: bold; text-align: center; text-decoration: none;
150 font-weight: bold; text-align: center; text-decoration: none;
116 font-size: 10px;
151 font-size: 10px;
117 }
152 }
118 td.indexlinks a:hover { background-color: #6666aa; }
153 td.indexlinks a:hover { background-color: #6666aa; }
119 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
154 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
120 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
155 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
121 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
156 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
122 div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
157 div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
123 .linenr { color:#999999; text-decoration:none }
158 .linenr { color:#999999; text-decoration:none }
124 div.rss_logo { float: right; white-space: nowrap; }
159 div.rss_logo { float: right; white-space: nowrap; }
125 div.rss_logo a {
160 div.rss_logo a {
126 padding:3px 6px; line-height:10px;
161 padding:3px 6px; line-height:10px;
127 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
162 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
128 color:#ffffff; background-color:#ff6600;
163 color:#ffffff; background-color:#ff6600;
129 font-weight:bold; font-family:sans-serif; font-size:10px;
164 font-weight:bold; font-family:sans-serif; font-size:10px;
130 text-align:center; text-decoration:none;
165 text-align:center; text-decoration:none;
131 }
166 }
132 div.rss_logo a:hover { background-color:#ee5500; }
167 div.rss_logo a:hover { background-color:#ee5500; }
133 pre { margin: 0; }
168 pre { margin: 0; }
134 span.logtags span {
169 span.logtags span {
135 padding: 0px 4px;
170 padding: 0px 4px;
136 font-size: 10px;
171 font-size: 10px;
137 font-weight: normal;
172 font-weight: normal;
138 border: 1px solid;
173 border: 1px solid;
139 background-color: #ffaaff;
174 background-color: #ffaaff;
140 border-color: #ffccff #ff00ee #ff00ee #ffccff;
175 border-color: #ffccff #ff00ee #ff00ee #ffccff;
141 }
176 }
142 span.logtags span.tagtag {
177 span.logtags span.tagtag {
143 background-color: #ffffaa;
178 background-color: #ffffaa;
144 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
179 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
145 }
180 }
146 span.logtags span.branchtag {
181 span.logtags span.branchtag {
147 background-color: #aaffaa;
182 background-color: #aaffaa;
148 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
183 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
149 }
184 }
150 span.logtags span.inbranchtag {
185 span.logtags span.inbranchtag {
151 background-color: #d5dde6;
186 background-color: #d5dde6;
152 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
187 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
153 }
188 }
154 % errors
189 % errors
@@ -1,124 +1,124 b''
1 adding a
1 adding a
2 adding b
2 adding b
3 adding c
3 adding c
4 % should give a 404 - file does not exist
4 % should give a 404 - file does not exist
5 404 Not Found
5 404 Not Found
6
6
7
7
8 error: Path not found: bork/
8 error: bork@8580ff50825a: not found in manifest
9 % should succeed
9 % should succeed
10 200 Script output follows
10 200 Script output follows
11
11
12
12
13 /a/
13 /a/
14 /b/
14 /b/
15
15
16 200 Script output follows
16 200 Script output follows
17
17
18 a
18 a
19 200 Script output follows
19 200 Script output follows
20
20
21 b
21 b
22 % should give a 404 - repo is not published
22 % should give a 404 - repo is not published
23 404 Not Found
23 404 Not Found
24
24
25
25
26 error: repository c not found
26 error: repository c not found
27 % should succeed, slashy names
27 % should succeed, slashy names
28 200 Script output follows
28 200 Script output follows
29
29
30
30
31 /b/
31 /b/
32 /t/a/
32 /t/a/
33
33
34 200 Script output follows
34 200 Script output follows
35
35
36
36
37 /t/a/
37 /t/a/
38
38
39 200 Script output follows
39 200 Script output follows
40
40
41
41
42 /t/a/
42 /t/a/
43
43
44 200 Script output follows
44 200 Script output follows
45
45
46 <?xml version="1.0" encoding="ascii"?>
46 <?xml version="1.0" encoding="ascii"?>
47 <feed xmlns="http://127.0.0.1/2005/Atom">
47 <feed xmlns="http://127.0.0.1/2005/Atom">
48 <!-- Changelog -->
48 <!-- Changelog -->
49 <id>http://127.0.0.1/t/a/</id>
49 <id>http://127.0.0.1/t/a/</id>
50 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
50 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
51 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
51 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
52 <title>t/a Changelog</title>
52 <title>t/a Changelog</title>
53 <updated>1970-01-01T00:00:01+00:00</updated>
53 <updated>1970-01-01T00:00:01+00:00</updated>
54
54
55 <entry>
55 <entry>
56 <title>a</title>
56 <title>a</title>
57 <id>http://127.0.0.1/mercurial/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
57 <id>http://127.0.0.1/mercurial/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
58 <link href="http://127.0.0.1/t/a/rev/8580ff50825a50c8f716709acdf8de0deddcd6ab"/>
58 <link href="http://127.0.0.1/t/a/rev/8580ff50825a50c8f716709acdf8de0deddcd6ab"/>
59 <author>
59 <author>
60 <name>test</name>
60 <name>test</name>
61 <email>&#116;&#101;&#115;&#116;</email>
61 <email>&#116;&#101;&#115;&#116;</email>
62 </author>
62 </author>
63 <updated>1970-01-01T00:00:01+00:00</updated>
63 <updated>1970-01-01T00:00:01+00:00</updated>
64 <published>1970-01-01T00:00:01+00:00</published>
64 <published>1970-01-01T00:00:01+00:00</published>
65 <content type="xhtml">
65 <content type="xhtml">
66 <div xmlns="http://127.0.0.1/1999/xhtml">
66 <div xmlns="http://127.0.0.1/1999/xhtml">
67 <pre xml:space="preserve">a</pre>
67 <pre xml:space="preserve">a</pre>
68 </div>
68 </div>
69 </content>
69 </content>
70 </entry>
70 </entry>
71
71
72 </feed>
72 </feed>
73 200 Script output follows
73 200 Script output follows
74
74
75 <?xml version="1.0" encoding="ascii"?>
75 <?xml version="1.0" encoding="ascii"?>
76 <feed xmlns="http://127.0.0.1/2005/Atom">
76 <feed xmlns="http://127.0.0.1/2005/Atom">
77 <!-- Changelog -->
77 <!-- Changelog -->
78 <id>http://127.0.0.1/t/a/</id>
78 <id>http://127.0.0.1/t/a/</id>
79 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
79 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
80 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
80 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
81 <title>t/a Changelog</title>
81 <title>t/a Changelog</title>
82 <updated>1970-01-01T00:00:01+00:00</updated>
82 <updated>1970-01-01T00:00:01+00:00</updated>
83
83
84 <entry>
84 <entry>
85 <title>a</title>
85 <title>a</title>
86 <id>http://127.0.0.1/mercurial/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
86 <id>http://127.0.0.1/mercurial/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
87 <link href="http://127.0.0.1/t/a/rev/8580ff50825a50c8f716709acdf8de0deddcd6ab"/>
87 <link href="http://127.0.0.1/t/a/rev/8580ff50825a50c8f716709acdf8de0deddcd6ab"/>
88 <author>
88 <author>
89 <name>test</name>
89 <name>test</name>
90 <email>&#116;&#101;&#115;&#116;</email>
90 <email>&#116;&#101;&#115;&#116;</email>
91 </author>
91 </author>
92 <updated>1970-01-01T00:00:01+00:00</updated>
92 <updated>1970-01-01T00:00:01+00:00</updated>
93 <published>1970-01-01T00:00:01+00:00</published>
93 <published>1970-01-01T00:00:01+00:00</published>
94 <content type="xhtml">
94 <content type="xhtml">
95 <div xmlns="http://127.0.0.1/1999/xhtml">
95 <div xmlns="http://127.0.0.1/1999/xhtml">
96 <pre xml:space="preserve">a</pre>
96 <pre xml:space="preserve">a</pre>
97 </div>
97 </div>
98 </content>
98 </content>
99 </entry>
99 </entry>
100
100
101 </feed>
101 </feed>
102 200 Script output follows
102 200 Script output follows
103
103
104 a
104 a
105 % should succeed
105 % should succeed
106 200 Script output follows
106 200 Script output follows
107
107
108
108
109 /a/
109 /a/
110 /b/
110 /b/
111 /c/
111 /c/
112
112
113 200 Script output follows
113 200 Script output follows
114
114
115 a
115 a
116 200 Script output follows
116 200 Script output follows
117
117
118 b
118 b
119 200 Script output follows
119 200 Script output follows
120
120
121 c
121 c
122 % paths errors 1
122 % paths errors 1
123 % paths errors 2
123 % paths errors 2
124 % collections errors
124 % collections errors
General Comments 0
You need to be logged in to leave comments. Login now