##// END OF EJS Templates
hgwebdir: honor web.templates and web.static for static files (issue3734)
Matt Mackall -
r18191:e4f17956 stable
parent child Browse files
Show More
@@ -1,451 +1,457 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 of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import os, re, time
9 import os, re, time
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import ui, hg, scmutil, util, templater
11 from mercurial import ui, hg, scmutil, util, templater
12 from mercurial import error, encoding
12 from mercurial import error, encoding
13 from common import ErrorResponse, get_mtime, staticfile, paritygen, \
13 from common import ErrorResponse, get_mtime, staticfile, 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 import webutil
17 import webutil
18
18
19 def cleannames(items):
19 def cleannames(items):
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
21
21
22 def findrepos(paths):
22 def findrepos(paths):
23 repos = []
23 repos = []
24 for prefix, root in cleannames(paths):
24 for prefix, root in cleannames(paths):
25 roothead, roottail = os.path.split(root)
25 roothead, roottail = os.path.split(root)
26 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
26 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
27 # /bar/ be served as as foo/N .
27 # /bar/ be served as as foo/N .
28 # '*' will not search inside dirs with .hg (except .hg/patches),
28 # '*' will not search inside dirs with .hg (except .hg/patches),
29 # '**' will search inside dirs with .hg (and thus also find subrepos).
29 # '**' will search inside dirs with .hg (and thus also find subrepos).
30 try:
30 try:
31 recurse = {'*': False, '**': True}[roottail]
31 recurse = {'*': False, '**': True}[roottail]
32 except KeyError:
32 except KeyError:
33 repos.append((prefix, root))
33 repos.append((prefix, root))
34 continue
34 continue
35 roothead = os.path.normpath(os.path.abspath(roothead))
35 roothead = os.path.normpath(os.path.abspath(roothead))
36 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
36 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
37 repos.extend(urlrepos(prefix, roothead, paths))
37 repos.extend(urlrepos(prefix, roothead, paths))
38 return repos
38 return repos
39
39
40 def urlrepos(prefix, roothead, paths):
40 def urlrepos(prefix, roothead, paths):
41 """yield url paths and filesystem paths from a list of repo paths
41 """yield url paths and filesystem paths from a list of repo paths
42
42
43 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
43 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
44 >>> conv(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
44 >>> conv(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
45 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
45 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
46 >>> conv(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
46 >>> conv(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
47 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
47 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
48 """
48 """
49 for path in paths:
49 for path in paths:
50 path = os.path.normpath(path)
50 path = os.path.normpath(path)
51 yield (prefix + '/' +
51 yield (prefix + '/' +
52 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path
52 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path
53
53
54 def geturlcgivars(baseurl, port):
54 def geturlcgivars(baseurl, port):
55 """
55 """
56 Extract CGI variables from baseurl
56 Extract CGI variables from baseurl
57
57
58 >>> geturlcgivars("http://host.org/base", "80")
58 >>> geturlcgivars("http://host.org/base", "80")
59 ('host.org', '80', '/base')
59 ('host.org', '80', '/base')
60 >>> geturlcgivars("http://host.org:8000/base", "80")
60 >>> geturlcgivars("http://host.org:8000/base", "80")
61 ('host.org', '8000', '/base')
61 ('host.org', '8000', '/base')
62 >>> geturlcgivars('/base', 8000)
62 >>> geturlcgivars('/base', 8000)
63 ('', '8000', '/base')
63 ('', '8000', '/base')
64 >>> geturlcgivars("base", '8000')
64 >>> geturlcgivars("base", '8000')
65 ('', '8000', '/base')
65 ('', '8000', '/base')
66 >>> geturlcgivars("http://host", '8000')
66 >>> geturlcgivars("http://host", '8000')
67 ('host', '8000', '/')
67 ('host', '8000', '/')
68 >>> geturlcgivars("http://host/", '8000')
68 >>> geturlcgivars("http://host/", '8000')
69 ('host', '8000', '/')
69 ('host', '8000', '/')
70 """
70 """
71 u = util.url(baseurl)
71 u = util.url(baseurl)
72 name = u.host or ''
72 name = u.host or ''
73 if u.port:
73 if u.port:
74 port = u.port
74 port = u.port
75 path = u.path or ""
75 path = u.path or ""
76 if not path.startswith('/'):
76 if not path.startswith('/'):
77 path = '/' + path
77 path = '/' + path
78
78
79 return name, str(port), path
79 return name, str(port), path
80
80
81 class hgwebdir(object):
81 class hgwebdir(object):
82 refreshinterval = 20
82 refreshinterval = 20
83
83
84 def __init__(self, conf, baseui=None):
84 def __init__(self, conf, baseui=None):
85 self.conf = conf
85 self.conf = conf
86 self.baseui = baseui
86 self.baseui = baseui
87 self.lastrefresh = 0
87 self.lastrefresh = 0
88 self.motd = None
88 self.motd = None
89 self.refresh()
89 self.refresh()
90
90
91 def refresh(self):
91 def refresh(self):
92 if self.lastrefresh + self.refreshinterval > time.time():
92 if self.lastrefresh + self.refreshinterval > time.time():
93 return
93 return
94
94
95 if self.baseui:
95 if self.baseui:
96 u = self.baseui.copy()
96 u = self.baseui.copy()
97 else:
97 else:
98 u = ui.ui()
98 u = ui.ui()
99 u.setconfig('ui', 'report_untrusted', 'off')
99 u.setconfig('ui', 'report_untrusted', 'off')
100 u.setconfig('ui', 'nontty', 'true')
100 u.setconfig('ui', 'nontty', 'true')
101
101
102 if not isinstance(self.conf, (dict, list, tuple)):
102 if not isinstance(self.conf, (dict, list, tuple)):
103 map = {'paths': 'hgweb-paths'}
103 map = {'paths': 'hgweb-paths'}
104 if not os.path.exists(self.conf):
104 if not os.path.exists(self.conf):
105 raise util.Abort(_('config file %s not found!') % self.conf)
105 raise util.Abort(_('config file %s not found!') % self.conf)
106 u.readconfig(self.conf, remap=map, trust=True)
106 u.readconfig(self.conf, remap=map, trust=True)
107 paths = []
107 paths = []
108 for name, ignored in u.configitems('hgweb-paths'):
108 for name, ignored in u.configitems('hgweb-paths'):
109 for path in u.configlist('hgweb-paths', name):
109 for path in u.configlist('hgweb-paths', name):
110 paths.append((name, path))
110 paths.append((name, path))
111 elif isinstance(self.conf, (list, tuple)):
111 elif isinstance(self.conf, (list, tuple)):
112 paths = self.conf
112 paths = self.conf
113 elif isinstance(self.conf, dict):
113 elif isinstance(self.conf, dict):
114 paths = self.conf.items()
114 paths = self.conf.items()
115
115
116 repos = findrepos(paths)
116 repos = findrepos(paths)
117 for prefix, root in u.configitems('collections'):
117 for prefix, root in u.configitems('collections'):
118 prefix = util.pconvert(prefix)
118 prefix = util.pconvert(prefix)
119 for path in scmutil.walkrepos(root, followsym=True):
119 for path in scmutil.walkrepos(root, followsym=True):
120 repo = os.path.normpath(path)
120 repo = os.path.normpath(path)
121 name = util.pconvert(repo)
121 name = util.pconvert(repo)
122 if name.startswith(prefix):
122 if name.startswith(prefix):
123 name = name[len(prefix):]
123 name = name[len(prefix):]
124 repos.append((name.lstrip('/'), repo))
124 repos.append((name.lstrip('/'), repo))
125
125
126 self.repos = repos
126 self.repos = repos
127 self.ui = u
127 self.ui = u
128 encoding.encoding = self.ui.config('web', 'encoding',
128 encoding.encoding = self.ui.config('web', 'encoding',
129 encoding.encoding)
129 encoding.encoding)
130 self.style = self.ui.config('web', 'style', 'paper')
130 self.style = self.ui.config('web', 'style', 'paper')
131 self.templatepath = self.ui.config('web', 'templates', None)
131 self.templatepath = self.ui.config('web', 'templates', None)
132 self.stripecount = self.ui.config('web', 'stripes', 1)
132 self.stripecount = self.ui.config('web', 'stripes', 1)
133 if self.stripecount:
133 if self.stripecount:
134 self.stripecount = int(self.stripecount)
134 self.stripecount = int(self.stripecount)
135 self._baseurl = self.ui.config('web', 'baseurl')
135 self._baseurl = self.ui.config('web', 'baseurl')
136 self.lastrefresh = time.time()
136 self.lastrefresh = time.time()
137
137
138 def run(self):
138 def run(self):
139 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
139 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
140 raise RuntimeError("This function is only intended to be "
140 raise RuntimeError("This function is only intended to be "
141 "called while running as a CGI script.")
141 "called while running as a CGI script.")
142 import mercurial.hgweb.wsgicgi as wsgicgi
142 import mercurial.hgweb.wsgicgi as wsgicgi
143 wsgicgi.launch(self)
143 wsgicgi.launch(self)
144
144
145 def __call__(self, env, respond):
145 def __call__(self, env, respond):
146 req = wsgirequest(env, respond)
146 req = wsgirequest(env, respond)
147 return self.run_wsgi(req)
147 return self.run_wsgi(req)
148
148
149 def read_allowed(self, ui, req):
149 def read_allowed(self, ui, req):
150 """Check allow_read and deny_read config options of a repo's ui object
150 """Check allow_read and deny_read config options of a repo's ui object
151 to determine user permissions. By default, with neither option set (or
151 to determine user permissions. By default, with neither option set (or
152 both empty), allow all users to read the repo. There are two ways a
152 both empty), allow all users to read the repo. There are two ways a
153 user can be denied read access: (1) deny_read is not empty, and the
153 user can be denied read access: (1) deny_read is not empty, and the
154 user is unauthenticated or deny_read contains user (or *), and (2)
154 user is unauthenticated or deny_read contains user (or *), and (2)
155 allow_read is not empty and the user is not in allow_read. Return True
155 allow_read is not empty and the user is not in allow_read. Return True
156 if user is allowed to read the repo, else return False."""
156 if user is allowed to read the repo, else return False."""
157
157
158 user = req.env.get('REMOTE_USER')
158 user = req.env.get('REMOTE_USER')
159
159
160 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
160 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
161 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
161 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
162 return False
162 return False
163
163
164 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
164 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
165 # by default, allow reading if no allow_read option has been set
165 # by default, allow reading if no allow_read option has been set
166 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
166 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
167 return True
167 return True
168
168
169 return False
169 return False
170
170
171 def run_wsgi(self, req):
171 def run_wsgi(self, req):
172 try:
172 try:
173 try:
173 try:
174 self.refresh()
174 self.refresh()
175
175
176 virtual = req.env.get("PATH_INFO", "").strip('/')
176 virtual = req.env.get("PATH_INFO", "").strip('/')
177 tmpl = self.templater(req)
177 tmpl = self.templater(req)
178 ctype = tmpl('mimetype', encoding=encoding.encoding)
178 ctype = tmpl('mimetype', encoding=encoding.encoding)
179 ctype = templater.stringify(ctype)
179 ctype = templater.stringify(ctype)
180
180
181 # a static file
181 # a static file
182 if virtual.startswith('static/') or 'static' in req.form:
182 if virtual.startswith('static/') or 'static' in req.form:
183 if virtual.startswith('static/'):
183 if virtual.startswith('static/'):
184 fname = virtual[7:]
184 fname = virtual[7:]
185 else:
185 else:
186 fname = req.form['static'][0]
186 fname = req.form['static'][0]
187 static = templater.templatepath('static')
187 static = self.ui.config("web", "static", None,
188 untrusted=False)
189 if not static:
190 tp = self.templatepath or templater.templatepath()
191 if isinstance(tp, str):
192 tp = [tp]
193 static = [os.path.join(p, 'static') for p in tp]
188 return (staticfile(static, fname, req),)
194 return (staticfile(static, fname, req),)
189
195
190 # top-level index
196 # top-level index
191 elif not virtual:
197 elif not virtual:
192 req.respond(HTTP_OK, ctype)
198 req.respond(HTTP_OK, ctype)
193 return self.makeindex(req, tmpl)
199 return self.makeindex(req, tmpl)
194
200
195 # nested indexes and hgwebs
201 # nested indexes and hgwebs
196
202
197 repos = dict(self.repos)
203 repos = dict(self.repos)
198 virtualrepo = virtual
204 virtualrepo = virtual
199 while virtualrepo:
205 while virtualrepo:
200 real = repos.get(virtualrepo)
206 real = repos.get(virtualrepo)
201 if real:
207 if real:
202 req.env['REPO_NAME'] = virtualrepo
208 req.env['REPO_NAME'] = virtualrepo
203 try:
209 try:
204 repo = hg.repository(self.ui, real)
210 repo = hg.repository(self.ui, real)
205 return hgweb(repo).run_wsgi(req)
211 return hgweb(repo).run_wsgi(req)
206 except IOError, inst:
212 except IOError, inst:
207 msg = inst.strerror
213 msg = inst.strerror
208 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
214 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
209 except error.RepoError, inst:
215 except error.RepoError, inst:
210 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
216 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
211
217
212 up = virtualrepo.rfind('/')
218 up = virtualrepo.rfind('/')
213 if up < 0:
219 if up < 0:
214 break
220 break
215 virtualrepo = virtualrepo[:up]
221 virtualrepo = virtualrepo[:up]
216
222
217 # browse subdirectories
223 # browse subdirectories
218 subdir = virtual + '/'
224 subdir = virtual + '/'
219 if [r for r in repos if r.startswith(subdir)]:
225 if [r for r in repos if r.startswith(subdir)]:
220 req.respond(HTTP_OK, ctype)
226 req.respond(HTTP_OK, ctype)
221 return self.makeindex(req, tmpl, subdir)
227 return self.makeindex(req, tmpl, subdir)
222
228
223 # prefixes not found
229 # prefixes not found
224 req.respond(HTTP_NOT_FOUND, ctype)
230 req.respond(HTTP_NOT_FOUND, ctype)
225 return tmpl("notfound", repo=virtual)
231 return tmpl("notfound", repo=virtual)
226
232
227 except ErrorResponse, err:
233 except ErrorResponse, err:
228 req.respond(err, ctype)
234 req.respond(err, ctype)
229 return tmpl('error', error=err.message or '')
235 return tmpl('error', error=err.message or '')
230 finally:
236 finally:
231 tmpl = None
237 tmpl = None
232
238
233 def makeindex(self, req, tmpl, subdir=""):
239 def makeindex(self, req, tmpl, subdir=""):
234
240
235 def archivelist(ui, nodeid, url):
241 def archivelist(ui, nodeid, url):
236 allowed = ui.configlist("web", "allow_archive", untrusted=True)
242 allowed = ui.configlist("web", "allow_archive", untrusted=True)
237 archives = []
243 archives = []
238 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
244 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
239 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
245 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
240 untrusted=True):
246 untrusted=True):
241 archives.append({"type" : i[0], "extension": i[1],
247 archives.append({"type" : i[0], "extension": i[1],
242 "node": nodeid, "url": url})
248 "node": nodeid, "url": url})
243 return archives
249 return archives
244
250
245 def rawentries(subdir="", **map):
251 def rawentries(subdir="", **map):
246
252
247 descend = self.ui.configbool('web', 'descend', True)
253 descend = self.ui.configbool('web', 'descend', True)
248 collapse = self.ui.configbool('web', 'collapse', False)
254 collapse = self.ui.configbool('web', 'collapse', False)
249 seenrepos = set()
255 seenrepos = set()
250 seendirs = set()
256 seendirs = set()
251 for name, path in self.repos:
257 for name, path in self.repos:
252
258
253 if not name.startswith(subdir):
259 if not name.startswith(subdir):
254 continue
260 continue
255 name = name[len(subdir):]
261 name = name[len(subdir):]
256 directory = False
262 directory = False
257
263
258 if '/' in name:
264 if '/' in name:
259 if not descend:
265 if not descend:
260 continue
266 continue
261
267
262 nameparts = name.split('/')
268 nameparts = name.split('/')
263 rootname = nameparts[0]
269 rootname = nameparts[0]
264
270
265 if not collapse:
271 if not collapse:
266 pass
272 pass
267 elif rootname in seendirs:
273 elif rootname in seendirs:
268 continue
274 continue
269 elif rootname in seenrepos:
275 elif rootname in seenrepos:
270 pass
276 pass
271 else:
277 else:
272 directory = True
278 directory = True
273 name = rootname
279 name = rootname
274
280
275 # redefine the path to refer to the directory
281 # redefine the path to refer to the directory
276 discarded = '/'.join(nameparts[1:])
282 discarded = '/'.join(nameparts[1:])
277
283
278 # remove name parts plus accompanying slash
284 # remove name parts plus accompanying slash
279 path = path[:-len(discarded) - 1]
285 path = path[:-len(discarded) - 1]
280
286
281 parts = [name]
287 parts = [name]
282 if 'PATH_INFO' in req.env:
288 if 'PATH_INFO' in req.env:
283 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
289 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
284 if req.env['SCRIPT_NAME']:
290 if req.env['SCRIPT_NAME']:
285 parts.insert(0, req.env['SCRIPT_NAME'])
291 parts.insert(0, req.env['SCRIPT_NAME'])
286 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
292 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
287
293
288 # show either a directory entry or a repository
294 # show either a directory entry or a repository
289 if directory:
295 if directory:
290 # get the directory's time information
296 # get the directory's time information
291 try:
297 try:
292 d = (get_mtime(path), util.makedate()[1])
298 d = (get_mtime(path), util.makedate()[1])
293 except OSError:
299 except OSError:
294 continue
300 continue
295
301
296 # add '/' to the name to make it obvious that
302 # add '/' to the name to make it obvious that
297 # the entry is a directory, not a regular repository
303 # the entry is a directory, not a regular repository
298 row = dict(contact="",
304 row = dict(contact="",
299 contact_sort="",
305 contact_sort="",
300 name=name + '/',
306 name=name + '/',
301 name_sort=name,
307 name_sort=name,
302 url=url,
308 url=url,
303 description="",
309 description="",
304 description_sort="",
310 description_sort="",
305 lastchange=d,
311 lastchange=d,
306 lastchange_sort=d[1]-d[0],
312 lastchange_sort=d[1]-d[0],
307 archives=[])
313 archives=[])
308
314
309 seendirs.add(name)
315 seendirs.add(name)
310 yield row
316 yield row
311 continue
317 continue
312
318
313 u = self.ui.copy()
319 u = self.ui.copy()
314 try:
320 try:
315 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
321 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
316 except Exception, e:
322 except Exception, e:
317 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
323 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
318 continue
324 continue
319 def get(section, name, default=None):
325 def get(section, name, default=None):
320 return u.config(section, name, default, untrusted=True)
326 return u.config(section, name, default, untrusted=True)
321
327
322 if u.configbool("web", "hidden", untrusted=True):
328 if u.configbool("web", "hidden", untrusted=True):
323 continue
329 continue
324
330
325 if not self.read_allowed(u, req):
331 if not self.read_allowed(u, req):
326 continue
332 continue
327
333
328 # update time with local timezone
334 # update time with local timezone
329 try:
335 try:
330 r = hg.repository(self.ui, path)
336 r = hg.repository(self.ui, path)
331 except IOError:
337 except IOError:
332 u.warn(_('error accessing repository at %s\n') % path)
338 u.warn(_('error accessing repository at %s\n') % path)
333 continue
339 continue
334 except error.RepoError:
340 except error.RepoError:
335 u.warn(_('error accessing repository at %s\n') % path)
341 u.warn(_('error accessing repository at %s\n') % path)
336 continue
342 continue
337 try:
343 try:
338 d = (get_mtime(r.spath), util.makedate()[1])
344 d = (get_mtime(r.spath), util.makedate()[1])
339 except OSError:
345 except OSError:
340 continue
346 continue
341
347
342 contact = get_contact(get)
348 contact = get_contact(get)
343 description = get("web", "description", "")
349 description = get("web", "description", "")
344 name = get("web", "name", name)
350 name = get("web", "name", name)
345 row = dict(contact=contact or "unknown",
351 row = dict(contact=contact or "unknown",
346 contact_sort=contact.upper() or "unknown",
352 contact_sort=contact.upper() or "unknown",
347 name=name,
353 name=name,
348 name_sort=name,
354 name_sort=name,
349 url=url,
355 url=url,
350 description=description or "unknown",
356 description=description or "unknown",
351 description_sort=description.upper() or "unknown",
357 description_sort=description.upper() or "unknown",
352 lastchange=d,
358 lastchange=d,
353 lastchange_sort=d[1]-d[0],
359 lastchange_sort=d[1]-d[0],
354 archives=archivelist(u, "tip", url))
360 archives=archivelist(u, "tip", url))
355
361
356 seenrepos.add(name)
362 seenrepos.add(name)
357 yield row
363 yield row
358
364
359 sortdefault = None, False
365 sortdefault = None, False
360 def entries(sortcolumn="", descending=False, subdir="", **map):
366 def entries(sortcolumn="", descending=False, subdir="", **map):
361 rows = rawentries(subdir=subdir, **map)
367 rows = rawentries(subdir=subdir, **map)
362
368
363 if sortcolumn and sortdefault != (sortcolumn, descending):
369 if sortcolumn and sortdefault != (sortcolumn, descending):
364 sortkey = '%s_sort' % sortcolumn
370 sortkey = '%s_sort' % sortcolumn
365 rows = sorted(rows, key=lambda x: x[sortkey],
371 rows = sorted(rows, key=lambda x: x[sortkey],
366 reverse=descending)
372 reverse=descending)
367 for row, parity in zip(rows, paritygen(self.stripecount)):
373 for row, parity in zip(rows, paritygen(self.stripecount)):
368 row['parity'] = parity
374 row['parity'] = parity
369 yield row
375 yield row
370
376
371 self.refresh()
377 self.refresh()
372 sortable = ["name", "description", "contact", "lastchange"]
378 sortable = ["name", "description", "contact", "lastchange"]
373 sortcolumn, descending = sortdefault
379 sortcolumn, descending = sortdefault
374 if 'sort' in req.form:
380 if 'sort' in req.form:
375 sortcolumn = req.form['sort'][0]
381 sortcolumn = req.form['sort'][0]
376 descending = sortcolumn.startswith('-')
382 descending = sortcolumn.startswith('-')
377 if descending:
383 if descending:
378 sortcolumn = sortcolumn[1:]
384 sortcolumn = sortcolumn[1:]
379 if sortcolumn not in sortable:
385 if sortcolumn not in sortable:
380 sortcolumn = ""
386 sortcolumn = ""
381
387
382 sort = [("sort_%s" % column,
388 sort = [("sort_%s" % column,
383 "%s%s" % ((not descending and column == sortcolumn)
389 "%s%s" % ((not descending and column == sortcolumn)
384 and "-" or "", column))
390 and "-" or "", column))
385 for column in sortable]
391 for column in sortable]
386
392
387 self.refresh()
393 self.refresh()
388 self.updatereqenv(req.env)
394 self.updatereqenv(req.env)
389
395
390 return tmpl("index", entries=entries, subdir=subdir,
396 return tmpl("index", entries=entries, subdir=subdir,
391 sortcolumn=sortcolumn, descending=descending,
397 sortcolumn=sortcolumn, descending=descending,
392 **dict(sort))
398 **dict(sort))
393
399
394 def templater(self, req):
400 def templater(self, req):
395
401
396 def header(**map):
402 def header(**map):
397 yield tmpl('header', encoding=encoding.encoding, **map)
403 yield tmpl('header', encoding=encoding.encoding, **map)
398
404
399 def footer(**map):
405 def footer(**map):
400 yield tmpl("footer", **map)
406 yield tmpl("footer", **map)
401
407
402 def motd(**map):
408 def motd(**map):
403 if self.motd is not None:
409 if self.motd is not None:
404 yield self.motd
410 yield self.motd
405 else:
411 else:
406 yield config('web', 'motd', '')
412 yield config('web', 'motd', '')
407
413
408 def config(section, name, default=None, untrusted=True):
414 def config(section, name, default=None, untrusted=True):
409 return self.ui.config(section, name, default, untrusted)
415 return self.ui.config(section, name, default, untrusted)
410
416
411 self.updatereqenv(req.env)
417 self.updatereqenv(req.env)
412
418
413 url = req.env.get('SCRIPT_NAME', '')
419 url = req.env.get('SCRIPT_NAME', '')
414 if not url.endswith('/'):
420 if not url.endswith('/'):
415 url += '/'
421 url += '/'
416
422
417 vars = {}
423 vars = {}
418 styles = (
424 styles = (
419 req.form.get('style', [None])[0],
425 req.form.get('style', [None])[0],
420 config('web', 'style'),
426 config('web', 'style'),
421 'paper'
427 'paper'
422 )
428 )
423 style, mapfile = templater.stylemap(styles, self.templatepath)
429 style, mapfile = templater.stylemap(styles, self.templatepath)
424 if style == styles[0]:
430 if style == styles[0]:
425 vars['style'] = style
431 vars['style'] = style
426
432
427 start = url[-1] == '?' and '&' or '?'
433 start = url[-1] == '?' and '&' or '?'
428 sessionvars = webutil.sessionvars(vars, start)
434 sessionvars = webutil.sessionvars(vars, start)
429 logourl = config('web', 'logourl', 'http://mercurial.selenic.com/')
435 logourl = config('web', 'logourl', 'http://mercurial.selenic.com/')
430 logoimg = config('web', 'logoimg', 'hglogo.png')
436 logoimg = config('web', 'logoimg', 'hglogo.png')
431 staticurl = config('web', 'staticurl') or url + 'static/'
437 staticurl = config('web', 'staticurl') or url + 'static/'
432 if not staticurl.endswith('/'):
438 if not staticurl.endswith('/'):
433 staticurl += '/'
439 staticurl += '/'
434
440
435 tmpl = templater.templater(mapfile,
441 tmpl = templater.templater(mapfile,
436 defaults={"header": header,
442 defaults={"header": header,
437 "footer": footer,
443 "footer": footer,
438 "motd": motd,
444 "motd": motd,
439 "url": url,
445 "url": url,
440 "logourl": logourl,
446 "logourl": logourl,
441 "logoimg": logoimg,
447 "logoimg": logoimg,
442 "staticurl": staticurl,
448 "staticurl": staticurl,
443 "sessionvars": sessionvars})
449 "sessionvars": sessionvars})
444 return tmpl
450 return tmpl
445
451
446 def updatereqenv(self, env):
452 def updatereqenv(self, env):
447 if self._baseurl is not None:
453 if self._baseurl is not None:
448 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
454 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
449 env['SERVER_NAME'] = name
455 env['SERVER_NAME'] = name
450 env['SERVER_PORT'] = port
456 env['SERVER_PORT'] = port
451 env['SCRIPT_NAME'] = path
457 env['SCRIPT_NAME'] = path
General Comments 0
You need to be logged in to leave comments. Login now