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