##// END OF EJS Templates
hgweb: fix a stupid KeyError introduced in a0e20a5eba3c...
Dirkjan Ochtman -
r5923:f39110af default
parent child Browse files
Show More
@@ -1,908 +1,908 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, mimetools, cStringIO
9 import os, mimetypes, re, mimetools, cStringIO
10 from mercurial.node import *
10 from mercurial.node import *
11 from mercurial import mdiff, ui, hg, util, archival, patch, hook
11 from mercurial import mdiff, ui, hg, util, archival, patch, hook
12 from mercurial import revlog, templater
12 from mercurial import revlog, templater
13 from common import ErrorResponse, get_mtime, style_map, paritygen, get_contact
13 from common import ErrorResponse, get_mtime, style_map, paritygen, get_contact
14 from request import wsgirequest
14 from request import wsgirequest
15 import webcommands, protocol
15 import webcommands, protocol
16
16
17 shortcuts = {
17 shortcuts = {
18 'cl': [('cmd', ['changelog']), ('rev', None)],
18 'cl': [('cmd', ['changelog']), ('rev', None)],
19 'sl': [('cmd', ['shortlog']), ('rev', None)],
19 'sl': [('cmd', ['shortlog']), ('rev', None)],
20 'cs': [('cmd', ['changeset']), ('node', None)],
20 'cs': [('cmd', ['changeset']), ('node', None)],
21 'f': [('cmd', ['file']), ('filenode', None)],
21 'f': [('cmd', ['file']), ('filenode', None)],
22 'fl': [('cmd', ['filelog']), ('filenode', None)],
22 'fl': [('cmd', ['filelog']), ('filenode', None)],
23 'fd': [('cmd', ['filediff']), ('node', None)],
23 'fd': [('cmd', ['filediff']), ('node', None)],
24 'fa': [('cmd', ['annotate']), ('filenode', None)],
24 'fa': [('cmd', ['annotate']), ('filenode', None)],
25 'mf': [('cmd', ['manifest']), ('manifest', None)],
25 'mf': [('cmd', ['manifest']), ('manifest', None)],
26 'ca': [('cmd', ['archive']), ('node', None)],
26 'ca': [('cmd', ['archive']), ('node', None)],
27 'tags': [('cmd', ['tags'])],
27 'tags': [('cmd', ['tags'])],
28 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
28 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
29 'static': [('cmd', ['static']), ('file', None)]
29 'static': [('cmd', ['static']), ('file', None)]
30 }
30 }
31
31
32 def _up(p):
32 def _up(p):
33 if p[0] != "/":
33 if p[0] != "/":
34 p = "/" + p
34 p = "/" + p
35 if p[-1] == "/":
35 if p[-1] == "/":
36 p = p[:-1]
36 p = p[:-1]
37 up = os.path.dirname(p)
37 up = os.path.dirname(p)
38 if up == "/":
38 if up == "/":
39 return "/"
39 return "/"
40 return up + "/"
40 return up + "/"
41
41
42 def revnavgen(pos, pagelen, limit, nodefunc):
42 def revnavgen(pos, pagelen, limit, nodefunc):
43 def seq(factor, limit=None):
43 def seq(factor, limit=None):
44 if limit:
44 if limit:
45 yield limit
45 yield limit
46 if limit >= 20 and limit <= 40:
46 if limit >= 20 and limit <= 40:
47 yield 50
47 yield 50
48 else:
48 else:
49 yield 1 * factor
49 yield 1 * factor
50 yield 3 * factor
50 yield 3 * factor
51 for f in seq(factor * 10):
51 for f in seq(factor * 10):
52 yield f
52 yield f
53
53
54 def nav(**map):
54 def nav(**map):
55 l = []
55 l = []
56 last = 0
56 last = 0
57 for f in seq(1, pagelen):
57 for f in seq(1, pagelen):
58 if f < pagelen or f <= last:
58 if f < pagelen or f <= last:
59 continue
59 continue
60 if f > limit:
60 if f > limit:
61 break
61 break
62 last = f
62 last = f
63 if pos + f < limit:
63 if pos + f < limit:
64 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
64 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
65 if pos - f >= 0:
65 if pos - f >= 0:
66 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
66 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
67
67
68 try:
68 try:
69 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
69 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
70
70
71 for label, node in l:
71 for label, node in l:
72 yield {"label": label, "node": node}
72 yield {"label": label, "node": node}
73
73
74 yield {"label": "tip", "node": "tip"}
74 yield {"label": "tip", "node": "tip"}
75 except hg.RepoError:
75 except hg.RepoError:
76 pass
76 pass
77
77
78 return nav
78 return nav
79
79
80 class hgweb(object):
80 class hgweb(object):
81 def __init__(self, repo, name=None):
81 def __init__(self, repo, name=None):
82 if isinstance(repo, str):
82 if isinstance(repo, str):
83 parentui = ui.ui(report_untrusted=False, interactive=False)
83 parentui = ui.ui(report_untrusted=False, interactive=False)
84 self.repo = hg.repository(parentui, repo)
84 self.repo = hg.repository(parentui, repo)
85 else:
85 else:
86 self.repo = repo
86 self.repo = repo
87
87
88 hook.redirect(True)
88 hook.redirect(True)
89 self.mtime = -1
89 self.mtime = -1
90 self.reponame = name
90 self.reponame = name
91 self.archives = 'zip', 'gz', 'bz2'
91 self.archives = 'zip', 'gz', 'bz2'
92 self.stripecount = 1
92 self.stripecount = 1
93 # a repo owner may set web.templates in .hg/hgrc to get any file
93 # a repo owner may set web.templates in .hg/hgrc to get any file
94 # readable by the user running the CGI script
94 # readable by the user running the CGI script
95 self.templatepath = self.config("web", "templates",
95 self.templatepath = self.config("web", "templates",
96 templater.templatepath(),
96 templater.templatepath(),
97 untrusted=False)
97 untrusted=False)
98
98
99 # The CGI scripts are often run by a user different from the repo owner.
99 # The CGI scripts are often run by a user different from the repo owner.
100 # Trust the settings from the .hg/hgrc files by default.
100 # Trust the settings from the .hg/hgrc files by default.
101 def config(self, section, name, default=None, untrusted=True):
101 def config(self, section, name, default=None, untrusted=True):
102 return self.repo.ui.config(section, name, default,
102 return self.repo.ui.config(section, name, default,
103 untrusted=untrusted)
103 untrusted=untrusted)
104
104
105 def configbool(self, section, name, default=False, untrusted=True):
105 def configbool(self, section, name, default=False, untrusted=True):
106 return self.repo.ui.configbool(section, name, default,
106 return self.repo.ui.configbool(section, name, default,
107 untrusted=untrusted)
107 untrusted=untrusted)
108
108
109 def configlist(self, section, name, default=None, untrusted=True):
109 def configlist(self, section, name, default=None, untrusted=True):
110 return self.repo.ui.configlist(section, name, default,
110 return self.repo.ui.configlist(section, name, default,
111 untrusted=untrusted)
111 untrusted=untrusted)
112
112
113 def refresh(self):
113 def refresh(self):
114 mtime = get_mtime(self.repo.root)
114 mtime = get_mtime(self.repo.root)
115 if mtime != self.mtime:
115 if mtime != self.mtime:
116 self.mtime = mtime
116 self.mtime = mtime
117 self.repo = hg.repository(self.repo.ui, self.repo.root)
117 self.repo = hg.repository(self.repo.ui, self.repo.root)
118 self.maxchanges = int(self.config("web", "maxchanges", 10))
118 self.maxchanges = int(self.config("web", "maxchanges", 10))
119 self.stripecount = int(self.config("web", "stripes", 1))
119 self.stripecount = int(self.config("web", "stripes", 1))
120 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
120 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
121 self.maxfiles = int(self.config("web", "maxfiles", 10))
121 self.maxfiles = int(self.config("web", "maxfiles", 10))
122 self.allowpull = self.configbool("web", "allowpull", True)
122 self.allowpull = self.configbool("web", "allowpull", True)
123 self.encoding = self.config("web", "encoding", util._encoding)
123 self.encoding = self.config("web", "encoding", util._encoding)
124
124
125 def run(self):
125 def run(self):
126 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
126 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
127 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
127 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
128 import mercurial.hgweb.wsgicgi as wsgicgi
128 import mercurial.hgweb.wsgicgi as wsgicgi
129 wsgicgi.launch(self)
129 wsgicgi.launch(self)
130
130
131 def __call__(self, env, respond):
131 def __call__(self, env, respond):
132 req = wsgirequest(env, respond)
132 req = wsgirequest(env, respond)
133 self.run_wsgi(req)
133 self.run_wsgi(req)
134 return req
134 return req
135
135
136 def run_wsgi(self, req):
136 def run_wsgi(self, req):
137
137
138 self.refresh()
138 self.refresh()
139
139
140 # expand form shortcuts
140 # expand form shortcuts
141
141
142 for k in shortcuts.iterkeys():
142 for k in shortcuts.iterkeys():
143 if k in req.form:
143 if k in req.form:
144 for name, value in shortcuts[k]:
144 for name, value in shortcuts[k]:
145 if value is None:
145 if value is None:
146 value = req.form[k]
146 value = req.form[k]
147 req.form[name] = value
147 req.form[name] = value
148 del req.form[k]
148 del req.form[k]
149
149
150 # work with CGI variables to create coherent structure
150 # work with CGI variables to create coherent structure
151 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
151 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
152
152
153 req.url = req.env['SCRIPT_NAME']
153 req.url = req.env['SCRIPT_NAME']
154 if not req.url.endswith('/'):
154 if not req.url.endswith('/'):
155 req.url += '/'
155 req.url += '/'
156 if 'REPO_NAME' in req.env:
156 if 'REPO_NAME' in req.env:
157 req.url += req.env['REPO_NAME'] + '/'
157 req.url += req.env['REPO_NAME'] + '/'
158
158
159 if req.env.get('PATH_INFO'):
159 if req.env.get('PATH_INFO'):
160 parts = req.env.get('PATH_INFO').strip('/').split('/')
160 parts = req.env.get('PATH_INFO').strip('/').split('/')
161 repo_parts = req.env.get('REPO_NAME', '').split('/')
161 repo_parts = req.env.get('REPO_NAME', '').split('/')
162 if parts[:len(repo_parts)] == repo_parts:
162 if parts[:len(repo_parts)] == repo_parts:
163 parts = parts[len(repo_parts):]
163 parts = parts[len(repo_parts):]
164 query = '/'.join(parts)
164 query = '/'.join(parts)
165 else:
165 else:
166 query = req.env['QUERY_STRING'].split('&', 1)[0]
166 query = req.env['QUERY_STRING'].split('&', 1)[0]
167 query = query.split(';', 1)[0]
167 query = query.split(';', 1)[0]
168
168
169 # translate user-visible url structure to internal structure
169 # translate user-visible url structure to internal structure
170
170
171 args = query.split('/', 2)
171 args = query.split('/', 2)
172 if 'cmd' not in req.form and args and args[0]:
172 if 'cmd' not in req.form and args and args[0]:
173
173
174 cmd = args.pop(0)
174 cmd = args.pop(0)
175 style = cmd.rfind('-')
175 style = cmd.rfind('-')
176 if style != -1:
176 if style != -1:
177 req.form['style'] = [cmd[:style]]
177 req.form['style'] = [cmd[:style]]
178 cmd = cmd[style+1:]
178 cmd = cmd[style+1:]
179
179
180 # avoid accepting e.g. style parameter as command
180 # avoid accepting e.g. style parameter as command
181 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
181 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
182 req.form['cmd'] = [cmd]
182 req.form['cmd'] = [cmd]
183
183
184 if args and args[0]:
184 if args and args[0]:
185 node = args.pop(0)
185 node = args.pop(0)
186 req.form['node'] = [node]
186 req.form['node'] = [node]
187 if args:
187 if args:
188 req.form['file'] = args
188 req.form['file'] = args
189
189
190 if cmd == 'static':
190 if cmd == 'static':
191 req.form['file'] = req.form['node']
191 req.form['file'] = req.form['node']
192 elif cmd == 'archive':
192 elif cmd == 'archive':
193 fn = req.form['node'][0]
193 fn = req.form['node'][0]
194 for type_, spec in self.archive_specs.iteritems():
194 for type_, spec in self.archive_specs.iteritems():
195 ext = spec[2]
195 ext = spec[2]
196 if fn.endswith(ext):
196 if fn.endswith(ext):
197 req.form['node'] = [fn[:-len(ext)]]
197 req.form['node'] = [fn[:-len(ext)]]
198 req.form['type'] = [type_]
198 req.form['type'] = [type_]
199
199
200 # actually process the request
200 # actually process the request
201
201
202 try:
202 try:
203
203
204 cmd = req.form.get('cmd', [''])[0]
204 cmd = req.form.get('cmd', [''])[0]
205 if hasattr(protocol, cmd):
205 if hasattr(protocol, cmd):
206 method = getattr(protocol, cmd)
206 method = getattr(protocol, cmd)
207 method(self, req)
207 method(self, req)
208 else:
208 else:
209
209
210 tmpl = self.templater(req)
210 tmpl = self.templater(req)
211 if cmd == '':
211 if cmd == '':
212 req.form['cmd'] = [tmpl.cache['default']]
212 req.form['cmd'] = [tmpl.cache['default']]
213 cmd = req.form['cmd'][0]
213 cmd = req.form['cmd'][0]
214
214
215 if cmd == 'file' and 'raw' in req.form['style']:
215 if cmd == 'file' and 'raw' in req.form.get('style', []):
216 webcommands.rawfile(self, req, tmpl)
216 webcommands.rawfile(self, req, tmpl)
217 else:
217 else:
218 getattr(webcommands, cmd)(self, req, tmpl)
218 getattr(webcommands, cmd)(self, req, tmpl)
219
219
220 del tmpl
220 del tmpl
221
221
222 except revlog.LookupError, err:
222 except revlog.LookupError, err:
223 req.respond(404, tmpl(
223 req.respond(404, tmpl(
224 'error', error='revision not found: %s' % err.name))
224 'error', error='revision not found: %s' % err.name))
225 except (hg.RepoError, revlog.RevlogError), inst:
225 except (hg.RepoError, revlog.RevlogError), inst:
226 req.respond('500 Internal Server Error',
226 req.respond('500 Internal Server Error',
227 tmpl('error', error=str(inst)))
227 tmpl('error', error=str(inst)))
228 except ErrorResponse, inst:
228 except ErrorResponse, inst:
229 req.respond(inst.code, tmpl('error', error=inst.message))
229 req.respond(inst.code, tmpl('error', error=inst.message))
230 except AttributeError:
230 except AttributeError:
231 req.respond(400, tmpl('error', error='No such method: ' + cmd))
231 req.respond(400, tmpl('error', error='No such method: ' + cmd))
232
232
233 def templater(self, req):
233 def templater(self, req):
234
234
235 # determine scheme, port and server name
235 # determine scheme, port and server name
236 # this is needed to create absolute urls
236 # this is needed to create absolute urls
237
237
238 proto = req.env.get('wsgi.url_scheme')
238 proto = req.env.get('wsgi.url_scheme')
239 if proto == 'https':
239 if proto == 'https':
240 proto = 'https'
240 proto = 'https'
241 default_port = "443"
241 default_port = "443"
242 else:
242 else:
243 proto = 'http'
243 proto = 'http'
244 default_port = "80"
244 default_port = "80"
245
245
246 port = req.env["SERVER_PORT"]
246 port = req.env["SERVER_PORT"]
247 port = port != default_port and (":" + port) or ""
247 port = port != default_port and (":" + port) or ""
248 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
248 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
249 staticurl = self.config("web", "staticurl") or req.url + 'static/'
249 staticurl = self.config("web", "staticurl") or req.url + 'static/'
250 if not staticurl.endswith('/'):
250 if not staticurl.endswith('/'):
251 staticurl += '/'
251 staticurl += '/'
252
252
253 # some functions for the templater
253 # some functions for the templater
254
254
255 def header(**map):
255 def header(**map):
256 header_file = cStringIO.StringIO(
256 header_file = cStringIO.StringIO(
257 ''.join(tmpl("header", encoding=self.encoding, **map)))
257 ''.join(tmpl("header", encoding=self.encoding, **map)))
258 msg = mimetools.Message(header_file, 0)
258 msg = mimetools.Message(header_file, 0)
259 req.header(msg.items())
259 req.header(msg.items())
260 yield header_file.read()
260 yield header_file.read()
261
261
262 def footer(**map):
262 def footer(**map):
263 yield tmpl("footer", **map)
263 yield tmpl("footer", **map)
264
264
265 def motd(**map):
265 def motd(**map):
266 yield self.config("web", "motd", "")
266 yield self.config("web", "motd", "")
267
267
268 def sessionvars(**map):
268 def sessionvars(**map):
269 fields = []
269 fields = []
270 if 'style' in req.form:
270 if 'style' in req.form:
271 style = req.form['style'][0]
271 style = req.form['style'][0]
272 if style != self.config('web', 'style', ''):
272 if style != self.config('web', 'style', ''):
273 fields.append(('style', style))
273 fields.append(('style', style))
274
274
275 separator = req.url[-1] == '?' and ';' or '?'
275 separator = req.url[-1] == '?' and ';' or '?'
276 for name, value in fields:
276 for name, value in fields:
277 yield dict(name=name, value=value, separator=separator)
277 yield dict(name=name, value=value, separator=separator)
278 separator = ';'
278 separator = ';'
279
279
280 # figure out which style to use
280 # figure out which style to use
281
281
282 style = self.config("web", "style", "")
282 style = self.config("web", "style", "")
283 if 'style' in req.form:
283 if 'style' in req.form:
284 style = req.form['style'][0]
284 style = req.form['style'][0]
285 mapfile = style_map(self.templatepath, style)
285 mapfile = style_map(self.templatepath, style)
286
286
287 if not self.reponame:
287 if not self.reponame:
288 self.reponame = (self.config("web", "name")
288 self.reponame = (self.config("web", "name")
289 or req.env.get('REPO_NAME')
289 or req.env.get('REPO_NAME')
290 or req.url.strip('/') or self.repo.root)
290 or req.url.strip('/') or self.repo.root)
291
291
292 # create the templater
292 # create the templater
293
293
294 tmpl = templater.templater(mapfile, templater.common_filters,
294 tmpl = templater.templater(mapfile, templater.common_filters,
295 defaults={"url": req.url,
295 defaults={"url": req.url,
296 "staticurl": staticurl,
296 "staticurl": staticurl,
297 "urlbase": urlbase,
297 "urlbase": urlbase,
298 "repo": self.reponame,
298 "repo": self.reponame,
299 "header": header,
299 "header": header,
300 "footer": footer,
300 "footer": footer,
301 "motd": motd,
301 "motd": motd,
302 "sessionvars": sessionvars
302 "sessionvars": sessionvars
303 })
303 })
304 return tmpl
304 return tmpl
305
305
306 def archivelist(self, nodeid):
306 def archivelist(self, nodeid):
307 allowed = self.configlist("web", "allow_archive")
307 allowed = self.configlist("web", "allow_archive")
308 for i, spec in self.archive_specs.iteritems():
308 for i, spec in self.archive_specs.iteritems():
309 if i in allowed or self.configbool("web", "allow" + i):
309 if i in allowed or self.configbool("web", "allow" + i):
310 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
310 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
311
311
312 def listfilediffs(self, tmpl, files, changeset):
312 def listfilediffs(self, tmpl, files, changeset):
313 for f in files[:self.maxfiles]:
313 for f in files[:self.maxfiles]:
314 yield tmpl("filedifflink", node=hex(changeset), file=f)
314 yield tmpl("filedifflink", node=hex(changeset), file=f)
315 if len(files) > self.maxfiles:
315 if len(files) > self.maxfiles:
316 yield tmpl("fileellipses")
316 yield tmpl("fileellipses")
317
317
318 def siblings(self, siblings=[], hiderev=None, **args):
318 def siblings(self, siblings=[], hiderev=None, **args):
319 siblings = [s for s in siblings if s.node() != nullid]
319 siblings = [s for s in siblings if s.node() != nullid]
320 if len(siblings) == 1 and siblings[0].rev() == hiderev:
320 if len(siblings) == 1 and siblings[0].rev() == hiderev:
321 return
321 return
322 for s in siblings:
322 for s in siblings:
323 d = {'node': hex(s.node()), 'rev': s.rev()}
323 d = {'node': hex(s.node()), 'rev': s.rev()}
324 if hasattr(s, 'path'):
324 if hasattr(s, 'path'):
325 d['file'] = s.path()
325 d['file'] = s.path()
326 d.update(args)
326 d.update(args)
327 yield d
327 yield d
328
328
329 def renamelink(self, fl, node):
329 def renamelink(self, fl, node):
330 r = fl.renamed(node)
330 r = fl.renamed(node)
331 if r:
331 if r:
332 return [dict(file=r[0], node=hex(r[1]))]
332 return [dict(file=r[0], node=hex(r[1]))]
333 return []
333 return []
334
334
335 def nodetagsdict(self, node):
335 def nodetagsdict(self, node):
336 return [{"name": i} for i in self.repo.nodetags(node)]
336 return [{"name": i} for i in self.repo.nodetags(node)]
337
337
338 def nodebranchdict(self, ctx):
338 def nodebranchdict(self, ctx):
339 branches = []
339 branches = []
340 branch = ctx.branch()
340 branch = ctx.branch()
341 # If this is an empty repo, ctx.node() == nullid,
341 # If this is an empty repo, ctx.node() == nullid,
342 # ctx.branch() == 'default', but branchtags() is
342 # ctx.branch() == 'default', but branchtags() is
343 # an empty dict. Using dict.get avoids a traceback.
343 # an empty dict. Using dict.get avoids a traceback.
344 if self.repo.branchtags().get(branch) == ctx.node():
344 if self.repo.branchtags().get(branch) == ctx.node():
345 branches.append({"name": branch})
345 branches.append({"name": branch})
346 return branches
346 return branches
347
347
348 def showtag(self, tmpl, t1, node=nullid, **args):
348 def showtag(self, tmpl, t1, node=nullid, **args):
349 for t in self.repo.nodetags(node):
349 for t in self.repo.nodetags(node):
350 yield tmpl(t1, tag=t, **args)
350 yield tmpl(t1, tag=t, **args)
351
351
352 def diff(self, tmpl, node1, node2, files):
352 def diff(self, tmpl, node1, node2, files):
353 def filterfiles(filters, files):
353 def filterfiles(filters, files):
354 l = [x for x in files if x in filters]
354 l = [x for x in files if x in filters]
355
355
356 for t in filters:
356 for t in filters:
357 if t and t[-1] != os.sep:
357 if t and t[-1] != os.sep:
358 t += os.sep
358 t += os.sep
359 l += [x for x in files if x.startswith(t)]
359 l += [x for x in files if x.startswith(t)]
360 return l
360 return l
361
361
362 parity = paritygen(self.stripecount)
362 parity = paritygen(self.stripecount)
363 def diffblock(diff, f, fn):
363 def diffblock(diff, f, fn):
364 yield tmpl("diffblock",
364 yield tmpl("diffblock",
365 lines=prettyprintlines(diff),
365 lines=prettyprintlines(diff),
366 parity=parity.next(),
366 parity=parity.next(),
367 file=f,
367 file=f,
368 filenode=hex(fn or nullid))
368 filenode=hex(fn or nullid))
369
369
370 def prettyprintlines(diff):
370 def prettyprintlines(diff):
371 for l in diff.splitlines(1):
371 for l in diff.splitlines(1):
372 if l.startswith('+'):
372 if l.startswith('+'):
373 yield tmpl("difflineplus", line=l)
373 yield tmpl("difflineplus", line=l)
374 elif l.startswith('-'):
374 elif l.startswith('-'):
375 yield tmpl("difflineminus", line=l)
375 yield tmpl("difflineminus", line=l)
376 elif l.startswith('@'):
376 elif l.startswith('@'):
377 yield tmpl("difflineat", line=l)
377 yield tmpl("difflineat", line=l)
378 else:
378 else:
379 yield tmpl("diffline", line=l)
379 yield tmpl("diffline", line=l)
380
380
381 r = self.repo
381 r = self.repo
382 c1 = r.changectx(node1)
382 c1 = r.changectx(node1)
383 c2 = r.changectx(node2)
383 c2 = r.changectx(node2)
384 date1 = util.datestr(c1.date())
384 date1 = util.datestr(c1.date())
385 date2 = util.datestr(c2.date())
385 date2 = util.datestr(c2.date())
386
386
387 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
387 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
388 if files:
388 if files:
389 modified, added, removed = map(lambda x: filterfiles(files, x),
389 modified, added, removed = map(lambda x: filterfiles(files, x),
390 (modified, added, removed))
390 (modified, added, removed))
391
391
392 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
392 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
393 for f in modified:
393 for f in modified:
394 to = c1.filectx(f).data()
394 to = c1.filectx(f).data()
395 tn = c2.filectx(f).data()
395 tn = c2.filectx(f).data()
396 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
396 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
397 opts=diffopts), f, tn)
397 opts=diffopts), f, tn)
398 for f in added:
398 for f in added:
399 to = None
399 to = None
400 tn = c2.filectx(f).data()
400 tn = c2.filectx(f).data()
401 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
401 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
402 opts=diffopts), f, tn)
402 opts=diffopts), f, tn)
403 for f in removed:
403 for f in removed:
404 to = c1.filectx(f).data()
404 to = c1.filectx(f).data()
405 tn = None
405 tn = None
406 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
406 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
407 opts=diffopts), f, tn)
407 opts=diffopts), f, tn)
408
408
409 def changelog(self, tmpl, ctx, shortlog=False):
409 def changelog(self, tmpl, ctx, shortlog=False):
410 def changelist(limit=0,**map):
410 def changelist(limit=0,**map):
411 cl = self.repo.changelog
411 cl = self.repo.changelog
412 l = [] # build a list in forward order for efficiency
412 l = [] # build a list in forward order for efficiency
413 for i in xrange(start, end):
413 for i in xrange(start, end):
414 ctx = self.repo.changectx(i)
414 ctx = self.repo.changectx(i)
415 n = ctx.node()
415 n = ctx.node()
416
416
417 l.insert(0, {"parity": parity.next(),
417 l.insert(0, {"parity": parity.next(),
418 "author": ctx.user(),
418 "author": ctx.user(),
419 "parent": self.siblings(ctx.parents(), i - 1),
419 "parent": self.siblings(ctx.parents(), i - 1),
420 "child": self.siblings(ctx.children(), i + 1),
420 "child": self.siblings(ctx.children(), i + 1),
421 "changelogtag": self.showtag("changelogtag",n),
421 "changelogtag": self.showtag("changelogtag",n),
422 "desc": ctx.description(),
422 "desc": ctx.description(),
423 "date": ctx.date(),
423 "date": ctx.date(),
424 "files": self.listfilediffs(tmpl, ctx.files(), n),
424 "files": self.listfilediffs(tmpl, ctx.files(), n),
425 "rev": i,
425 "rev": i,
426 "node": hex(n),
426 "node": hex(n),
427 "tags": self.nodetagsdict(n),
427 "tags": self.nodetagsdict(n),
428 "branches": self.nodebranchdict(ctx)})
428 "branches": self.nodebranchdict(ctx)})
429
429
430 if limit > 0:
430 if limit > 0:
431 l = l[:limit]
431 l = l[:limit]
432
432
433 for e in l:
433 for e in l:
434 yield e
434 yield e
435
435
436 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
436 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
437 cl = self.repo.changelog
437 cl = self.repo.changelog
438 count = cl.count()
438 count = cl.count()
439 pos = ctx.rev()
439 pos = ctx.rev()
440 start = max(0, pos - maxchanges + 1)
440 start = max(0, pos - maxchanges + 1)
441 end = min(count, start + maxchanges)
441 end = min(count, start + maxchanges)
442 pos = end - 1
442 pos = end - 1
443 parity = paritygen(self.stripecount, offset=start-end)
443 parity = paritygen(self.stripecount, offset=start-end)
444
444
445 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
445 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
446
446
447 return tmpl(shortlog and 'shortlog' or 'changelog',
447 return tmpl(shortlog and 'shortlog' or 'changelog',
448 changenav=changenav,
448 changenav=changenav,
449 node=hex(cl.tip()),
449 node=hex(cl.tip()),
450 rev=pos, changesets=count,
450 rev=pos, changesets=count,
451 entries=lambda **x: changelist(limit=0,**x),
451 entries=lambda **x: changelist(limit=0,**x),
452 latestentry=lambda **x: changelist(limit=1,**x),
452 latestentry=lambda **x: changelist(limit=1,**x),
453 archives=self.archivelist("tip"))
453 archives=self.archivelist("tip"))
454
454
455 def search(self, tmpl, query):
455 def search(self, tmpl, query):
456
456
457 def changelist(**map):
457 def changelist(**map):
458 cl = self.repo.changelog
458 cl = self.repo.changelog
459 count = 0
459 count = 0
460 qw = query.lower().split()
460 qw = query.lower().split()
461
461
462 def revgen():
462 def revgen():
463 for i in xrange(cl.count() - 1, 0, -100):
463 for i in xrange(cl.count() - 1, 0, -100):
464 l = []
464 l = []
465 for j in xrange(max(0, i - 100), i):
465 for j in xrange(max(0, i - 100), i):
466 ctx = self.repo.changectx(j)
466 ctx = self.repo.changectx(j)
467 l.append(ctx)
467 l.append(ctx)
468 l.reverse()
468 l.reverse()
469 for e in l:
469 for e in l:
470 yield e
470 yield e
471
471
472 for ctx in revgen():
472 for ctx in revgen():
473 miss = 0
473 miss = 0
474 for q in qw:
474 for q in qw:
475 if not (q in ctx.user().lower() or
475 if not (q in ctx.user().lower() or
476 q in ctx.description().lower() or
476 q in ctx.description().lower() or
477 q in " ".join(ctx.files()).lower()):
477 q in " ".join(ctx.files()).lower()):
478 miss = 1
478 miss = 1
479 break
479 break
480 if miss:
480 if miss:
481 continue
481 continue
482
482
483 count += 1
483 count += 1
484 n = ctx.node()
484 n = ctx.node()
485
485
486 yield tmpl('searchentry',
486 yield tmpl('searchentry',
487 parity=parity.next(),
487 parity=parity.next(),
488 author=ctx.user(),
488 author=ctx.user(),
489 parent=self.siblings(ctx.parents()),
489 parent=self.siblings(ctx.parents()),
490 child=self.siblings(ctx.children()),
490 child=self.siblings(ctx.children()),
491 changelogtag=self.showtag("changelogtag",n),
491 changelogtag=self.showtag("changelogtag",n),
492 desc=ctx.description(),
492 desc=ctx.description(),
493 date=ctx.date(),
493 date=ctx.date(),
494 files=self.listfilediffs(tmpl, ctx.files(), n),
494 files=self.listfilediffs(tmpl, ctx.files(), n),
495 rev=ctx.rev(),
495 rev=ctx.rev(),
496 node=hex(n),
496 node=hex(n),
497 tags=self.nodetagsdict(n),
497 tags=self.nodetagsdict(n),
498 branches=self.nodebranchdict(ctx))
498 branches=self.nodebranchdict(ctx))
499
499
500 if count >= self.maxchanges:
500 if count >= self.maxchanges:
501 break
501 break
502
502
503 cl = self.repo.changelog
503 cl = self.repo.changelog
504 parity = paritygen(self.stripecount)
504 parity = paritygen(self.stripecount)
505
505
506 return tmpl('search',
506 return tmpl('search',
507 query=query,
507 query=query,
508 node=hex(cl.tip()),
508 node=hex(cl.tip()),
509 entries=changelist,
509 entries=changelist,
510 archives=self.archivelist("tip"))
510 archives=self.archivelist("tip"))
511
511
512 def changeset(self, tmpl, ctx):
512 def changeset(self, tmpl, ctx):
513 n = ctx.node()
513 n = ctx.node()
514 parents = ctx.parents()
514 parents = ctx.parents()
515 p1 = parents[0].node()
515 p1 = parents[0].node()
516
516
517 files = []
517 files = []
518 parity = paritygen(self.stripecount)
518 parity = paritygen(self.stripecount)
519 for f in ctx.files():
519 for f in ctx.files():
520 files.append(tmpl("filenodelink",
520 files.append(tmpl("filenodelink",
521 node=hex(n), file=f,
521 node=hex(n), file=f,
522 parity=parity.next()))
522 parity=parity.next()))
523
523
524 def diff(**map):
524 def diff(**map):
525 yield self.diff(tmpl, p1, n, None)
525 yield self.diff(tmpl, p1, n, None)
526
526
527 return tmpl('changeset',
527 return tmpl('changeset',
528 diff=diff,
528 diff=diff,
529 rev=ctx.rev(),
529 rev=ctx.rev(),
530 node=hex(n),
530 node=hex(n),
531 parent=self.siblings(parents),
531 parent=self.siblings(parents),
532 child=self.siblings(ctx.children()),
532 child=self.siblings(ctx.children()),
533 changesettag=self.showtag("changesettag",n),
533 changesettag=self.showtag("changesettag",n),
534 author=ctx.user(),
534 author=ctx.user(),
535 desc=ctx.description(),
535 desc=ctx.description(),
536 date=ctx.date(),
536 date=ctx.date(),
537 files=files,
537 files=files,
538 archives=self.archivelist(hex(n)),
538 archives=self.archivelist(hex(n)),
539 tags=self.nodetagsdict(n),
539 tags=self.nodetagsdict(n),
540 branches=self.nodebranchdict(ctx))
540 branches=self.nodebranchdict(ctx))
541
541
542 def filelog(self, tmpl, fctx):
542 def filelog(self, tmpl, fctx):
543 f = fctx.path()
543 f = fctx.path()
544 fl = fctx.filelog()
544 fl = fctx.filelog()
545 count = fl.count()
545 count = fl.count()
546 pagelen = self.maxshortchanges
546 pagelen = self.maxshortchanges
547 pos = fctx.filerev()
547 pos = fctx.filerev()
548 start = max(0, pos - pagelen + 1)
548 start = max(0, pos - pagelen + 1)
549 end = min(count, start + pagelen)
549 end = min(count, start + pagelen)
550 pos = end - 1
550 pos = end - 1
551 parity = paritygen(self.stripecount, offset=start-end)
551 parity = paritygen(self.stripecount, offset=start-end)
552
552
553 def entries(limit=0, **map):
553 def entries(limit=0, **map):
554 l = []
554 l = []
555
555
556 for i in xrange(start, end):
556 for i in xrange(start, end):
557 ctx = fctx.filectx(i)
557 ctx = fctx.filectx(i)
558 n = fl.node(i)
558 n = fl.node(i)
559
559
560 l.insert(0, {"parity": parity.next(),
560 l.insert(0, {"parity": parity.next(),
561 "filerev": i,
561 "filerev": i,
562 "file": f,
562 "file": f,
563 "node": hex(ctx.node()),
563 "node": hex(ctx.node()),
564 "author": ctx.user(),
564 "author": ctx.user(),
565 "date": ctx.date(),
565 "date": ctx.date(),
566 "rename": self.renamelink(fl, n),
566 "rename": self.renamelink(fl, n),
567 "parent": self.siblings(fctx.parents()),
567 "parent": self.siblings(fctx.parents()),
568 "child": self.siblings(fctx.children()),
568 "child": self.siblings(fctx.children()),
569 "desc": ctx.description()})
569 "desc": ctx.description()})
570
570
571 if limit > 0:
571 if limit > 0:
572 l = l[:limit]
572 l = l[:limit]
573
573
574 for e in l:
574 for e in l:
575 yield e
575 yield e
576
576
577 nodefunc = lambda x: fctx.filectx(fileid=x)
577 nodefunc = lambda x: fctx.filectx(fileid=x)
578 nav = revnavgen(pos, pagelen, count, nodefunc)
578 nav = revnavgen(pos, pagelen, count, nodefunc)
579 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
579 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
580 entries=lambda **x: entries(limit=0, **x),
580 entries=lambda **x: entries(limit=0, **x),
581 latestentry=lambda **x: entries(limit=1, **x))
581 latestentry=lambda **x: entries(limit=1, **x))
582
582
583 def filerevision(self, tmpl, fctx):
583 def filerevision(self, tmpl, fctx):
584 f = fctx.path()
584 f = fctx.path()
585 text = fctx.data()
585 text = fctx.data()
586 fl = fctx.filelog()
586 fl = fctx.filelog()
587 n = fctx.filenode()
587 n = fctx.filenode()
588 parity = paritygen(self.stripecount)
588 parity = paritygen(self.stripecount)
589
589
590 mt = mimetypes.guess_type(f)[0]
590 mt = mimetypes.guess_type(f)[0]
591 rawtext = text
591 rawtext = text
592 if util.binary(text):
592 if util.binary(text):
593 mt = mt or 'application/octet-stream'
593 mt = mt or 'application/octet-stream'
594 text = "(binary:%s)" % mt
594 text = "(binary:%s)" % mt
595 mt = mt or 'text/plain'
595 mt = mt or 'text/plain'
596
596
597 def lines():
597 def lines():
598 for l, t in enumerate(text.splitlines(1)):
598 for l, t in enumerate(text.splitlines(1)):
599 yield {"line": t,
599 yield {"line": t,
600 "linenumber": "% 6d" % (l + 1),
600 "linenumber": "% 6d" % (l + 1),
601 "parity": parity.next()}
601 "parity": parity.next()}
602
602
603 return tmpl("filerevision",
603 return tmpl("filerevision",
604 file=f,
604 file=f,
605 path=_up(f),
605 path=_up(f),
606 text=lines(),
606 text=lines(),
607 raw=rawtext,
607 raw=rawtext,
608 mimetype=mt,
608 mimetype=mt,
609 rev=fctx.rev(),
609 rev=fctx.rev(),
610 node=hex(fctx.node()),
610 node=hex(fctx.node()),
611 author=fctx.user(),
611 author=fctx.user(),
612 date=fctx.date(),
612 date=fctx.date(),
613 desc=fctx.description(),
613 desc=fctx.description(),
614 parent=self.siblings(fctx.parents()),
614 parent=self.siblings(fctx.parents()),
615 child=self.siblings(fctx.children()),
615 child=self.siblings(fctx.children()),
616 rename=self.renamelink(fl, n),
616 rename=self.renamelink(fl, n),
617 permissions=fctx.manifest().flags(f))
617 permissions=fctx.manifest().flags(f))
618
618
619 def fileannotate(self, tmpl, fctx):
619 def fileannotate(self, tmpl, fctx):
620 f = fctx.path()
620 f = fctx.path()
621 n = fctx.filenode()
621 n = fctx.filenode()
622 fl = fctx.filelog()
622 fl = fctx.filelog()
623 parity = paritygen(self.stripecount)
623 parity = paritygen(self.stripecount)
624
624
625 def annotate(**map):
625 def annotate(**map):
626 last = None
626 last = None
627 for f, l in fctx.annotate(follow=True):
627 for f, l in fctx.annotate(follow=True):
628 fnode = f.filenode()
628 fnode = f.filenode()
629 name = self.repo.ui.shortuser(f.user())
629 name = self.repo.ui.shortuser(f.user())
630
630
631 if last != fnode:
631 if last != fnode:
632 last = fnode
632 last = fnode
633
633
634 yield {"parity": parity.next(),
634 yield {"parity": parity.next(),
635 "node": hex(f.node()),
635 "node": hex(f.node()),
636 "rev": f.rev(),
636 "rev": f.rev(),
637 "author": name,
637 "author": name,
638 "file": f.path(),
638 "file": f.path(),
639 "line": l}
639 "line": l}
640
640
641 return tmpl("fileannotate",
641 return tmpl("fileannotate",
642 file=f,
642 file=f,
643 annotate=annotate,
643 annotate=annotate,
644 path=_up(f),
644 path=_up(f),
645 rev=fctx.rev(),
645 rev=fctx.rev(),
646 node=hex(fctx.node()),
646 node=hex(fctx.node()),
647 author=fctx.user(),
647 author=fctx.user(),
648 date=fctx.date(),
648 date=fctx.date(),
649 desc=fctx.description(),
649 desc=fctx.description(),
650 rename=self.renamelink(fl, n),
650 rename=self.renamelink(fl, n),
651 parent=self.siblings(fctx.parents()),
651 parent=self.siblings(fctx.parents()),
652 child=self.siblings(fctx.children()),
652 child=self.siblings(fctx.children()),
653 permissions=fctx.manifest().flags(f))
653 permissions=fctx.manifest().flags(f))
654
654
655 def manifest(self, tmpl, ctx, path):
655 def manifest(self, tmpl, ctx, path):
656 mf = ctx.manifest()
656 mf = ctx.manifest()
657 node = ctx.node()
657 node = ctx.node()
658
658
659 files = {}
659 files = {}
660 parity = paritygen(self.stripecount)
660 parity = paritygen(self.stripecount)
661
661
662 if path and path[-1] != "/":
662 if path and path[-1] != "/":
663 path += "/"
663 path += "/"
664 l = len(path)
664 l = len(path)
665 abspath = "/" + path
665 abspath = "/" + path
666
666
667 for f, n in mf.items():
667 for f, n in mf.items():
668 if f[:l] != path:
668 if f[:l] != path:
669 continue
669 continue
670 remain = f[l:]
670 remain = f[l:]
671 if "/" in remain:
671 if "/" in remain:
672 short = remain[:remain.index("/") + 1] # bleah
672 short = remain[:remain.index("/") + 1] # bleah
673 files[short] = (f, None)
673 files[short] = (f, None)
674 else:
674 else:
675 short = os.path.basename(remain)
675 short = os.path.basename(remain)
676 files[short] = (f, n)
676 files[short] = (f, n)
677
677
678 if not files:
678 if not files:
679 raise ErrorResponse(404, 'Path not found: ' + path)
679 raise ErrorResponse(404, 'Path not found: ' + path)
680
680
681 def filelist(**map):
681 def filelist(**map):
682 fl = files.keys()
682 fl = files.keys()
683 fl.sort()
683 fl.sort()
684 for f in fl:
684 for f in fl:
685 full, fnode = files[f]
685 full, fnode = files[f]
686 if not fnode:
686 if not fnode:
687 continue
687 continue
688
688
689 fctx = ctx.filectx(full)
689 fctx = ctx.filectx(full)
690 yield {"file": full,
690 yield {"file": full,
691 "parity": parity.next(),
691 "parity": parity.next(),
692 "basename": f,
692 "basename": f,
693 "date": fctx.changectx().date(),
693 "date": fctx.changectx().date(),
694 "size": fctx.size(),
694 "size": fctx.size(),
695 "permissions": mf.flags(full)}
695 "permissions": mf.flags(full)}
696
696
697 def dirlist(**map):
697 def dirlist(**map):
698 fl = files.keys()
698 fl = files.keys()
699 fl.sort()
699 fl.sort()
700 for f in fl:
700 for f in fl:
701 full, fnode = files[f]
701 full, fnode = files[f]
702 if fnode:
702 if fnode:
703 continue
703 continue
704
704
705 yield {"parity": parity.next(),
705 yield {"parity": parity.next(),
706 "path": "%s%s" % (abspath, f),
706 "path": "%s%s" % (abspath, f),
707 "basename": f[:-1]}
707 "basename": f[:-1]}
708
708
709 return tmpl("manifest",
709 return tmpl("manifest",
710 rev=ctx.rev(),
710 rev=ctx.rev(),
711 node=hex(node),
711 node=hex(node),
712 path=abspath,
712 path=abspath,
713 up=_up(abspath),
713 up=_up(abspath),
714 upparity=parity.next(),
714 upparity=parity.next(),
715 fentries=filelist,
715 fentries=filelist,
716 dentries=dirlist,
716 dentries=dirlist,
717 archives=self.archivelist(hex(node)),
717 archives=self.archivelist(hex(node)),
718 tags=self.nodetagsdict(node),
718 tags=self.nodetagsdict(node),
719 branches=self.nodebranchdict(ctx))
719 branches=self.nodebranchdict(ctx))
720
720
721 def tags(self, tmpl):
721 def tags(self, tmpl):
722 i = self.repo.tagslist()
722 i = self.repo.tagslist()
723 i.reverse()
723 i.reverse()
724 parity = paritygen(self.stripecount)
724 parity = paritygen(self.stripecount)
725
725
726 def entries(notip=False,limit=0, **map):
726 def entries(notip=False,limit=0, **map):
727 count = 0
727 count = 0
728 for k, n in i:
728 for k, n in i:
729 if notip and k == "tip":
729 if notip and k == "tip":
730 continue
730 continue
731 if limit > 0 and count >= limit:
731 if limit > 0 and count >= limit:
732 continue
732 continue
733 count = count + 1
733 count = count + 1
734 yield {"parity": parity.next(),
734 yield {"parity": parity.next(),
735 "tag": k,
735 "tag": k,
736 "date": self.repo.changectx(n).date(),
736 "date": self.repo.changectx(n).date(),
737 "node": hex(n)}
737 "node": hex(n)}
738
738
739 return tmpl("tags",
739 return tmpl("tags",
740 node=hex(self.repo.changelog.tip()),
740 node=hex(self.repo.changelog.tip()),
741 entries=lambda **x: entries(False,0, **x),
741 entries=lambda **x: entries(False,0, **x),
742 entriesnotip=lambda **x: entries(True,0, **x),
742 entriesnotip=lambda **x: entries(True,0, **x),
743 latestentry=lambda **x: entries(True,1, **x))
743 latestentry=lambda **x: entries(True,1, **x))
744
744
745 def summary(self, tmpl):
745 def summary(self, tmpl):
746 i = self.repo.tagslist()
746 i = self.repo.tagslist()
747 i.reverse()
747 i.reverse()
748
748
749 def tagentries(**map):
749 def tagentries(**map):
750 parity = paritygen(self.stripecount)
750 parity = paritygen(self.stripecount)
751 count = 0
751 count = 0
752 for k, n in i:
752 for k, n in i:
753 if k == "tip": # skip tip
753 if k == "tip": # skip tip
754 continue;
754 continue;
755
755
756 count += 1
756 count += 1
757 if count > 10: # limit to 10 tags
757 if count > 10: # limit to 10 tags
758 break;
758 break;
759
759
760 yield tmpl("tagentry",
760 yield tmpl("tagentry",
761 parity=parity.next(),
761 parity=parity.next(),
762 tag=k,
762 tag=k,
763 node=hex(n),
763 node=hex(n),
764 date=self.repo.changectx(n).date())
764 date=self.repo.changectx(n).date())
765
765
766
766
767 def branches(**map):
767 def branches(**map):
768 parity = paritygen(self.stripecount)
768 parity = paritygen(self.stripecount)
769
769
770 b = self.repo.branchtags()
770 b = self.repo.branchtags()
771 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
771 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
772 l.sort()
772 l.sort()
773
773
774 for r,n,t in l:
774 for r,n,t in l:
775 ctx = self.repo.changectx(n)
775 ctx = self.repo.changectx(n)
776
776
777 yield {'parity': parity.next(),
777 yield {'parity': parity.next(),
778 'branch': t,
778 'branch': t,
779 'node': hex(n),
779 'node': hex(n),
780 'date': ctx.date()}
780 'date': ctx.date()}
781
781
782 def changelist(**map):
782 def changelist(**map):
783 parity = paritygen(self.stripecount, offset=start-end)
783 parity = paritygen(self.stripecount, offset=start-end)
784 l = [] # build a list in forward order for efficiency
784 l = [] # build a list in forward order for efficiency
785 for i in xrange(start, end):
785 for i in xrange(start, end):
786 ctx = self.repo.changectx(i)
786 ctx = self.repo.changectx(i)
787 n = ctx.node()
787 n = ctx.node()
788 hn = hex(n)
788 hn = hex(n)
789
789
790 l.insert(0, tmpl(
790 l.insert(0, tmpl(
791 'shortlogentry',
791 'shortlogentry',
792 parity=parity.next(),
792 parity=parity.next(),
793 author=ctx.user(),
793 author=ctx.user(),
794 desc=ctx.description(),
794 desc=ctx.description(),
795 date=ctx.date(),
795 date=ctx.date(),
796 rev=i,
796 rev=i,
797 node=hn,
797 node=hn,
798 tags=self.nodetagsdict(n),
798 tags=self.nodetagsdict(n),
799 branches=self.nodebranchdict(ctx)))
799 branches=self.nodebranchdict(ctx)))
800
800
801 yield l
801 yield l
802
802
803 cl = self.repo.changelog
803 cl = self.repo.changelog
804 count = cl.count()
804 count = cl.count()
805 start = max(0, count - self.maxchanges)
805 start = max(0, count - self.maxchanges)
806 end = min(count, start + self.maxchanges)
806 end = min(count, start + self.maxchanges)
807
807
808 return tmpl("summary",
808 return tmpl("summary",
809 desc=self.config("web", "description", "unknown"),
809 desc=self.config("web", "description", "unknown"),
810 owner=get_contact(self.config) or "unknown",
810 owner=get_contact(self.config) or "unknown",
811 lastchange=cl.read(cl.tip())[2],
811 lastchange=cl.read(cl.tip())[2],
812 tags=tagentries,
812 tags=tagentries,
813 branches=branches,
813 branches=branches,
814 shortlog=changelist,
814 shortlog=changelist,
815 node=hex(cl.tip()),
815 node=hex(cl.tip()),
816 archives=self.archivelist("tip"))
816 archives=self.archivelist("tip"))
817
817
818 def filediff(self, tmpl, fctx):
818 def filediff(self, tmpl, fctx):
819 n = fctx.node()
819 n = fctx.node()
820 path = fctx.path()
820 path = fctx.path()
821 parents = fctx.parents()
821 parents = fctx.parents()
822 p1 = parents and parents[0].node() or nullid
822 p1 = parents and parents[0].node() or nullid
823
823
824 def diff(**map):
824 def diff(**map):
825 yield self.diff(tmpl, p1, n, [path])
825 yield self.diff(tmpl, p1, n, [path])
826
826
827 return tmpl("filediff",
827 return tmpl("filediff",
828 file=path,
828 file=path,
829 node=hex(n),
829 node=hex(n),
830 rev=fctx.rev(),
830 rev=fctx.rev(),
831 parent=self.siblings(parents),
831 parent=self.siblings(parents),
832 child=self.siblings(fctx.children()),
832 child=self.siblings(fctx.children()),
833 diff=diff)
833 diff=diff)
834
834
835 archive_specs = {
835 archive_specs = {
836 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
836 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
837 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
837 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
838 'zip': ('application/zip', 'zip', '.zip', None),
838 'zip': ('application/zip', 'zip', '.zip', None),
839 }
839 }
840
840
841 def archive(self, tmpl, req, key, type_):
841 def archive(self, tmpl, req, key, type_):
842 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
842 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
843 cnode = self.repo.lookup(key)
843 cnode = self.repo.lookup(key)
844 arch_version = key
844 arch_version = key
845 if cnode == key or key == 'tip':
845 if cnode == key or key == 'tip':
846 arch_version = short(cnode)
846 arch_version = short(cnode)
847 name = "%s-%s" % (reponame, arch_version)
847 name = "%s-%s" % (reponame, arch_version)
848 mimetype, artype, extension, encoding = self.archive_specs[type_]
848 mimetype, artype, extension, encoding = self.archive_specs[type_]
849 headers = [('Content-type', mimetype),
849 headers = [('Content-type', mimetype),
850 ('Content-disposition', 'attachment; filename=%s%s' %
850 ('Content-disposition', 'attachment; filename=%s%s' %
851 (name, extension))]
851 (name, extension))]
852 if encoding:
852 if encoding:
853 headers.append(('Content-encoding', encoding))
853 headers.append(('Content-encoding', encoding))
854 req.header(headers)
854 req.header(headers)
855 archival.archive(self.repo, req, cnode, artype, prefix=name)
855 archival.archive(self.repo, req, cnode, artype, prefix=name)
856
856
857 # add tags to things
857 # add tags to things
858 # tags -> list of changesets corresponding to tags
858 # tags -> list of changesets corresponding to tags
859 # find tag, changeset, file
859 # find tag, changeset, file
860
860
861 def cleanpath(self, path):
861 def cleanpath(self, path):
862 path = path.lstrip('/')
862 path = path.lstrip('/')
863 return util.canonpath(self.repo.root, '', path)
863 return util.canonpath(self.repo.root, '', path)
864
864
865 def changectx(self, req):
865 def changectx(self, req):
866 if 'node' in req.form:
866 if 'node' in req.form:
867 changeid = req.form['node'][0]
867 changeid = req.form['node'][0]
868 elif 'manifest' in req.form:
868 elif 'manifest' in req.form:
869 changeid = req.form['manifest'][0]
869 changeid = req.form['manifest'][0]
870 else:
870 else:
871 changeid = self.repo.changelog.count() - 1
871 changeid = self.repo.changelog.count() - 1
872
872
873 try:
873 try:
874 ctx = self.repo.changectx(changeid)
874 ctx = self.repo.changectx(changeid)
875 except hg.RepoError:
875 except hg.RepoError:
876 man = self.repo.manifest
876 man = self.repo.manifest
877 mn = man.lookup(changeid)
877 mn = man.lookup(changeid)
878 ctx = self.repo.changectx(man.linkrev(mn))
878 ctx = self.repo.changectx(man.linkrev(mn))
879
879
880 return ctx
880 return ctx
881
881
882 def filectx(self, req):
882 def filectx(self, req):
883 path = self.cleanpath(req.form['file'][0])
883 path = self.cleanpath(req.form['file'][0])
884 if 'node' in req.form:
884 if 'node' in req.form:
885 changeid = req.form['node'][0]
885 changeid = req.form['node'][0]
886 else:
886 else:
887 changeid = req.form['filenode'][0]
887 changeid = req.form['filenode'][0]
888 try:
888 try:
889 ctx = self.repo.changectx(changeid)
889 ctx = self.repo.changectx(changeid)
890 fctx = ctx.filectx(path)
890 fctx = ctx.filectx(path)
891 except hg.RepoError:
891 except hg.RepoError:
892 fctx = self.repo.filectx(path, fileid=changeid)
892 fctx = self.repo.filectx(path, fileid=changeid)
893
893
894 return fctx
894 return fctx
895
895
896 def check_perm(self, req, op, default):
896 def check_perm(self, req, op, default):
897 '''check permission for operation based on user auth.
897 '''check permission for operation based on user auth.
898 return true if op allowed, else false.
898 return true if op allowed, else false.
899 default is policy to use if no config given.'''
899 default is policy to use if no config given.'''
900
900
901 user = req.env.get('REMOTE_USER')
901 user = req.env.get('REMOTE_USER')
902
902
903 deny = self.configlist('web', 'deny_' + op)
903 deny = self.configlist('web', 'deny_' + op)
904 if deny and (not user or deny == ['*'] or user in deny):
904 if deny and (not user or deny == ['*'] or user in deny):
905 return False
905 return False
906
906
907 allow = self.configlist('web', 'allow_' + op)
907 allow = self.configlist('web', 'allow_' + op)
908 return (allow and (allow == ['*'] or user in allow)) or default
908 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,53 +1,54 b''
1 #!/bin/sh
1 #!/bin/sh
2 # An attempt at more fully testing the hgweb web interface.
2 # An attempt at more fully testing the hgweb web interface.
3 # The following things are tested elsewhere and are therefore omitted:
3 # The following things are tested elsewhere and are therefore omitted:
4 # - archive, tested in test-archive
4 # - archive, tested in test-archive
5 # - unbundle, tested in test-push-http
5 # - unbundle, tested in test-push-http
6 # - changegroupsubset, tested in test-pull
6 # - changegroupsubset, tested in test-pull
7
7
8 echo % Set up the repo
8 echo % Set up the repo
9 hg init test
9 hg init test
10 cd test
10 cd test
11 mkdir da
11 mkdir da
12 echo foo > da/foo
12 echo foo > da/foo
13 echo foo > foo
13 echo foo > foo
14 hg ci -d'0 0' -Ambase
14 hg ci -d'0 0' -Ambase
15 hg tag 1.0
15 hg tag 1.0
16 hg serve -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
16 hg serve -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
17 cat hg.pid >> $DAEMON_PIDS
17 cat hg.pid >> $DAEMON_PIDS
18
18
19 echo % Logs and changes
19 echo % Logs and changes
20 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
20 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
21 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
21 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
22 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/foo/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
22 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/foo/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
23 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/shortlog/' | sed "s/[0-9]* years/many years/"
23 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/shortlog/' | sed "s/[0-9]* years/many years/"
24 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/1/?style=raw'
24 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/1/?style=raw'
25
25
26 echo % File-related
26 echo % File-related
27 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo/?style=raw'
27 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo/?style=raw'
28 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/annotate/1/foo/?style=raw'
28 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/annotate/1/foo/?style=raw'
29 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/?style=raw'
29 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/?style=raw'
30 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo' | sed "s/[0-9]* years/many years/"
30 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/filediff/1/foo/?style=raw'
31 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/filediff/1/foo/?style=raw'
31
32
32 echo % Overviews
33 echo % Overviews
33 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/tags/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
34 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/tags/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
34 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/summary/?style=gitweb' | sed "s/[0-9]* years ago/long ago/g"
35 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/summary/?style=gitweb' | sed "s/[0-9]* years ago/long ago/g"
35
36
36 echo % capabilities
37 echo % capabilities
37 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/capabilities'
38 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/capabilities'
38 echo % heads
39 echo % heads
39 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/heads'
40 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/heads'
40 echo % lookup
41 echo % lookup
41 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/lookup/1'
42 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/lookup/1'
42 echo % branches
43 echo % branches
43 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/branches'
44 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/branches'
44 echo % changegroup
45 echo % changegroup
45 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/changegroup'
46 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/changegroup'
46 echo % stream_out
47 echo % stream_out
47 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/stream_out'
48 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/stream_out'
48
49
49 echo % Static files
50 echo % Static files
50 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/static/style.css'
51 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/static/style.css'
51
52
52 echo % ERRORS ENCOUNTERED
53 echo % ERRORS ENCOUNTERED
53 cat errors.log
54 cat errors.log
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
General Comments 0
You need to be logged in to leave comments. Login now