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