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