##// END OF EJS Templates
hgweb: remove some legacy code
Dirkjan Ochtman -
r5886:dd1998dd default
parent child Browse files
Show More
@@ -1,910 +1,910
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 req.env.has_key('REPO_NAME'):
156 if req.env.has_key('REPO_NAME'):
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 tmpl = self.templater(req)
209 tmpl = self.templater(req)
210 if cmd == '':
210 if cmd == '':
211 req.form['cmd'] = [tmpl.cache['default']]
211 req.form['cmd'] = [tmpl.cache['default']]
212 cmd = req.form['cmd'][0]
212 cmd = req.form['cmd'][0]
213 method = getattr(webcommands, cmd)
213 method = getattr(webcommands, cmd)
214 method(self, req, tmpl)
214 method(self, req, tmpl)
215 del tmpl
215 del tmpl
216
216
217 except revlog.LookupError, err:
217 except revlog.LookupError, err:
218 req.respond(404, tmpl(
218 req.respond(404, tmpl(
219 'error', error='revision not found: %s' % err.name))
219 'error', error='revision not found: %s' % err.name))
220 except (hg.RepoError, revlog.RevlogError), inst:
220 except (hg.RepoError, revlog.RevlogError), inst:
221 req.respond('500 Internal Server Error',
221 req.respond('500 Internal Server Error',
222 tmpl('error', error=str(inst)))
222 tmpl('error', error=str(inst)))
223 except ErrorResponse, inst:
223 except ErrorResponse, inst:
224 req.respond(inst.code, tmpl('error', error=inst.message))
224 req.respond(inst.code, tmpl('error', error=inst.message))
225 except AttributeError:
225 except AttributeError:
226 req.respond(400, tmpl('error', error='No such method: ' + cmd))
226 req.respond(400, tmpl('error', error='No such method: ' + cmd))
227
227
228 def templater(self, req):
228 def templater(self, req):
229
229
230 # determine scheme, port and server name
230 # determine scheme, port and server name
231 # this is needed to create absolute urls
231 # this is needed to create absolute urls
232
232
233 proto = req.env.get('wsgi.url_scheme')
233 proto = req.env.get('wsgi.url_scheme')
234 if proto == 'https':
234 if proto == 'https':
235 proto = 'https'
235 proto = 'https'
236 default_port = "443"
236 default_port = "443"
237 else:
237 else:
238 proto = 'http'
238 proto = 'http'
239 default_port = "80"
239 default_port = "80"
240
240
241 port = req.env["SERVER_PORT"]
241 port = req.env["SERVER_PORT"]
242 port = port != default_port and (":" + port) or ""
242 port = port != default_port and (":" + port) or ""
243 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
243 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
244 staticurl = self.config("web", "staticurl") or req.url + 'static/'
244 staticurl = self.config("web", "staticurl") or req.url + 'static/'
245 if not staticurl.endswith('/'):
245 if not staticurl.endswith('/'):
246 staticurl += '/'
246 staticurl += '/'
247
247
248 # some functions for the templater
248 # some functions for the templater
249
249
250 def header(**map):
250 def header(**map):
251 header_file = cStringIO.StringIO(
251 header_file = cStringIO.StringIO(
252 ''.join(tmpl("header", encoding=self.encoding, **map)))
252 ''.join(tmpl("header", encoding=self.encoding, **map)))
253 msg = mimetools.Message(header_file, 0)
253 msg = mimetools.Message(header_file, 0)
254 req.header(msg.items())
254 req.header(msg.items())
255 yield header_file.read()
255 yield header_file.read()
256
256
257 def rawfileheader(**map):
257 def rawfileheader(**map):
258 req.header([('Content-type', map['mimetype']),
258 req.header([('Content-type', map['mimetype']),
259 ('Content-disposition', 'filename=%s' % map['file']),
259 ('Content-disposition', 'filename=%s' % map['file']),
260 ('Content-length', str(len(map['raw'])))])
260 ('Content-length', str(len(map['raw'])))])
261 yield ''
261 yield ''
262
262
263 def footer(**map):
263 def footer(**map):
264 yield tmpl("footer", **map)
264 yield tmpl("footer", **map)
265
265
266 def motd(**map):
266 def motd(**map):
267 yield self.config("web", "motd", "")
267 yield self.config("web", "motd", "")
268
268
269 def sessionvars(**map):
269 def sessionvars(**map):
270 fields = []
270 fields = []
271 if req.form.has_key('style'):
271 if req.form.has_key('style'):
272 style = req.form['style'][0]
272 style = req.form['style'][0]
273 if style != self.config('web', 'style', ''):
273 if style != self.config('web', 'style', ''):
274 fields.append(('style', style))
274 fields.append(('style', style))
275
275
276 separator = req.url[-1] == '?' and ';' or '?'
276 separator = req.url[-1] == '?' and ';' or '?'
277 for name, value in fields:
277 for name, value in fields:
278 yield dict(name=name, value=value, separator=separator)
278 yield dict(name=name, value=value, separator=separator)
279 separator = ';'
279 separator = ';'
280
280
281 # figure out which style to use
281 # figure out which style to use
282
282
283 style = self.config("web", "style", "")
283 style = self.config("web", "style", "")
284 if req.form.has_key('style'):
284 if req.form.has_key('style'):
285 style = req.form['style'][0]
285 style = req.form['style'][0]
286 mapfile = style_map(self.templatepath, style)
286 mapfile = style_map(self.templatepath, style)
287
287
288 if not self.reponame:
288 if not self.reponame:
289 self.reponame = (self.config("web", "name")
289 self.reponame = (self.config("web", "name")
290 or req.env.get('REPO_NAME')
290 or req.env.get('REPO_NAME')
291 or req.url.strip('/') or self.repo.root)
291 or req.url.strip('/') or self.repo.root)
292
292
293 # create the templater
293 # create the templater
294
294
295 tmpl = templater.templater(mapfile, templater.common_filters,
295 tmpl = templater.templater(mapfile, templater.common_filters,
296 defaults={"url": req.url,
296 defaults={"url": req.url,
297 "staticurl": staticurl,
297 "staticurl": staticurl,
298 "urlbase": urlbase,
298 "urlbase": urlbase,
299 "repo": self.reponame,
299 "repo": self.reponame,
300 "header": header,
300 "header": header,
301 "footer": footer,
301 "footer": footer,
302 "motd": motd,
302 "motd": motd,
303 "rawfileheader": rawfileheader,
303 "rawfileheader": rawfileheader,
304 "sessionvars": sessionvars
304 "sessionvars": sessionvars
305 })
305 })
306 return tmpl
306 return tmpl
307
307
308 def archivelist(self, nodeid):
308 def archivelist(self, nodeid):
309 allowed = self.configlist("web", "allow_archive")
309 allowed = self.configlist("web", "allow_archive")
310 for i, spec in self.archive_specs.iteritems():
310 for i, spec in self.archive_specs.iteritems():
311 if i in allowed or self.configbool("web", "allow" + i):
311 if i in allowed or self.configbool("web", "allow" + i):
312 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
312 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
313
313
314 def listfilediffs(self, tmpl, files, changeset):
314 def listfilediffs(self, tmpl, files, changeset):
315 for f in files[:self.maxfiles]:
315 for f in files[:self.maxfiles]:
316 yield tmpl("filedifflink", node=hex(changeset), file=f)
316 yield tmpl("filedifflink", node=hex(changeset), file=f)
317 if len(files) > self.maxfiles:
317 if len(files) > self.maxfiles:
318 yield tmpl("fileellipses")
318 yield tmpl("fileellipses")
319
319
320 def siblings(self, siblings=[], hiderev=None, **args):
320 def siblings(self, siblings=[], hiderev=None, **args):
321 siblings = [s for s in siblings if s.node() != nullid]
321 siblings = [s for s in siblings if s.node() != nullid]
322 if len(siblings) == 1 and siblings[0].rev() == hiderev:
322 if len(siblings) == 1 and siblings[0].rev() == hiderev:
323 return
323 return
324 for s in siblings:
324 for s in siblings:
325 d = {'node': hex(s.node()), 'rev': s.rev()}
325 d = {'node': hex(s.node()), 'rev': s.rev()}
326 if hasattr(s, 'path'):
326 if hasattr(s, 'path'):
327 d['file'] = s.path()
327 d['file'] = s.path()
328 d.update(args)
328 d.update(args)
329 yield d
329 yield d
330
330
331 def renamelink(self, fl, node):
331 def renamelink(self, fl, node):
332 r = fl.renamed(node)
332 r = fl.renamed(node)
333 if r:
333 if r:
334 return [dict(file=r[0], node=hex(r[1]))]
334 return [dict(file=r[0], node=hex(r[1]))]
335 return []
335 return []
336
336
337 def nodetagsdict(self, node):
337 def nodetagsdict(self, node):
338 return [{"name": i} for i in self.repo.nodetags(node)]
338 return [{"name": i} for i in self.repo.nodetags(node)]
339
339
340 def nodebranchdict(self, ctx):
340 def nodebranchdict(self, ctx):
341 branches = []
341 branches = []
342 branch = ctx.branch()
342 branch = ctx.branch()
343 # If this is an empty repo, ctx.node() == nullid,
343 # If this is an empty repo, ctx.node() == nullid,
344 # ctx.branch() == 'default', but branchtags() is
344 # ctx.branch() == 'default', but branchtags() is
345 # an empty dict. Using dict.get avoids a traceback.
345 # an empty dict. Using dict.get avoids a traceback.
346 if self.repo.branchtags().get(branch) == ctx.node():
346 if self.repo.branchtags().get(branch) == ctx.node():
347 branches.append({"name": branch})
347 branches.append({"name": branch})
348 return branches
348 return branches
349
349
350 def showtag(self, tmpl, t1, node=nullid, **args):
350 def showtag(self, tmpl, t1, node=nullid, **args):
351 for t in self.repo.nodetags(node):
351 for t in self.repo.nodetags(node):
352 yield tmpl(t1, tag=t, **args)
352 yield tmpl(t1, tag=t, **args)
353
353
354 def diff(self, tmpl, node1, node2, files):
354 def diff(self, tmpl, node1, node2, files):
355 def filterfiles(filters, files):
355 def filterfiles(filters, files):
356 l = [x for x in files if x in filters]
356 l = [x for x in files if x in filters]
357
357
358 for t in filters:
358 for t in filters:
359 if t and t[-1] != os.sep:
359 if t and t[-1] != os.sep:
360 t += os.sep
360 t += os.sep
361 l += [x for x in files if x.startswith(t)]
361 l += [x for x in files if x.startswith(t)]
362 return l
362 return l
363
363
364 parity = paritygen(self.stripecount)
364 parity = paritygen(self.stripecount)
365 def diffblock(diff, f, fn):
365 def diffblock(diff, f, fn):
366 yield tmpl("diffblock",
366 yield tmpl("diffblock",
367 lines=prettyprintlines(diff),
367 lines=prettyprintlines(diff),
368 parity=parity.next(),
368 parity=parity.next(),
369 file=f,
369 file=f,
370 filenode=hex(fn or nullid))
370 filenode=hex(fn or nullid))
371
371
372 def prettyprintlines(diff):
372 def prettyprintlines(diff):
373 for l in diff.splitlines(1):
373 for l in diff.splitlines(1):
374 if l.startswith('+'):
374 if l.startswith('+'):
375 yield tmpl("difflineplus", line=l)
375 yield tmpl("difflineplus", line=l)
376 elif l.startswith('-'):
376 elif l.startswith('-'):
377 yield tmpl("difflineminus", line=l)
377 yield tmpl("difflineminus", line=l)
378 elif l.startswith('@'):
378 elif l.startswith('@'):
379 yield tmpl("difflineat", line=l)
379 yield tmpl("difflineat", line=l)
380 else:
380 else:
381 yield tmpl("diffline", line=l)
381 yield tmpl("diffline", line=l)
382
382
383 r = self.repo
383 r = self.repo
384 c1 = r.changectx(node1)
384 c1 = r.changectx(node1)
385 c2 = r.changectx(node2)
385 c2 = r.changectx(node2)
386 date1 = util.datestr(c1.date())
386 date1 = util.datestr(c1.date())
387 date2 = util.datestr(c2.date())
387 date2 = util.datestr(c2.date())
388
388
389 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
389 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
390 if files:
390 if files:
391 modified, added, removed = map(lambda x: filterfiles(files, x),
391 modified, added, removed = map(lambda x: filterfiles(files, x),
392 (modified, added, removed))
392 (modified, added, removed))
393
393
394 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
394 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
395 for f in modified:
395 for f in modified:
396 to = c1.filectx(f).data()
396 to = c1.filectx(f).data()
397 tn = c2.filectx(f).data()
397 tn = c2.filectx(f).data()
398 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
398 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
399 opts=diffopts), f, tn)
399 opts=diffopts), f, tn)
400 for f in added:
400 for f in added:
401 to = None
401 to = None
402 tn = c2.filectx(f).data()
402 tn = c2.filectx(f).data()
403 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
403 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
404 opts=diffopts), f, tn)
404 opts=diffopts), f, tn)
405 for f in removed:
405 for f in removed:
406 to = c1.filectx(f).data()
406 to = c1.filectx(f).data()
407 tn = None
407 tn = None
408 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
408 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
409 opts=diffopts), f, tn)
409 opts=diffopts), f, tn)
410
410
411 def changelog(self, tmpl, ctx, shortlog=False):
411 def changelog(self, tmpl, ctx, shortlog=False):
412 def changelist(limit=0,**map):
412 def changelist(limit=0,**map):
413 cl = self.repo.changelog
413 cl = self.repo.changelog
414 l = [] # build a list in forward order for efficiency
414 l = [] # build a list in forward order for efficiency
415 for i in xrange(start, end):
415 for i in xrange(start, end):
416 ctx = self.repo.changectx(i)
416 ctx = self.repo.changectx(i)
417 n = ctx.node()
417 n = ctx.node()
418
418
419 l.insert(0, {"parity": parity.next(),
419 l.insert(0, {"parity": parity.next(),
420 "author": ctx.user(),
420 "author": ctx.user(),
421 "parent": self.siblings(ctx.parents(), i - 1),
421 "parent": self.siblings(ctx.parents(), i - 1),
422 "child": self.siblings(ctx.children(), i + 1),
422 "child": self.siblings(ctx.children(), i + 1),
423 "changelogtag": self.showtag("changelogtag",n),
423 "changelogtag": self.showtag("changelogtag",n),
424 "desc": ctx.description(),
424 "desc": ctx.description(),
425 "date": ctx.date(),
425 "date": ctx.date(),
426 "files": self.listfilediffs(tmpl, ctx.files(), n),
426 "files": self.listfilediffs(tmpl, ctx.files(), n),
427 "rev": i,
427 "rev": i,
428 "node": hex(n),
428 "node": hex(n),
429 "tags": self.nodetagsdict(n),
429 "tags": self.nodetagsdict(n),
430 "branches": self.nodebranchdict(ctx)})
430 "branches": self.nodebranchdict(ctx)})
431
431
432 if limit > 0:
432 if limit > 0:
433 l = l[:limit]
433 l = l[:limit]
434
434
435 for e in l:
435 for e in l:
436 yield e
436 yield e
437
437
438 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
438 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
439 cl = self.repo.changelog
439 cl = self.repo.changelog
440 count = cl.count()
440 count = cl.count()
441 pos = ctx.rev()
441 pos = ctx.rev()
442 start = max(0, pos - maxchanges + 1)
442 start = max(0, pos - maxchanges + 1)
443 end = min(count, start + maxchanges)
443 end = min(count, start + maxchanges)
444 pos = end - 1
444 pos = end - 1
445 parity = paritygen(self.stripecount, offset=start-end)
445 parity = paritygen(self.stripecount, offset=start-end)
446
446
447 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
447 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
448
448
449 yield tmpl(shortlog and 'shortlog' or 'changelog',
449 yield tmpl(shortlog and 'shortlog' or 'changelog',
450 changenav=changenav,
450 changenav=changenav,
451 node=hex(cl.tip()),
451 node=hex(cl.tip()),
452 rev=pos, changesets=count,
452 rev=pos, changesets=count,
453 entries=lambda **x: changelist(limit=0,**x),
453 entries=lambda **x: changelist(limit=0,**x),
454 latestentry=lambda **x: changelist(limit=1,**x),
454 latestentry=lambda **x: changelist(limit=1,**x),
455 archives=self.archivelist("tip"))
455 archives=self.archivelist("tip"))
456
456
457 def search(self, tmpl, query):
457 def search(self, tmpl, query):
458
458
459 def changelist(**map):
459 def changelist(**map):
460 cl = self.repo.changelog
460 cl = self.repo.changelog
461 count = 0
461 count = 0
462 qw = query.lower().split()
462 qw = query.lower().split()
463
463
464 def revgen():
464 def revgen():
465 for i in xrange(cl.count() - 1, 0, -100):
465 for i in xrange(cl.count() - 1, 0, -100):
466 l = []
466 l = []
467 for j in xrange(max(0, i - 100), i):
467 for j in xrange(max(0, i - 100), i):
468 ctx = self.repo.changectx(j)
468 ctx = self.repo.changectx(j)
469 l.append(ctx)
469 l.append(ctx)
470 l.reverse()
470 l.reverse()
471 for e in l:
471 for e in l:
472 yield e
472 yield e
473
473
474 for ctx in revgen():
474 for ctx in revgen():
475 miss = 0
475 miss = 0
476 for q in qw:
476 for q in qw:
477 if not (q in ctx.user().lower() or
477 if not (q in ctx.user().lower() or
478 q in ctx.description().lower() or
478 q in ctx.description().lower() or
479 q in " ".join(ctx.files()).lower()):
479 q in " ".join(ctx.files()).lower()):
480 miss = 1
480 miss = 1
481 break
481 break
482 if miss:
482 if miss:
483 continue
483 continue
484
484
485 count += 1
485 count += 1
486 n = ctx.node()
486 n = ctx.node()
487
487
488 yield tmpl('searchentry',
488 yield tmpl('searchentry',
489 parity=parity.next(),
489 parity=parity.next(),
490 author=ctx.user(),
490 author=ctx.user(),
491 parent=self.siblings(ctx.parents()),
491 parent=self.siblings(ctx.parents()),
492 child=self.siblings(ctx.children()),
492 child=self.siblings(ctx.children()),
493 changelogtag=self.showtag("changelogtag",n),
493 changelogtag=self.showtag("changelogtag",n),
494 desc=ctx.description(),
494 desc=ctx.description(),
495 date=ctx.date(),
495 date=ctx.date(),
496 files=self.listfilediffs(tmpl, ctx.files(), n),
496 files=self.listfilediffs(tmpl, ctx.files(), n),
497 rev=ctx.rev(),
497 rev=ctx.rev(),
498 node=hex(n),
498 node=hex(n),
499 tags=self.nodetagsdict(n),
499 tags=self.nodetagsdict(n),
500 branches=self.nodebranchdict(ctx))
500 branches=self.nodebranchdict(ctx))
501
501
502 if count >= self.maxchanges:
502 if count >= self.maxchanges:
503 break
503 break
504
504
505 cl = self.repo.changelog
505 cl = self.repo.changelog
506 parity = paritygen(self.stripecount)
506 parity = paritygen(self.stripecount)
507
507
508 yield tmpl('search',
508 yield tmpl('search',
509 query=query,
509 query=query,
510 node=hex(cl.tip()),
510 node=hex(cl.tip()),
511 entries=changelist,
511 entries=changelist,
512 archives=self.archivelist("tip"))
512 archives=self.archivelist("tip"))
513
513
514 def changeset(self, tmpl, ctx):
514 def changeset(self, tmpl, ctx):
515 n = ctx.node()
515 n = ctx.node()
516 parents = ctx.parents()
516 parents = ctx.parents()
517 p1 = parents[0].node()
517 p1 = parents[0].node()
518
518
519 files = []
519 files = []
520 parity = paritygen(self.stripecount)
520 parity = paritygen(self.stripecount)
521 for f in ctx.files():
521 for f in ctx.files():
522 files.append(tmpl("filenodelink",
522 files.append(tmpl("filenodelink",
523 node=hex(n), file=f,
523 node=hex(n), file=f,
524 parity=parity.next()))
524 parity=parity.next()))
525
525
526 def diff(**map):
526 def diff(**map):
527 yield self.diff(tmpl, p1, n, None)
527 yield self.diff(tmpl, p1, n, None)
528
528
529 yield tmpl('changeset',
529 yield tmpl('changeset',
530 diff=diff,
530 diff=diff,
531 rev=ctx.rev(),
531 rev=ctx.rev(),
532 node=hex(n),
532 node=hex(n),
533 parent=self.siblings(parents),
533 parent=self.siblings(parents),
534 child=self.siblings(ctx.children()),
534 child=self.siblings(ctx.children()),
535 changesettag=self.showtag("changesettag",n),
535 changesettag=self.showtag("changesettag",n),
536 author=ctx.user(),
536 author=ctx.user(),
537 desc=ctx.description(),
537 desc=ctx.description(),
538 date=ctx.date(),
538 date=ctx.date(),
539 files=files,
539 files=files,
540 archives=self.archivelist(hex(n)),
540 archives=self.archivelist(hex(n)),
541 tags=self.nodetagsdict(n),
541 tags=self.nodetagsdict(n),
542 branches=self.nodebranchdict(ctx))
542 branches=self.nodebranchdict(ctx))
543
543
544 def filelog(self, tmpl, fctx):
544 def filelog(self, tmpl, fctx):
545 f = fctx.path()
545 f = fctx.path()
546 fl = fctx.filelog()
546 fl = fctx.filelog()
547 count = fl.count()
547 count = fl.count()
548 pagelen = self.maxshortchanges
548 pagelen = self.maxshortchanges
549 pos = fctx.filerev()
549 pos = fctx.filerev()
550 start = max(0, pos - pagelen + 1)
550 start = max(0, pos - pagelen + 1)
551 end = min(count, start + pagelen)
551 end = min(count, start + pagelen)
552 pos = end - 1
552 pos = end - 1
553 parity = paritygen(self.stripecount, offset=start-end)
553 parity = paritygen(self.stripecount, offset=start-end)
554
554
555 def entries(limit=0, **map):
555 def entries(limit=0, **map):
556 l = []
556 l = []
557
557
558 for i in xrange(start, end):
558 for i in xrange(start, end):
559 ctx = fctx.filectx(i)
559 ctx = fctx.filectx(i)
560 n = fl.node(i)
560 n = fl.node(i)
561
561
562 l.insert(0, {"parity": parity.next(),
562 l.insert(0, {"parity": parity.next(),
563 "filerev": i,
563 "filerev": i,
564 "file": f,
564 "file": f,
565 "node": hex(ctx.node()),
565 "node": hex(ctx.node()),
566 "author": ctx.user(),
566 "author": ctx.user(),
567 "date": ctx.date(),
567 "date": ctx.date(),
568 "rename": self.renamelink(fl, n),
568 "rename": self.renamelink(fl, n),
569 "parent": self.siblings(fctx.parents()),
569 "parent": self.siblings(fctx.parents()),
570 "child": self.siblings(fctx.children()),
570 "child": self.siblings(fctx.children()),
571 "desc": ctx.description()})
571 "desc": ctx.description()})
572
572
573 if limit > 0:
573 if limit > 0:
574 l = l[:limit]
574 l = l[:limit]
575
575
576 for e in l:
576 for e in l:
577 yield e
577 yield e
578
578
579 nodefunc = lambda x: fctx.filectx(fileid=x)
579 nodefunc = lambda x: fctx.filectx(fileid=x)
580 nav = revnavgen(pos, pagelen, count, nodefunc)
580 nav = revnavgen(pos, pagelen, count, nodefunc)
581 yield tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
581 yield tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
582 entries=lambda **x: entries(limit=0, **x),
582 entries=lambda **x: entries(limit=0, **x),
583 latestentry=lambda **x: entries(limit=1, **x))
583 latestentry=lambda **x: entries(limit=1, **x))
584
584
585 def filerevision(self, tmpl, fctx):
585 def filerevision(self, tmpl, fctx):
586 f = fctx.path()
586 f = fctx.path()
587 text = fctx.data()
587 text = fctx.data()
588 fl = fctx.filelog()
588 fl = fctx.filelog()
589 n = fctx.filenode()
589 n = fctx.filenode()
590 parity = paritygen(self.stripecount)
590 parity = paritygen(self.stripecount)
591
591
592 mt = mimetypes.guess_type(f)[0]
592 mt = mimetypes.guess_type(f)[0]
593 rawtext = text
593 rawtext = text
594 if util.binary(text):
594 if util.binary(text):
595 mt = mt or 'application/octet-stream'
595 mt = mt or 'application/octet-stream'
596 text = "(binary:%s)" % mt
596 text = "(binary:%s)" % mt
597 mt = mt or 'text/plain'
597 mt = mt or 'text/plain'
598
598
599 def lines():
599 def lines():
600 for l, t in enumerate(text.splitlines(1)):
600 for l, t in enumerate(text.splitlines(1)):
601 yield {"line": t,
601 yield {"line": t,
602 "linenumber": "% 6d" % (l + 1),
602 "linenumber": "% 6d" % (l + 1),
603 "parity": parity.next()}
603 "parity": parity.next()}
604
604
605 yield tmpl("filerevision",
605 yield tmpl("filerevision",
606 file=f,
606 file=f,
607 path=_up(f),
607 path=_up(f),
608 text=lines(),
608 text=lines(),
609 raw=rawtext,
609 raw=rawtext,
610 mimetype=mt,
610 mimetype=mt,
611 rev=fctx.rev(),
611 rev=fctx.rev(),
612 node=hex(fctx.node()),
612 node=hex(fctx.node()),
613 author=fctx.user(),
613 author=fctx.user(),
614 date=fctx.date(),
614 date=fctx.date(),
615 desc=fctx.description(),
615 desc=fctx.description(),
616 parent=self.siblings(fctx.parents()),
616 parent=self.siblings(fctx.parents()),
617 child=self.siblings(fctx.children()),
617 child=self.siblings(fctx.children()),
618 rename=self.renamelink(fl, n),
618 rename=self.renamelink(fl, n),
619 permissions=fctx.manifest().flags(f))
619 permissions=fctx.manifest().flags(f))
620
620
621 def fileannotate(self, tmpl, fctx):
621 def fileannotate(self, tmpl, fctx):
622 f = fctx.path()
622 f = fctx.path()
623 n = fctx.filenode()
623 n = fctx.filenode()
624 fl = fctx.filelog()
624 fl = fctx.filelog()
625 parity = paritygen(self.stripecount)
625 parity = paritygen(self.stripecount)
626
626
627 def annotate(**map):
627 def annotate(**map):
628 last = None
628 last = None
629 for f, l in fctx.annotate(follow=True):
629 for f, l in fctx.annotate(follow=True):
630 fnode = f.filenode()
630 fnode = f.filenode()
631 name = self.repo.ui.shortuser(f.user())
631 name = self.repo.ui.shortuser(f.user())
632
632
633 if last != fnode:
633 if last != fnode:
634 last = fnode
634 last = fnode
635
635
636 yield {"parity": parity.next(),
636 yield {"parity": parity.next(),
637 "node": hex(f.node()),
637 "node": hex(f.node()),
638 "rev": f.rev(),
638 "rev": f.rev(),
639 "author": name,
639 "author": name,
640 "file": f.path(),
640 "file": f.path(),
641 "line": l}
641 "line": l}
642
642
643 yield tmpl("fileannotate",
643 yield tmpl("fileannotate",
644 file=f,
644 file=f,
645 annotate=annotate,
645 annotate=annotate,
646 path=_up(f),
646 path=_up(f),
647 rev=fctx.rev(),
647 rev=fctx.rev(),
648 node=hex(fctx.node()),
648 node=hex(fctx.node()),
649 author=fctx.user(),
649 author=fctx.user(),
650 date=fctx.date(),
650 date=fctx.date(),
651 desc=fctx.description(),
651 desc=fctx.description(),
652 rename=self.renamelink(fl, n),
652 rename=self.renamelink(fl, n),
653 parent=self.siblings(fctx.parents()),
653 parent=self.siblings(fctx.parents()),
654 child=self.siblings(fctx.children()),
654 child=self.siblings(fctx.children()),
655 permissions=fctx.manifest().flags(f))
655 permissions=fctx.manifest().flags(f))
656
656
657 def manifest(self, tmpl, ctx, path):
657 def manifest(self, tmpl, ctx, path):
658 mf = ctx.manifest()
658 mf = ctx.manifest()
659 node = ctx.node()
659 node = ctx.node()
660
660
661 files = {}
661 files = {}
662 parity = paritygen(self.stripecount)
662 parity = paritygen(self.stripecount)
663
663
664 if path and path[-1] != "/":
664 if path and path[-1] != "/":
665 path += "/"
665 path += "/"
666 l = len(path)
666 l = len(path)
667 abspath = "/" + path
667 abspath = "/" + path
668
668
669 for f, n in mf.items():
669 for f, n in mf.items():
670 if f[:l] != path:
670 if f[:l] != path:
671 continue
671 continue
672 remain = f[l:]
672 remain = f[l:]
673 if "/" in remain:
673 if "/" in remain:
674 short = remain[:remain.index("/") + 1] # bleah
674 short = remain[:remain.index("/") + 1] # bleah
675 files[short] = (f, None)
675 files[short] = (f, None)
676 else:
676 else:
677 short = os.path.basename(remain)
677 short = os.path.basename(remain)
678 files[short] = (f, n)
678 files[short] = (f, n)
679
679
680 if not files:
680 if not files:
681 raise ErrorResponse(404, 'Path not found: ' + path)
681 raise ErrorResponse(404, 'Path not found: ' + path)
682
682
683 def filelist(**map):
683 def filelist(**map):
684 fl = files.keys()
684 fl = files.keys()
685 fl.sort()
685 fl.sort()
686 for f in fl:
686 for f in fl:
687 full, fnode = files[f]
687 full, fnode = files[f]
688 if not fnode:
688 if not fnode:
689 continue
689 continue
690
690
691 fctx = ctx.filectx(full)
691 fctx = ctx.filectx(full)
692 yield {"file": full,
692 yield {"file": full,
693 "parity": parity.next(),
693 "parity": parity.next(),
694 "basename": f,
694 "basename": f,
695 "date": fctx.changectx().date(),
695 "date": fctx.changectx().date(),
696 "size": fctx.size(),
696 "size": fctx.size(),
697 "permissions": mf.flags(full)}
697 "permissions": mf.flags(full)}
698
698
699 def dirlist(**map):
699 def dirlist(**map):
700 fl = files.keys()
700 fl = files.keys()
701 fl.sort()
701 fl.sort()
702 for f in fl:
702 for f in fl:
703 full, fnode = files[f]
703 full, fnode = files[f]
704 if fnode:
704 if fnode:
705 continue
705 continue
706
706
707 yield {"parity": parity.next(),
707 yield {"parity": parity.next(),
708 "path": "%s%s" % (abspath, f),
708 "path": "%s%s" % (abspath, f),
709 "basename": f[:-1]}
709 "basename": f[:-1]}
710
710
711 yield tmpl("manifest",
711 yield tmpl("manifest",
712 rev=ctx.rev(),
712 rev=ctx.rev(),
713 node=hex(node),
713 node=hex(node),
714 path=abspath,
714 path=abspath,
715 up=_up(abspath),
715 up=_up(abspath),
716 upparity=parity.next(),
716 upparity=parity.next(),
717 fentries=filelist,
717 fentries=filelist,
718 dentries=dirlist,
718 dentries=dirlist,
719 archives=self.archivelist(hex(node)),
719 archives=self.archivelist(hex(node)),
720 tags=self.nodetagsdict(node),
720 tags=self.nodetagsdict(node),
721 branches=self.nodebranchdict(ctx))
721 branches=self.nodebranchdict(ctx))
722
722
723 def tags(self, tmpl):
723 def tags(self, tmpl):
724 i = self.repo.tagslist()
724 i = self.repo.tagslist()
725 i.reverse()
725 i.reverse()
726 parity = paritygen(self.stripecount)
726 parity = paritygen(self.stripecount)
727
727
728 def entries(notip=False,limit=0, **map):
728 def entries(notip=False,limit=0, **map):
729 count = 0
729 count = 0
730 for k, n in i:
730 for k, n in i:
731 if notip and k == "tip":
731 if notip and k == "tip":
732 continue
732 continue
733 if limit > 0 and count >= limit:
733 if limit > 0 and count >= limit:
734 continue
734 continue
735 count = count + 1
735 count = count + 1
736 yield {"parity": parity.next(),
736 yield {"parity": parity.next(),
737 "tag": k,
737 "tag": k,
738 "date": self.repo.changectx(n).date(),
738 "date": self.repo.changectx(n).date(),
739 "node": hex(n)}
739 "node": hex(n)}
740
740
741 yield tmpl("tags",
741 yield tmpl("tags",
742 node=hex(self.repo.changelog.tip()),
742 node=hex(self.repo.changelog.tip()),
743 entries=lambda **x: entries(False,0, **x),
743 entries=lambda **x: entries(False,0, **x),
744 entriesnotip=lambda **x: entries(True,0, **x),
744 entriesnotip=lambda **x: entries(True,0, **x),
745 latestentry=lambda **x: entries(True,1, **x))
745 latestentry=lambda **x: entries(True,1, **x))
746
746
747 def summary(self, tmpl):
747 def summary(self, tmpl):
748 i = self.repo.tagslist()
748 i = self.repo.tagslist()
749 i.reverse()
749 i.reverse()
750
750
751 def tagentries(**map):
751 def tagentries(**map):
752 parity = paritygen(self.stripecount)
752 parity = paritygen(self.stripecount)
753 count = 0
753 count = 0
754 for k, n in i:
754 for k, n in i:
755 if k == "tip": # skip tip
755 if k == "tip": # skip tip
756 continue;
756 continue;
757
757
758 count += 1
758 count += 1
759 if count > 10: # limit to 10 tags
759 if count > 10: # limit to 10 tags
760 break;
760 break;
761
761
762 yield tmpl("tagentry",
762 yield tmpl("tagentry",
763 parity=parity.next(),
763 parity=parity.next(),
764 tag=k,
764 tag=k,
765 node=hex(n),
765 node=hex(n),
766 date=self.repo.changectx(n).date())
766 date=self.repo.changectx(n).date())
767
767
768
768
769 def branches(**map):
769 def branches(**map):
770 parity = paritygen(self.stripecount)
770 parity = paritygen(self.stripecount)
771
771
772 b = self.repo.branchtags()
772 b = self.repo.branchtags()
773 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
773 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
774 l.sort()
774 l.sort()
775
775
776 for r,n,t in l:
776 for r,n,t in l:
777 ctx = self.repo.changectx(n)
777 ctx = self.repo.changectx(n)
778
778
779 yield {'parity': parity.next(),
779 yield {'parity': parity.next(),
780 'branch': t,
780 'branch': t,
781 'node': hex(n),
781 'node': hex(n),
782 'date': ctx.date()}
782 'date': ctx.date()}
783
783
784 def changelist(**map):
784 def changelist(**map):
785 parity = paritygen(self.stripecount, offset=start-end)
785 parity = paritygen(self.stripecount, offset=start-end)
786 l = [] # build a list in forward order for efficiency
786 l = [] # build a list in forward order for efficiency
787 for i in xrange(start, end):
787 for i in xrange(start, end):
788 ctx = self.repo.changectx(i)
788 ctx = self.repo.changectx(i)
789 n = ctx.node()
789 n = ctx.node()
790 hn = hex(n)
790 hn = hex(n)
791
791
792 l.insert(0, tmpl(
792 l.insert(0, tmpl(
793 'shortlogentry',
793 'shortlogentry',
794 parity=parity.next(),
794 parity=parity.next(),
795 author=ctx.user(),
795 author=ctx.user(),
796 desc=ctx.description(),
796 desc=ctx.description(),
797 date=ctx.date(),
797 date=ctx.date(),
798 rev=i,
798 rev=i,
799 node=hn,
799 node=hn,
800 tags=self.nodetagsdict(n),
800 tags=self.nodetagsdict(n),
801 branches=self.nodebranchdict(ctx)))
801 branches=self.nodebranchdict(ctx)))
802
802
803 yield l
803 yield l
804
804
805 cl = self.repo.changelog
805 cl = self.repo.changelog
806 count = cl.count()
806 count = cl.count()
807 start = max(0, count - self.maxchanges)
807 start = max(0, count - self.maxchanges)
808 end = min(count, start + self.maxchanges)
808 end = min(count, start + self.maxchanges)
809
809
810 yield tmpl("summary",
810 yield tmpl("summary",
811 desc=self.config("web", "description", "unknown"),
811 desc=self.config("web", "description", "unknown"),
812 owner=get_contact(self.config) or "unknown",
812 owner=get_contact(self.config) or "unknown",
813 lastchange=cl.read(cl.tip())[2],
813 lastchange=cl.read(cl.tip())[2],
814 tags=tagentries,
814 tags=tagentries,
815 branches=branches,
815 branches=branches,
816 shortlog=changelist,
816 shortlog=changelist,
817 node=hex(cl.tip()),
817 node=hex(cl.tip()),
818 archives=self.archivelist("tip"))
818 archives=self.archivelist("tip"))
819
819
820 def filediff(self, tmpl, fctx):
820 def filediff(self, tmpl, fctx):
821 n = fctx.node()
821 n = fctx.node()
822 path = fctx.path()
822 path = fctx.path()
823 parents = fctx.parents()
823 parents = fctx.parents()
824 p1 = parents and parents[0].node() or nullid
824 p1 = parents and parents[0].node() or nullid
825
825
826 def diff(**map):
826 def diff(**map):
827 yield self.diff(tmpl, p1, n, [path])
827 yield self.diff(tmpl, p1, n, [path])
828
828
829 yield tmpl("filediff",
829 yield tmpl("filediff",
830 file=path,
830 file=path,
831 node=hex(n),
831 node=hex(n),
832 rev=fctx.rev(),
832 rev=fctx.rev(),
833 parent=self.siblings(parents),
833 parent=self.siblings(parents),
834 child=self.siblings(fctx.children()),
834 child=self.siblings(fctx.children()),
835 diff=diff)
835 diff=diff)
836
836
837 archive_specs = {
837 archive_specs = {
838 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
838 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
839 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
839 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
840 'zip': ('application/zip', 'zip', '.zip', None),
840 'zip': ('application/zip', 'zip', '.zip', None),
841 }
841 }
842
842
843 def archive(self, tmpl, req, key, type_):
843 def archive(self, tmpl, req, key, type_):
844 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
844 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
845 cnode = self.repo.lookup(key)
845 cnode = self.repo.lookup(key)
846 arch_version = key
846 arch_version = key
847 if cnode == key or key == 'tip':
847 if cnode == key or key == 'tip':
848 arch_version = short(cnode)
848 arch_version = short(cnode)
849 name = "%s-%s" % (reponame, arch_version)
849 name = "%s-%s" % (reponame, arch_version)
850 mimetype, artype, extension, encoding = self.archive_specs[type_]
850 mimetype, artype, extension, encoding = self.archive_specs[type_]
851 headers = [('Content-type', mimetype),
851 headers = [('Content-type', mimetype),
852 ('Content-disposition', 'attachment; filename=%s%s' %
852 ('Content-disposition', 'attachment; filename=%s%s' %
853 (name, extension))]
853 (name, extension))]
854 if encoding:
854 if encoding:
855 headers.append(('Content-encoding', encoding))
855 headers.append(('Content-encoding', encoding))
856 req.header(headers)
856 req.header(headers)
857 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
857 archival.archive(self.repo, req, cnode, artype, prefix=name)
858
858
859 # add tags to things
859 # add tags to things
860 # tags -> list of changesets corresponding to tags
860 # tags -> list of changesets corresponding to tags
861 # find tag, changeset, file
861 # find tag, changeset, file
862
862
863 def cleanpath(self, path):
863 def cleanpath(self, path):
864 path = path.lstrip('/')
864 path = path.lstrip('/')
865 return util.canonpath(self.repo.root, '', path)
865 return util.canonpath(self.repo.root, '', path)
866
866
867 def changectx(self, req):
867 def changectx(self, req):
868 if req.form.has_key('node'):
868 if req.form.has_key('node'):
869 changeid = req.form['node'][0]
869 changeid = req.form['node'][0]
870 elif req.form.has_key('manifest'):
870 elif req.form.has_key('manifest'):
871 changeid = req.form['manifest'][0]
871 changeid = req.form['manifest'][0]
872 else:
872 else:
873 changeid = self.repo.changelog.count() - 1
873 changeid = self.repo.changelog.count() - 1
874
874
875 try:
875 try:
876 ctx = self.repo.changectx(changeid)
876 ctx = self.repo.changectx(changeid)
877 except hg.RepoError:
877 except hg.RepoError:
878 man = self.repo.manifest
878 man = self.repo.manifest
879 mn = man.lookup(changeid)
879 mn = man.lookup(changeid)
880 ctx = self.repo.changectx(man.linkrev(mn))
880 ctx = self.repo.changectx(man.linkrev(mn))
881
881
882 return ctx
882 return ctx
883
883
884 def filectx(self, req):
884 def filectx(self, req):
885 path = self.cleanpath(req.form['file'][0])
885 path = self.cleanpath(req.form['file'][0])
886 if req.form.has_key('node'):
886 if req.form.has_key('node'):
887 changeid = req.form['node'][0]
887 changeid = req.form['node'][0]
888 else:
888 else:
889 changeid = req.form['filenode'][0]
889 changeid = req.form['filenode'][0]
890 try:
890 try:
891 ctx = self.repo.changectx(changeid)
891 ctx = self.repo.changectx(changeid)
892 fctx = ctx.filectx(path)
892 fctx = ctx.filectx(path)
893 except hg.RepoError:
893 except hg.RepoError:
894 fctx = self.repo.filectx(path, fileid=changeid)
894 fctx = self.repo.filectx(path, fileid=changeid)
895
895
896 return fctx
896 return fctx
897
897
898 def check_perm(self, req, op, default):
898 def check_perm(self, req, op, default):
899 '''check permission for operation based on user auth.
899 '''check permission for operation based on user auth.
900 return true if op allowed, else false.
900 return true if op allowed, else false.
901 default is policy to use if no config given.'''
901 default is policy to use if no config given.'''
902
902
903 user = req.env.get('REMOTE_USER')
903 user = req.env.get('REMOTE_USER')
904
904
905 deny = self.configlist('web', 'deny_' + op)
905 deny = self.configlist('web', 'deny_' + op)
906 if deny and (not user or deny == ['*'] or user in deny):
906 if deny and (not user or deny == ['*'] or user in deny):
907 return False
907 return False
908
908
909 allow = self.configlist('web', 'allow_' + op)
909 allow = self.configlist('web', 'allow_' + op)
910 return (allow and (allow == ['*'] or user in allow)) or default
910 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,92 +1,90
1 # hgweb/request.py - An http request from either CGI or the standalone server.
1 # hgweb/request.py - An http request from either CGI or the standalone server.
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, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 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 socket, cgi, errno
9 import socket, cgi, errno
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from common import ErrorResponse, statusmessage
11 from common import ErrorResponse, statusmessage
12
12
13 class wsgirequest(object):
13 class wsgirequest(object):
14 def __init__(self, wsgienv, start_response):
14 def __init__(self, wsgienv, start_response):
15 version = wsgienv['wsgi.version']
15 version = wsgienv['wsgi.version']
16 if (version < (1, 0)) or (version >= (2, 0)):
16 if (version < (1, 0)) or (version >= (2, 0)):
17 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
17 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
18 % version)
18 % version)
19 self.inp = wsgienv['wsgi.input']
19 self.inp = wsgienv['wsgi.input']
20 self.server_write = None
20 self.server_write = None
21 self.err = wsgienv['wsgi.errors']
21 self.err = wsgienv['wsgi.errors']
22 self.threaded = wsgienv['wsgi.multithread']
22 self.threaded = wsgienv['wsgi.multithread']
23 self.multiprocess = wsgienv['wsgi.multiprocess']
23 self.multiprocess = wsgienv['wsgi.multiprocess']
24 self.run_once = wsgienv['wsgi.run_once']
24 self.run_once = wsgienv['wsgi.run_once']
25 self.env = wsgienv
25 self.env = wsgienv
26 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
26 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
27 self.start_response = start_response
27 self.start_response = start_response
28 self.headers = []
28 self.headers = []
29
29
30 out = property(lambda self: self)
31
32 def __iter__(self):
30 def __iter__(self):
33 return iter([])
31 return iter([])
34
32
35 def read(self, count=-1):
33 def read(self, count=-1):
36 return self.inp.read(count)
34 return self.inp.read(count)
37
35
38 def respond(self, status, *things):
36 def respond(self, status, *things):
39 for thing in things:
37 for thing in things:
40 if hasattr(thing, "__iter__"):
38 if hasattr(thing, "__iter__"):
41 for part in thing:
39 for part in thing:
42 self.respond(status, part)
40 self.respond(status, part)
43 else:
41 else:
44 thing = str(thing)
42 thing = str(thing)
45 if self.server_write is None:
43 if self.server_write is None:
46 if not self.headers:
44 if not self.headers:
47 raise RuntimeError("request.write called before headers sent (%s)." % thing)
45 raise RuntimeError("request.write called before headers sent (%s)." % thing)
48 if isinstance(status, ErrorResponse):
46 if isinstance(status, ErrorResponse):
49 status = statusmessage(status.code)
47 status = statusmessage(status.code)
50 elif isinstance(status, int):
48 elif isinstance(status, int):
51 status = statusmessage(status)
49 status = statusmessage(status)
52 self.server_write = self.start_response(status,
50 self.server_write = self.start_response(status,
53 self.headers)
51 self.headers)
54 self.start_response = None
52 self.start_response = None
55 self.headers = []
53 self.headers = []
56 try:
54 try:
57 self.server_write(thing)
55 self.server_write(thing)
58 except socket.error, inst:
56 except socket.error, inst:
59 if inst[0] != errno.ECONNRESET:
57 if inst[0] != errno.ECONNRESET:
60 raise
58 raise
61
59
62 def write(self, *things):
60 def write(self, *things):
63 self.respond('200 Script output follows', *things)
61 self.respond('200 Script output follows', *things)
64
62
65 def writelines(self, lines):
63 def writelines(self, lines):
66 for line in lines:
64 for line in lines:
67 self.write(line)
65 self.write(line)
68
66
69 def flush(self):
67 def flush(self):
70 return None
68 return None
71
69
72 def close(self):
70 def close(self):
73 return None
71 return None
74
72
75 def header(self, headers=[('Content-type','text/html')]):
73 def header(self, headers=[('Content-type','text/html')]):
76 self.headers.extend(headers)
74 self.headers.extend(headers)
77
75
78 def httphdr(self, type, filename=None, length=0, headers={}):
76 def httphdr(self, type, filename=None, length=0, headers={}):
79 headers = headers.items()
77 headers = headers.items()
80 headers.append(('Content-type', type))
78 headers.append(('Content-type', type))
81 if filename:
79 if filename:
82 headers.append(('Content-disposition', 'attachment; filename=%s' %
80 headers.append(('Content-disposition', 'attachment; filename=%s' %
83 filename))
81 filename))
84 if length:
82 if length:
85 headers.append(('Content-length', str(length)))
83 headers.append(('Content-length', str(length)))
86 self.header(headers)
84 self.header(headers)
87
85
88 def wsgiapplication(app_maker):
86 def wsgiapplication(app_maker):
89 application = app_maker()
87 application = app_maker()
90 def run_wsgi(env, respond):
88 def run_wsgi(env, respond):
91 application(env, respond)
89 application(env, respond)
92 return run_wsgi
90 return run_wsgi
General Comments 0
You need to be logged in to leave comments. Login now