##// END OF EJS Templates
hgwebdir: make collapsed folders easier to distinguish from repositories...
Angel Ezquerra -
r17838:d51364b3 default
parent child Browse files
Show More
@@ -1,449 +1,451 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 = templater.templatepath('static')
188 return (staticfile(static, fname, req),)
188 return (staticfile(static, fname, req),)
189
189
190 # top-level index
190 # top-level index
191 elif not virtual:
191 elif not virtual:
192 req.respond(HTTP_OK, ctype)
192 req.respond(HTTP_OK, ctype)
193 return self.makeindex(req, tmpl)
193 return self.makeindex(req, tmpl)
194
194
195 # nested indexes and hgwebs
195 # nested indexes and hgwebs
196
196
197 repos = dict(self.repos)
197 repos = dict(self.repos)
198 virtualrepo = virtual
198 virtualrepo = virtual
199 while virtualrepo:
199 while virtualrepo:
200 real = repos.get(virtualrepo)
200 real = repos.get(virtualrepo)
201 if real:
201 if real:
202 req.env['REPO_NAME'] = virtualrepo
202 req.env['REPO_NAME'] = virtualrepo
203 try:
203 try:
204 repo = hg.repository(self.ui, real)
204 repo = hg.repository(self.ui, real)
205 return hgweb(repo).run_wsgi(req)
205 return hgweb(repo).run_wsgi(req)
206 except IOError, inst:
206 except IOError, inst:
207 msg = inst.strerror
207 msg = inst.strerror
208 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
208 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
209 except error.RepoError, inst:
209 except error.RepoError, inst:
210 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
210 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
211
211
212 up = virtualrepo.rfind('/')
212 up = virtualrepo.rfind('/')
213 if up < 0:
213 if up < 0:
214 break
214 break
215 virtualrepo = virtualrepo[:up]
215 virtualrepo = virtualrepo[:up]
216
216
217 # browse subdirectories
217 # browse subdirectories
218 subdir = virtual + '/'
218 subdir = virtual + '/'
219 if [r for r in repos if r.startswith(subdir)]:
219 if [r for r in repos if r.startswith(subdir)]:
220 req.respond(HTTP_OK, ctype)
220 req.respond(HTTP_OK, ctype)
221 return self.makeindex(req, tmpl, subdir)
221 return self.makeindex(req, tmpl, subdir)
222
222
223 # prefixes not found
223 # prefixes not found
224 req.respond(HTTP_NOT_FOUND, ctype)
224 req.respond(HTTP_NOT_FOUND, ctype)
225 return tmpl("notfound", repo=virtual)
225 return tmpl("notfound", repo=virtual)
226
226
227 except ErrorResponse, err:
227 except ErrorResponse, err:
228 req.respond(err, ctype)
228 req.respond(err, ctype)
229 return tmpl('error', error=err.message or '')
229 return tmpl('error', error=err.message or '')
230 finally:
230 finally:
231 tmpl = None
231 tmpl = None
232
232
233 def makeindex(self, req, tmpl, subdir=""):
233 def makeindex(self, req, tmpl, subdir=""):
234
234
235 def archivelist(ui, nodeid, url):
235 def archivelist(ui, nodeid, url):
236 allowed = ui.configlist("web", "allow_archive", untrusted=True)
236 allowed = ui.configlist("web", "allow_archive", untrusted=True)
237 archives = []
237 archives = []
238 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
238 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
239 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
239 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
240 untrusted=True):
240 untrusted=True):
241 archives.append({"type" : i[0], "extension": i[1],
241 archives.append({"type" : i[0], "extension": i[1],
242 "node": nodeid, "url": url})
242 "node": nodeid, "url": url})
243 return archives
243 return archives
244
244
245 def rawentries(subdir="", **map):
245 def rawentries(subdir="", **map):
246
246
247 descend = self.ui.configbool('web', 'descend', True)
247 descend = self.ui.configbool('web', 'descend', True)
248 collapse = self.ui.configbool('web', 'collapse', False)
248 collapse = self.ui.configbool('web', 'collapse', False)
249 seenrepos = set()
249 seenrepos = set()
250 seendirs = set()
250 seendirs = set()
251 for name, path in self.repos:
251 for name, path in self.repos:
252
252
253 if not name.startswith(subdir):
253 if not name.startswith(subdir):
254 continue
254 continue
255 name = name[len(subdir):]
255 name = name[len(subdir):]
256 directory = False
256 directory = False
257
257
258 if '/' in name:
258 if '/' in name:
259 if not descend:
259 if not descend:
260 continue
260 continue
261
261
262 nameparts = name.split('/')
262 nameparts = name.split('/')
263 rootname = nameparts[0]
263 rootname = nameparts[0]
264
264
265 if not collapse:
265 if not collapse:
266 pass
266 pass
267 elif rootname in seendirs:
267 elif rootname in seendirs:
268 continue
268 continue
269 elif rootname in seenrepos:
269 elif rootname in seenrepos:
270 pass
270 pass
271 else:
271 else:
272 directory = True
272 directory = True
273 name = rootname
273 name = rootname
274
274
275 # redefine the path to refer to the directory
275 # redefine the path to refer to the directory
276 discarded = '/'.join(nameparts[1:])
276 discarded = '/'.join(nameparts[1:])
277
277
278 # remove name parts plus accompanying slash
278 # remove name parts plus accompanying slash
279 path = path[:-len(discarded) - 1]
279 path = path[:-len(discarded) - 1]
280
280
281 parts = [name]
281 parts = [name]
282 if 'PATH_INFO' in req.env:
282 if 'PATH_INFO' in req.env:
283 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
283 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
284 if req.env['SCRIPT_NAME']:
284 if req.env['SCRIPT_NAME']:
285 parts.insert(0, req.env['SCRIPT_NAME'])
285 parts.insert(0, req.env['SCRIPT_NAME'])
286 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
286 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
287
287
288 # show either a directory entry or a repository
288 # show either a directory entry or a repository
289 if directory:
289 if directory:
290 # get the directory's time information
290 # get the directory's time information
291 try:
291 try:
292 d = (get_mtime(path), util.makedate()[1])
292 d = (get_mtime(path), util.makedate()[1])
293 except OSError:
293 except OSError:
294 continue
294 continue
295
295
296 # add '/' to the name to make it obvious that
297 # the entry is a directory, not a regular repository
296 row = dict(contact="",
298 row = dict(contact="",
297 contact_sort="",
299 contact_sort="",
298 name=name,
300 name=name + '/',
299 name_sort=name,
301 name_sort=name,
300 url=url,
302 url=url,
301 description="",
303 description="",
302 description_sort="",
304 description_sort="",
303 lastchange=d,
305 lastchange=d,
304 lastchange_sort=d[1]-d[0],
306 lastchange_sort=d[1]-d[0],
305 archives=[])
307 archives=[])
306
308
307 seendirs.add(name)
309 seendirs.add(name)
308 yield row
310 yield row
309 continue
311 continue
310
312
311 u = self.ui.copy()
313 u = self.ui.copy()
312 try:
314 try:
313 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
315 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
314 except Exception, e:
316 except Exception, e:
315 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
317 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
316 continue
318 continue
317 def get(section, name, default=None):
319 def get(section, name, default=None):
318 return u.config(section, name, default, untrusted=True)
320 return u.config(section, name, default, untrusted=True)
319
321
320 if u.configbool("web", "hidden", untrusted=True):
322 if u.configbool("web", "hidden", untrusted=True):
321 continue
323 continue
322
324
323 if not self.read_allowed(u, req):
325 if not self.read_allowed(u, req):
324 continue
326 continue
325
327
326 # update time with local timezone
328 # update time with local timezone
327 try:
329 try:
328 r = hg.repository(self.ui, path)
330 r = hg.repository(self.ui, path)
329 except IOError:
331 except IOError:
330 u.warn(_('error accessing repository at %s\n') % path)
332 u.warn(_('error accessing repository at %s\n') % path)
331 continue
333 continue
332 except error.RepoError:
334 except error.RepoError:
333 u.warn(_('error accessing repository at %s\n') % path)
335 u.warn(_('error accessing repository at %s\n') % path)
334 continue
336 continue
335 try:
337 try:
336 d = (get_mtime(r.spath), util.makedate()[1])
338 d = (get_mtime(r.spath), util.makedate()[1])
337 except OSError:
339 except OSError:
338 continue
340 continue
339
341
340 contact = get_contact(get)
342 contact = get_contact(get)
341 description = get("web", "description", "")
343 description = get("web", "description", "")
342 name = get("web", "name", name)
344 name = get("web", "name", name)
343 row = dict(contact=contact or "unknown",
345 row = dict(contact=contact or "unknown",
344 contact_sort=contact.upper() or "unknown",
346 contact_sort=contact.upper() or "unknown",
345 name=name,
347 name=name,
346 name_sort=name,
348 name_sort=name,
347 url=url,
349 url=url,
348 description=description or "unknown",
350 description=description or "unknown",
349 description_sort=description.upper() or "unknown",
351 description_sort=description.upper() or "unknown",
350 lastchange=d,
352 lastchange=d,
351 lastchange_sort=d[1]-d[0],
353 lastchange_sort=d[1]-d[0],
352 archives=archivelist(u, "tip", url))
354 archives=archivelist(u, "tip", url))
353
355
354 seenrepos.add(name)
356 seenrepos.add(name)
355 yield row
357 yield row
356
358
357 sortdefault = None, False
359 sortdefault = None, False
358 def entries(sortcolumn="", descending=False, subdir="", **map):
360 def entries(sortcolumn="", descending=False, subdir="", **map):
359 rows = rawentries(subdir=subdir, **map)
361 rows = rawentries(subdir=subdir, **map)
360
362
361 if sortcolumn and sortdefault != (sortcolumn, descending):
363 if sortcolumn and sortdefault != (sortcolumn, descending):
362 sortkey = '%s_sort' % sortcolumn
364 sortkey = '%s_sort' % sortcolumn
363 rows = sorted(rows, key=lambda x: x[sortkey],
365 rows = sorted(rows, key=lambda x: x[sortkey],
364 reverse=descending)
366 reverse=descending)
365 for row, parity in zip(rows, paritygen(self.stripecount)):
367 for row, parity in zip(rows, paritygen(self.stripecount)):
366 row['parity'] = parity
368 row['parity'] = parity
367 yield row
369 yield row
368
370
369 self.refresh()
371 self.refresh()
370 sortable = ["name", "description", "contact", "lastchange"]
372 sortable = ["name", "description", "contact", "lastchange"]
371 sortcolumn, descending = sortdefault
373 sortcolumn, descending = sortdefault
372 if 'sort' in req.form:
374 if 'sort' in req.form:
373 sortcolumn = req.form['sort'][0]
375 sortcolumn = req.form['sort'][0]
374 descending = sortcolumn.startswith('-')
376 descending = sortcolumn.startswith('-')
375 if descending:
377 if descending:
376 sortcolumn = sortcolumn[1:]
378 sortcolumn = sortcolumn[1:]
377 if sortcolumn not in sortable:
379 if sortcolumn not in sortable:
378 sortcolumn = ""
380 sortcolumn = ""
379
381
380 sort = [("sort_%s" % column,
382 sort = [("sort_%s" % column,
381 "%s%s" % ((not descending and column == sortcolumn)
383 "%s%s" % ((not descending and column == sortcolumn)
382 and "-" or "", column))
384 and "-" or "", column))
383 for column in sortable]
385 for column in sortable]
384
386
385 self.refresh()
387 self.refresh()
386 self.updatereqenv(req.env)
388 self.updatereqenv(req.env)
387
389
388 return tmpl("index", entries=entries, subdir=subdir,
390 return tmpl("index", entries=entries, subdir=subdir,
389 sortcolumn=sortcolumn, descending=descending,
391 sortcolumn=sortcolumn, descending=descending,
390 **dict(sort))
392 **dict(sort))
391
393
392 def templater(self, req):
394 def templater(self, req):
393
395
394 def header(**map):
396 def header(**map):
395 yield tmpl('header', encoding=encoding.encoding, **map)
397 yield tmpl('header', encoding=encoding.encoding, **map)
396
398
397 def footer(**map):
399 def footer(**map):
398 yield tmpl("footer", **map)
400 yield tmpl("footer", **map)
399
401
400 def motd(**map):
402 def motd(**map):
401 if self.motd is not None:
403 if self.motd is not None:
402 yield self.motd
404 yield self.motd
403 else:
405 else:
404 yield config('web', 'motd', '')
406 yield config('web', 'motd', '')
405
407
406 def config(section, name, default=None, untrusted=True):
408 def config(section, name, default=None, untrusted=True):
407 return self.ui.config(section, name, default, untrusted)
409 return self.ui.config(section, name, default, untrusted)
408
410
409 self.updatereqenv(req.env)
411 self.updatereqenv(req.env)
410
412
411 url = req.env.get('SCRIPT_NAME', '')
413 url = req.env.get('SCRIPT_NAME', '')
412 if not url.endswith('/'):
414 if not url.endswith('/'):
413 url += '/'
415 url += '/'
414
416
415 vars = {}
417 vars = {}
416 styles = (
418 styles = (
417 req.form.get('style', [None])[0],
419 req.form.get('style', [None])[0],
418 config('web', 'style'),
420 config('web', 'style'),
419 'paper'
421 'paper'
420 )
422 )
421 style, mapfile = templater.stylemap(styles, self.templatepath)
423 style, mapfile = templater.stylemap(styles, self.templatepath)
422 if style == styles[0]:
424 if style == styles[0]:
423 vars['style'] = style
425 vars['style'] = style
424
426
425 start = url[-1] == '?' and '&' or '?'
427 start = url[-1] == '?' and '&' or '?'
426 sessionvars = webutil.sessionvars(vars, start)
428 sessionvars = webutil.sessionvars(vars, start)
427 logourl = config('web', 'logourl', 'http://mercurial.selenic.com/')
429 logourl = config('web', 'logourl', 'http://mercurial.selenic.com/')
428 logoimg = config('web', 'logoimg', 'hglogo.png')
430 logoimg = config('web', 'logoimg', 'hglogo.png')
429 staticurl = config('web', 'staticurl') or url + 'static/'
431 staticurl = config('web', 'staticurl') or url + 'static/'
430 if not staticurl.endswith('/'):
432 if not staticurl.endswith('/'):
431 staticurl += '/'
433 staticurl += '/'
432
434
433 tmpl = templater.templater(mapfile,
435 tmpl = templater.templater(mapfile,
434 defaults={"header": header,
436 defaults={"header": header,
435 "footer": footer,
437 "footer": footer,
436 "motd": motd,
438 "motd": motd,
437 "url": url,
439 "url": url,
438 "logourl": logourl,
440 "logourl": logourl,
439 "logoimg": logoimg,
441 "logoimg": logoimg,
440 "staticurl": staticurl,
442 "staticurl": staticurl,
441 "sessionvars": sessionvars})
443 "sessionvars": sessionvars})
442 return tmpl
444 return tmpl
443
445
444 def updatereqenv(self, env):
446 def updatereqenv(self, env):
445 if self._baseurl is not None:
447 if self._baseurl is not None:
446 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
448 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
447 env['SERVER_NAME'] = name
449 env['SERVER_NAME'] = name
448 env['SERVER_PORT'] = port
450 env['SERVER_PORT'] = port
449 env['SCRIPT_NAME'] = path
451 env['SCRIPT_NAME'] = path
General Comments 0
You need to be logged in to leave comments. Login now