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