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