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