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