##// END OF EJS Templates
hgweb: use try/except/finally
Matt Mackall -
r25083:ef36536a default
parent child Browse files
Show More
@@ -1,464 +1,463 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, ismember, \
13 from common import ErrorResponse, get_mtime, staticfile, paritygen, ismember, \
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, makebreadcrumb
15 from hgweb_mod import hgweb, makebreadcrumb
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', 'hgwebdir')
99 u.setconfig('ui', 'report_untrusted', 'off', 'hgwebdir')
100 u.setconfig('ui', 'nontty', 'true', 'hgwebdir')
100 u.setconfig('ui', 'nontty', 'true', 'hgwebdir')
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 prefix = self.ui.config('web', 'prefix', '')
136 prefix = self.ui.config('web', 'prefix', '')
137 if prefix.startswith('/'):
137 if prefix.startswith('/'):
138 prefix = prefix[1:]
138 prefix = prefix[1:]
139 if prefix.endswith('/'):
139 if prefix.endswith('/'):
140 prefix = prefix[:-1]
140 prefix = prefix[:-1]
141 self.prefix = prefix
141 self.prefix = prefix
142 self.lastrefresh = time.time()
142 self.lastrefresh = time.time()
143
143
144 def run(self):
144 def run(self):
145 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
145 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
146 raise RuntimeError("This function is only intended to be "
146 raise RuntimeError("This function is only intended to be "
147 "called while running as a CGI script.")
147 "called while running as a CGI script.")
148 import mercurial.hgweb.wsgicgi as wsgicgi
148 import mercurial.hgweb.wsgicgi as wsgicgi
149 wsgicgi.launch(self)
149 wsgicgi.launch(self)
150
150
151 def __call__(self, env, respond):
151 def __call__(self, env, respond):
152 req = wsgirequest(env, respond)
152 req = wsgirequest(env, respond)
153 return self.run_wsgi(req)
153 return self.run_wsgi(req)
154
154
155 def read_allowed(self, ui, req):
155 def read_allowed(self, ui, req):
156 """Check allow_read and deny_read config options of a repo's ui object
156 """Check allow_read and deny_read config options of a repo's ui object
157 to determine user permissions. By default, with neither option set (or
157 to determine user permissions. By default, with neither option set (or
158 both empty), allow all users to read the repo. There are two ways a
158 both empty), allow all users to read the repo. There are two ways a
159 user can be denied read access: (1) deny_read is not empty, and the
159 user can be denied read access: (1) deny_read is not empty, and the
160 user is unauthenticated or deny_read contains user (or *), and (2)
160 user is unauthenticated or deny_read contains user (or *), and (2)
161 allow_read is not empty and the user is not in allow_read. Return True
161 allow_read is not empty and the user is not in allow_read. Return True
162 if user is allowed to read the repo, else return False."""
162 if user is allowed to read the repo, else return False."""
163
163
164 user = req.env.get('REMOTE_USER')
164 user = req.env.get('REMOTE_USER')
165
165
166 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
166 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
167 if deny_read and (not user or ismember(ui, user, deny_read)):
167 if deny_read and (not user or ismember(ui, user, deny_read)):
168 return False
168 return False
169
169
170 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
170 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
171 # by default, allow reading if no allow_read option has been set
171 # by default, allow reading if no allow_read option has been set
172 if (not allow_read) or ismember(ui, user, allow_read):
172 if (not allow_read) or ismember(ui, user, allow_read):
173 return True
173 return True
174
174
175 return False
175 return False
176
176
177 def run_wsgi(self, req):
177 def run_wsgi(self, req):
178 try:
178 try:
179 try:
179 self.refresh()
180 self.refresh()
181
180
182 virtual = req.env.get("PATH_INFO", "").strip('/')
181 virtual = req.env.get("PATH_INFO", "").strip('/')
183 tmpl = self.templater(req)
182 tmpl = self.templater(req)
184 ctype = tmpl('mimetype', encoding=encoding.encoding)
183 ctype = tmpl('mimetype', encoding=encoding.encoding)
185 ctype = templater.stringify(ctype)
184 ctype = templater.stringify(ctype)
186
185
187 # a static file
186 # a static file
188 if virtual.startswith('static/') or 'static' in req.form:
187 if virtual.startswith('static/') or 'static' in req.form:
189 if virtual.startswith('static/'):
188 if virtual.startswith('static/'):
190 fname = virtual[7:]
189 fname = virtual[7:]
191 else:
190 else:
192 fname = req.form['static'][0]
191 fname = req.form['static'][0]
193 static = self.ui.config("web", "static", None,
192 static = self.ui.config("web", "static", None,
194 untrusted=False)
193 untrusted=False)
195 if not static:
194 if not static:
196 tp = self.templatepath or templater.templatepaths()
195 tp = self.templatepath or templater.templatepaths()
197 if isinstance(tp, str):
196 if isinstance(tp, str):
198 tp = [tp]
197 tp = [tp]
199 static = [os.path.join(p, 'static') for p in tp]
198 static = [os.path.join(p, 'static') for p in tp]
200 staticfile(static, fname, req)
199 staticfile(static, fname, req)
201 return []
200 return []
202
201
203 # top-level index
202 # top-level index
204 elif not virtual:
203 elif not virtual:
205 req.respond(HTTP_OK, ctype)
204 req.respond(HTTP_OK, ctype)
206 return self.makeindex(req, tmpl)
205 return self.makeindex(req, tmpl)
207
206
208 # nested indexes and hgwebs
207 # nested indexes and hgwebs
209
208
210 repos = dict(self.repos)
209 repos = dict(self.repos)
211 virtualrepo = virtual
210 virtualrepo = virtual
212 while virtualrepo:
211 while virtualrepo:
213 real = repos.get(virtualrepo)
212 real = repos.get(virtualrepo)
214 if real:
213 if real:
215 req.env['REPO_NAME'] = virtualrepo
214 req.env['REPO_NAME'] = virtualrepo
216 try:
215 try:
217 # ensure caller gets private copy of ui
216 # ensure caller gets private copy of ui
218 repo = hg.repository(self.ui.copy(), real)
217 repo = hg.repository(self.ui.copy(), real)
219 return hgweb(repo).run_wsgi(req)
218 return hgweb(repo).run_wsgi(req)
220 except IOError, inst:
219 except IOError, inst:
221 msg = inst.strerror
220 msg = inst.strerror
222 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
221 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
223 except error.RepoError, inst:
222 except error.RepoError, inst:
224 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
223 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
225
224
226 up = virtualrepo.rfind('/')
225 up = virtualrepo.rfind('/')
227 if up < 0:
226 if up < 0:
228 break
227 break
229 virtualrepo = virtualrepo[:up]
228 virtualrepo = virtualrepo[:up]
230
229
231 # browse subdirectories
230 # browse subdirectories
232 subdir = virtual + '/'
231 subdir = virtual + '/'
233 if [r for r in repos if r.startswith(subdir)]:
232 if [r for r in repos if r.startswith(subdir)]:
234 req.respond(HTTP_OK, ctype)
233 req.respond(HTTP_OK, ctype)
235 return self.makeindex(req, tmpl, subdir)
234 return self.makeindex(req, tmpl, subdir)
236
235
237 # prefixes not found
236 # prefixes not found
238 req.respond(HTTP_NOT_FOUND, ctype)
237 req.respond(HTTP_NOT_FOUND, ctype)
239 return tmpl("notfound", repo=virtual)
238 return tmpl("notfound", repo=virtual)
240
239
241 except ErrorResponse, err:
240 except ErrorResponse, err:
242 req.respond(err, ctype)
241 req.respond(err, ctype)
243 return tmpl('error', error=err.message or '')
242 return tmpl('error', error=err.message or '')
244 finally:
243 finally:
245 tmpl = None
244 tmpl = None
246
245
247 def makeindex(self, req, tmpl, subdir=""):
246 def makeindex(self, req, tmpl, subdir=""):
248
247
249 def archivelist(ui, nodeid, url):
248 def archivelist(ui, nodeid, url):
250 allowed = ui.configlist("web", "allow_archive", untrusted=True)
249 allowed = ui.configlist("web", "allow_archive", untrusted=True)
251 archives = []
250 archives = []
252 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
251 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
253 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
252 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
254 untrusted=True):
253 untrusted=True):
255 archives.append({"type" : i[0], "extension": i[1],
254 archives.append({"type" : i[0], "extension": i[1],
256 "node": nodeid, "url": url})
255 "node": nodeid, "url": url})
257 return archives
256 return archives
258
257
259 def rawentries(subdir="", **map):
258 def rawentries(subdir="", **map):
260
259
261 descend = self.ui.configbool('web', 'descend', True)
260 descend = self.ui.configbool('web', 'descend', True)
262 collapse = self.ui.configbool('web', 'collapse', False)
261 collapse = self.ui.configbool('web', 'collapse', False)
263 seenrepos = set()
262 seenrepos = set()
264 seendirs = set()
263 seendirs = set()
265 for name, path in self.repos:
264 for name, path in self.repos:
266
265
267 if not name.startswith(subdir):
266 if not name.startswith(subdir):
268 continue
267 continue
269 name = name[len(subdir):]
268 name = name[len(subdir):]
270 directory = False
269 directory = False
271
270
272 if '/' in name:
271 if '/' in name:
273 if not descend:
272 if not descend:
274 continue
273 continue
275
274
276 nameparts = name.split('/')
275 nameparts = name.split('/')
277 rootname = nameparts[0]
276 rootname = nameparts[0]
278
277
279 if not collapse:
278 if not collapse:
280 pass
279 pass
281 elif rootname in seendirs:
280 elif rootname in seendirs:
282 continue
281 continue
283 elif rootname in seenrepos:
282 elif rootname in seenrepos:
284 pass
283 pass
285 else:
284 else:
286 directory = True
285 directory = True
287 name = rootname
286 name = rootname
288
287
289 # redefine the path to refer to the directory
288 # redefine the path to refer to the directory
290 discarded = '/'.join(nameparts[1:])
289 discarded = '/'.join(nameparts[1:])
291
290
292 # remove name parts plus accompanying slash
291 # remove name parts plus accompanying slash
293 path = path[:-len(discarded) - 1]
292 path = path[:-len(discarded) - 1]
294
293
295 parts = [name]
294 parts = [name]
296 if 'PATH_INFO' in req.env:
295 if 'PATH_INFO' in req.env:
297 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
296 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
298 if req.env['SCRIPT_NAME']:
297 if req.env['SCRIPT_NAME']:
299 parts.insert(0, req.env['SCRIPT_NAME'])
298 parts.insert(0, req.env['SCRIPT_NAME'])
300 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
299 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
301
300
302 # show either a directory entry or a repository
301 # show either a directory entry or a repository
303 if directory:
302 if directory:
304 # get the directory's time information
303 # get the directory's time information
305 try:
304 try:
306 d = (get_mtime(path), util.makedate()[1])
305 d = (get_mtime(path), util.makedate()[1])
307 except OSError:
306 except OSError:
308 continue
307 continue
309
308
310 # add '/' to the name to make it obvious that
309 # add '/' to the name to make it obvious that
311 # the entry is a directory, not a regular repository
310 # the entry is a directory, not a regular repository
312 row = {'contact': "",
311 row = {'contact': "",
313 'contact_sort': "",
312 'contact_sort': "",
314 'name': name + '/',
313 'name': name + '/',
315 'name_sort': name,
314 'name_sort': name,
316 'url': url,
315 'url': url,
317 'description': "",
316 'description': "",
318 'description_sort': "",
317 'description_sort': "",
319 'lastchange': d,
318 'lastchange': d,
320 'lastchange_sort': d[1]-d[0],
319 'lastchange_sort': d[1]-d[0],
321 'archives': [],
320 'archives': [],
322 'isdirectory': True}
321 'isdirectory': True}
323
322
324 seendirs.add(name)
323 seendirs.add(name)
325 yield row
324 yield row
326 continue
325 continue
327
326
328 u = self.ui.copy()
327 u = self.ui.copy()
329 try:
328 try:
330 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
329 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
331 except Exception, e:
330 except Exception, e:
332 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
331 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
333 continue
332 continue
334 def get(section, name, default=None):
333 def get(section, name, default=None):
335 return u.config(section, name, default, untrusted=True)
334 return u.config(section, name, default, untrusted=True)
336
335
337 if u.configbool("web", "hidden", untrusted=True):
336 if u.configbool("web", "hidden", untrusted=True):
338 continue
337 continue
339
338
340 if not self.read_allowed(u, req):
339 if not self.read_allowed(u, req):
341 continue
340 continue
342
341
343 # update time with local timezone
342 # update time with local timezone
344 try:
343 try:
345 r = hg.repository(self.ui, path)
344 r = hg.repository(self.ui, path)
346 except IOError:
345 except IOError:
347 u.warn(_('error accessing repository at %s\n') % path)
346 u.warn(_('error accessing repository at %s\n') % path)
348 continue
347 continue
349 except error.RepoError:
348 except error.RepoError:
350 u.warn(_('error accessing repository at %s\n') % path)
349 u.warn(_('error accessing repository at %s\n') % path)
351 continue
350 continue
352 try:
351 try:
353 d = (get_mtime(r.spath), util.makedate()[1])
352 d = (get_mtime(r.spath), util.makedate()[1])
354 except OSError:
353 except OSError:
355 continue
354 continue
356
355
357 contact = get_contact(get)
356 contact = get_contact(get)
358 description = get("web", "description", "")
357 description = get("web", "description", "")
359 name = get("web", "name", name)
358 name = get("web", "name", name)
360 row = {'contact': contact or "unknown",
359 row = {'contact': contact or "unknown",
361 'contact_sort': contact.upper() or "unknown",
360 'contact_sort': contact.upper() or "unknown",
362 'name': name,
361 'name': name,
363 'name_sort': name,
362 'name_sort': name,
364 'url': url,
363 'url': url,
365 'description': description or "unknown",
364 'description': description or "unknown",
366 'description_sort': description.upper() or "unknown",
365 'description_sort': description.upper() or "unknown",
367 'lastchange': d,
366 'lastchange': d,
368 'lastchange_sort': d[1]-d[0],
367 'lastchange_sort': d[1]-d[0],
369 'archives': archivelist(u, "tip", url),
368 'archives': archivelist(u, "tip", url),
370 'isdirectory': None,
369 'isdirectory': None,
371 }
370 }
372
371
373 seenrepos.add(name)
372 seenrepos.add(name)
374 yield row
373 yield row
375
374
376 sortdefault = None, False
375 sortdefault = None, False
377 def entries(sortcolumn="", descending=False, subdir="", **map):
376 def entries(sortcolumn="", descending=False, subdir="", **map):
378 rows = rawentries(subdir=subdir, **map)
377 rows = rawentries(subdir=subdir, **map)
379
378
380 if sortcolumn and sortdefault != (sortcolumn, descending):
379 if sortcolumn and sortdefault != (sortcolumn, descending):
381 sortkey = '%s_sort' % sortcolumn
380 sortkey = '%s_sort' % sortcolumn
382 rows = sorted(rows, key=lambda x: x[sortkey],
381 rows = sorted(rows, key=lambda x: x[sortkey],
383 reverse=descending)
382 reverse=descending)
384 for row, parity in zip(rows, paritygen(self.stripecount)):
383 for row, parity in zip(rows, paritygen(self.stripecount)):
385 row['parity'] = parity
384 row['parity'] = parity
386 yield row
385 yield row
387
386
388 self.refresh()
387 self.refresh()
389 sortable = ["name", "description", "contact", "lastchange"]
388 sortable = ["name", "description", "contact", "lastchange"]
390 sortcolumn, descending = sortdefault
389 sortcolumn, descending = sortdefault
391 if 'sort' in req.form:
390 if 'sort' in req.form:
392 sortcolumn = req.form['sort'][0]
391 sortcolumn = req.form['sort'][0]
393 descending = sortcolumn.startswith('-')
392 descending = sortcolumn.startswith('-')
394 if descending:
393 if descending:
395 sortcolumn = sortcolumn[1:]
394 sortcolumn = sortcolumn[1:]
396 if sortcolumn not in sortable:
395 if sortcolumn not in sortable:
397 sortcolumn = ""
396 sortcolumn = ""
398
397
399 sort = [("sort_%s" % column,
398 sort = [("sort_%s" % column,
400 "%s%s" % ((not descending and column == sortcolumn)
399 "%s%s" % ((not descending and column == sortcolumn)
401 and "-" or "", column))
400 and "-" or "", column))
402 for column in sortable]
401 for column in sortable]
403
402
404 self.refresh()
403 self.refresh()
405 self.updatereqenv(req.env)
404 self.updatereqenv(req.env)
406
405
407 return tmpl("index", entries=entries, subdir=subdir,
406 return tmpl("index", entries=entries, subdir=subdir,
408 pathdef=makebreadcrumb('/' + subdir, self.prefix),
407 pathdef=makebreadcrumb('/' + subdir, self.prefix),
409 sortcolumn=sortcolumn, descending=descending,
408 sortcolumn=sortcolumn, descending=descending,
410 **dict(sort))
409 **dict(sort))
411
410
412 def templater(self, req):
411 def templater(self, req):
413
412
414 def motd(**map):
413 def motd(**map):
415 if self.motd is not None:
414 if self.motd is not None:
416 yield self.motd
415 yield self.motd
417 else:
416 else:
418 yield config('web', 'motd', '')
417 yield config('web', 'motd', '')
419
418
420 def config(section, name, default=None, untrusted=True):
419 def config(section, name, default=None, untrusted=True):
421 return self.ui.config(section, name, default, untrusted)
420 return self.ui.config(section, name, default, untrusted)
422
421
423 self.updatereqenv(req.env)
422 self.updatereqenv(req.env)
424
423
425 url = req.env.get('SCRIPT_NAME', '')
424 url = req.env.get('SCRIPT_NAME', '')
426 if not url.endswith('/'):
425 if not url.endswith('/'):
427 url += '/'
426 url += '/'
428
427
429 vars = {}
428 vars = {}
430 styles = (
429 styles = (
431 req.form.get('style', [None])[0],
430 req.form.get('style', [None])[0],
432 config('web', 'style'),
431 config('web', 'style'),
433 'paper'
432 'paper'
434 )
433 )
435 style, mapfile = templater.stylemap(styles, self.templatepath)
434 style, mapfile = templater.stylemap(styles, self.templatepath)
436 if style == styles[0]:
435 if style == styles[0]:
437 vars['style'] = style
436 vars['style'] = style
438
437
439 start = url[-1] == '?' and '&' or '?'
438 start = url[-1] == '?' and '&' or '?'
440 sessionvars = webutil.sessionvars(vars, start)
439 sessionvars = webutil.sessionvars(vars, start)
441 logourl = config('web', 'logourl', 'http://mercurial.selenic.com/')
440 logourl = config('web', 'logourl', 'http://mercurial.selenic.com/')
442 logoimg = config('web', 'logoimg', 'hglogo.png')
441 logoimg = config('web', 'logoimg', 'hglogo.png')
443 staticurl = config('web', 'staticurl') or url + 'static/'
442 staticurl = config('web', 'staticurl') or url + 'static/'
444 if not staticurl.endswith('/'):
443 if not staticurl.endswith('/'):
445 staticurl += '/'
444 staticurl += '/'
446
445
447 tmpl = templater.templater(mapfile,
446 tmpl = templater.templater(mapfile,
448 defaults={"encoding": encoding.encoding,
447 defaults={"encoding": encoding.encoding,
449 "motd": motd,
448 "motd": motd,
450 "url": url,
449 "url": url,
451 "logourl": logourl,
450 "logourl": logourl,
452 "logoimg": logoimg,
451 "logoimg": logoimg,
453 "staticurl": staticurl,
452 "staticurl": staticurl,
454 "sessionvars": sessionvars,
453 "sessionvars": sessionvars,
455 "style": style,
454 "style": style,
456 })
455 })
457 return tmpl
456 return tmpl
458
457
459 def updatereqenv(self, env):
458 def updatereqenv(self, env):
460 if self._baseurl is not None:
459 if self._baseurl is not None:
461 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
460 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
462 env['SERVER_NAME'] = name
461 env['SERVER_NAME'] = name
463 env['SERVER_PORT'] = port
462 env['SERVER_PORT'] = port
464 env['SCRIPT_NAME'] = path
463 env['SCRIPT_NAME'] = path
General Comments 0
You need to be logged in to leave comments. Login now