##// END OF EJS Templates
hgweb: fix a bug due to variable name typo...
Gregory Szorc -
r36904:97e1dda9 default
parent child Browse files
Show More
@@ -1,553 +1,553 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 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import os
11 import os
12 import re
12 import re
13 import time
13 import time
14
14
15 from ..i18n import _
15 from ..i18n import _
16
16
17 from .common import (
17 from .common import (
18 ErrorResponse,
18 ErrorResponse,
19 HTTP_NOT_FOUND,
19 HTTP_NOT_FOUND,
20 HTTP_OK,
20 HTTP_OK,
21 HTTP_SERVER_ERROR,
21 HTTP_SERVER_ERROR,
22 cspvalues,
22 cspvalues,
23 get_contact,
23 get_contact,
24 get_mtime,
24 get_mtime,
25 ismember,
25 ismember,
26 paritygen,
26 paritygen,
27 staticfile,
27 staticfile,
28 )
28 )
29
29
30 from .. import (
30 from .. import (
31 configitems,
31 configitems,
32 encoding,
32 encoding,
33 error,
33 error,
34 hg,
34 hg,
35 profiling,
35 profiling,
36 pycompat,
36 pycompat,
37 scmutil,
37 scmutil,
38 templater,
38 templater,
39 ui as uimod,
39 ui as uimod,
40 util,
40 util,
41 )
41 )
42
42
43 from . import (
43 from . import (
44 hgweb_mod,
44 hgweb_mod,
45 request as requestmod,
45 request as requestmod,
46 webutil,
46 webutil,
47 wsgicgi,
47 wsgicgi,
48 )
48 )
49 from ..utils import dateutil
49 from ..utils import dateutil
50
50
51 def cleannames(items):
51 def cleannames(items):
52 return [(util.pconvert(name).strip('/'), path) for name, path in items]
52 return [(util.pconvert(name).strip('/'), path) for name, path in items]
53
53
54 def findrepos(paths):
54 def findrepos(paths):
55 repos = []
55 repos = []
56 for prefix, root in cleannames(paths):
56 for prefix, root in cleannames(paths):
57 roothead, roottail = os.path.split(root)
57 roothead, roottail = os.path.split(root)
58 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
58 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
59 # /bar/ be served as as foo/N .
59 # /bar/ be served as as foo/N .
60 # '*' will not search inside dirs with .hg (except .hg/patches),
60 # '*' will not search inside dirs with .hg (except .hg/patches),
61 # '**' will search inside dirs with .hg (and thus also find subrepos).
61 # '**' will search inside dirs with .hg (and thus also find subrepos).
62 try:
62 try:
63 recurse = {'*': False, '**': True}[roottail]
63 recurse = {'*': False, '**': True}[roottail]
64 except KeyError:
64 except KeyError:
65 repos.append((prefix, root))
65 repos.append((prefix, root))
66 continue
66 continue
67 roothead = os.path.normpath(os.path.abspath(roothead))
67 roothead = os.path.normpath(os.path.abspath(roothead))
68 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
68 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
69 repos.extend(urlrepos(prefix, roothead, paths))
69 repos.extend(urlrepos(prefix, roothead, paths))
70 return repos
70 return repos
71
71
72 def urlrepos(prefix, roothead, paths):
72 def urlrepos(prefix, roothead, paths):
73 """yield url paths and filesystem paths from a list of repo paths
73 """yield url paths and filesystem paths from a list of repo paths
74
74
75 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
75 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
76 >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
76 >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
77 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
77 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
78 >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
78 >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
79 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
79 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
80 """
80 """
81 for path in paths:
81 for path in paths:
82 path = os.path.normpath(path)
82 path = os.path.normpath(path)
83 yield (prefix + '/' +
83 yield (prefix + '/' +
84 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path
84 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path
85
85
86 def geturlcgivars(baseurl, port):
86 def geturlcgivars(baseurl, port):
87 """
87 """
88 Extract CGI variables from baseurl
88 Extract CGI variables from baseurl
89
89
90 >>> geturlcgivars(b"http://host.org/base", b"80")
90 >>> geturlcgivars(b"http://host.org/base", b"80")
91 ('host.org', '80', '/base')
91 ('host.org', '80', '/base')
92 >>> geturlcgivars(b"http://host.org:8000/base", b"80")
92 >>> geturlcgivars(b"http://host.org:8000/base", b"80")
93 ('host.org', '8000', '/base')
93 ('host.org', '8000', '/base')
94 >>> geturlcgivars(b'/base', 8000)
94 >>> geturlcgivars(b'/base', 8000)
95 ('', '8000', '/base')
95 ('', '8000', '/base')
96 >>> geturlcgivars(b"base", b'8000')
96 >>> geturlcgivars(b"base", b'8000')
97 ('', '8000', '/base')
97 ('', '8000', '/base')
98 >>> geturlcgivars(b"http://host", b'8000')
98 >>> geturlcgivars(b"http://host", b'8000')
99 ('host', '8000', '/')
99 ('host', '8000', '/')
100 >>> geturlcgivars(b"http://host/", b'8000')
100 >>> geturlcgivars(b"http://host/", b'8000')
101 ('host', '8000', '/')
101 ('host', '8000', '/')
102 """
102 """
103 u = util.url(baseurl)
103 u = util.url(baseurl)
104 name = u.host or ''
104 name = u.host or ''
105 if u.port:
105 if u.port:
106 port = u.port
106 port = u.port
107 path = u.path or ""
107 path = u.path or ""
108 if not path.startswith('/'):
108 if not path.startswith('/'):
109 path = '/' + path
109 path = '/' + path
110
110
111 return name, pycompat.bytestr(port), path
111 return name, pycompat.bytestr(port), path
112
112
113 class hgwebdir(object):
113 class hgwebdir(object):
114 """HTTP server for multiple repositories.
114 """HTTP server for multiple repositories.
115
115
116 Given a configuration, different repositories will be served depending
116 Given a configuration, different repositories will be served depending
117 on the request path.
117 on the request path.
118
118
119 Instances are typically used as WSGI applications.
119 Instances are typically used as WSGI applications.
120 """
120 """
121 def __init__(self, conf, baseui=None):
121 def __init__(self, conf, baseui=None):
122 self.conf = conf
122 self.conf = conf
123 self.baseui = baseui
123 self.baseui = baseui
124 self.ui = None
124 self.ui = None
125 self.lastrefresh = 0
125 self.lastrefresh = 0
126 self.motd = None
126 self.motd = None
127 self.refresh()
127 self.refresh()
128
128
129 def refresh(self):
129 def refresh(self):
130 if self.ui:
130 if self.ui:
131 refreshinterval = self.ui.configint('web', 'refreshinterval')
131 refreshinterval = self.ui.configint('web', 'refreshinterval')
132 else:
132 else:
133 item = configitems.coreitems['web']['refreshinterval']
133 item = configitems.coreitems['web']['refreshinterval']
134 refreshinterval = item.default
134 refreshinterval = item.default
135
135
136 # refreshinterval <= 0 means to always refresh.
136 # refreshinterval <= 0 means to always refresh.
137 if (refreshinterval > 0 and
137 if (refreshinterval > 0 and
138 self.lastrefresh + refreshinterval > time.time()):
138 self.lastrefresh + refreshinterval > time.time()):
139 return
139 return
140
140
141 if self.baseui:
141 if self.baseui:
142 u = self.baseui.copy()
142 u = self.baseui.copy()
143 else:
143 else:
144 u = uimod.ui.load()
144 u = uimod.ui.load()
145 u.setconfig('ui', 'report_untrusted', 'off', 'hgwebdir')
145 u.setconfig('ui', 'report_untrusted', 'off', 'hgwebdir')
146 u.setconfig('ui', 'nontty', 'true', 'hgwebdir')
146 u.setconfig('ui', 'nontty', 'true', 'hgwebdir')
147 # displaying bundling progress bar while serving feels wrong and may
147 # displaying bundling progress bar while serving feels wrong and may
148 # break some wsgi implementations.
148 # break some wsgi implementations.
149 u.setconfig('progress', 'disable', 'true', 'hgweb')
149 u.setconfig('progress', 'disable', 'true', 'hgweb')
150
150
151 if not isinstance(self.conf, (dict, list, tuple)):
151 if not isinstance(self.conf, (dict, list, tuple)):
152 map = {'paths': 'hgweb-paths'}
152 map = {'paths': 'hgweb-paths'}
153 if not os.path.exists(self.conf):
153 if not os.path.exists(self.conf):
154 raise error.Abort(_('config file %s not found!') % self.conf)
154 raise error.Abort(_('config file %s not found!') % self.conf)
155 u.readconfig(self.conf, remap=map, trust=True)
155 u.readconfig(self.conf, remap=map, trust=True)
156 paths = []
156 paths = []
157 for name, ignored in u.configitems('hgweb-paths'):
157 for name, ignored in u.configitems('hgweb-paths'):
158 for path in u.configlist('hgweb-paths', name):
158 for path in u.configlist('hgweb-paths', name):
159 paths.append((name, path))
159 paths.append((name, path))
160 elif isinstance(self.conf, (list, tuple)):
160 elif isinstance(self.conf, (list, tuple)):
161 paths = self.conf
161 paths = self.conf
162 elif isinstance(self.conf, dict):
162 elif isinstance(self.conf, dict):
163 paths = self.conf.items()
163 paths = self.conf.items()
164
164
165 repos = findrepos(paths)
165 repos = findrepos(paths)
166 for prefix, root in u.configitems('collections'):
166 for prefix, root in u.configitems('collections'):
167 prefix = util.pconvert(prefix)
167 prefix = util.pconvert(prefix)
168 for path in scmutil.walkrepos(root, followsym=True):
168 for path in scmutil.walkrepos(root, followsym=True):
169 repo = os.path.normpath(path)
169 repo = os.path.normpath(path)
170 name = util.pconvert(repo)
170 name = util.pconvert(repo)
171 if name.startswith(prefix):
171 if name.startswith(prefix):
172 name = name[len(prefix):]
172 name = name[len(prefix):]
173 repos.append((name.lstrip('/'), repo))
173 repos.append((name.lstrip('/'), repo))
174
174
175 self.repos = repos
175 self.repos = repos
176 self.ui = u
176 self.ui = u
177 encoding.encoding = self.ui.config('web', 'encoding')
177 encoding.encoding = self.ui.config('web', 'encoding')
178 self.style = self.ui.config('web', 'style')
178 self.style = self.ui.config('web', 'style')
179 self.templatepath = self.ui.config('web', 'templates', untrusted=False)
179 self.templatepath = self.ui.config('web', 'templates', untrusted=False)
180 self.stripecount = self.ui.config('web', 'stripes')
180 self.stripecount = self.ui.config('web', 'stripes')
181 if self.stripecount:
181 if self.stripecount:
182 self.stripecount = int(self.stripecount)
182 self.stripecount = int(self.stripecount)
183 self._baseurl = self.ui.config('web', 'baseurl')
183 self._baseurl = self.ui.config('web', 'baseurl')
184 prefix = self.ui.config('web', 'prefix')
184 prefix = self.ui.config('web', 'prefix')
185 if prefix.startswith('/'):
185 if prefix.startswith('/'):
186 prefix = prefix[1:]
186 prefix = prefix[1:]
187 if prefix.endswith('/'):
187 if prefix.endswith('/'):
188 prefix = prefix[:-1]
188 prefix = prefix[:-1]
189 self.prefix = prefix
189 self.prefix = prefix
190 self.lastrefresh = time.time()
190 self.lastrefresh = time.time()
191
191
192 def run(self):
192 def run(self):
193 if not encoding.environ.get('GATEWAY_INTERFACE',
193 if not encoding.environ.get('GATEWAY_INTERFACE',
194 '').startswith("CGI/1."):
194 '').startswith("CGI/1."):
195 raise RuntimeError("This function is only intended to be "
195 raise RuntimeError("This function is only intended to be "
196 "called while running as a CGI script.")
196 "called while running as a CGI script.")
197 wsgicgi.launch(self)
197 wsgicgi.launch(self)
198
198
199 def __call__(self, env, respond):
199 def __call__(self, env, respond):
200 wsgireq = requestmod.wsgirequest(env, respond)
200 wsgireq = requestmod.wsgirequest(env, respond)
201 return self.run_wsgi(wsgireq)
201 return self.run_wsgi(wsgireq)
202
202
203 def read_allowed(self, ui, wsgireq):
203 def read_allowed(self, ui, wsgireq):
204 """Check allow_read and deny_read config options of a repo's ui object
204 """Check allow_read and deny_read config options of a repo's ui object
205 to determine user permissions. By default, with neither option set (or
205 to determine user permissions. By default, with neither option set (or
206 both empty), allow all users to read the repo. There are two ways a
206 both empty), allow all users to read the repo. There are two ways a
207 user can be denied read access: (1) deny_read is not empty, and the
207 user can be denied read access: (1) deny_read is not empty, and the
208 user is unauthenticated or deny_read contains user (or *), and (2)
208 user is unauthenticated or deny_read contains user (or *), and (2)
209 allow_read is not empty and the user is not in allow_read. Return True
209 allow_read is not empty and the user is not in allow_read. Return True
210 if user is allowed to read the repo, else return False."""
210 if user is allowed to read the repo, else return False."""
211
211
212 user = wsgireq.env.get('REMOTE_USER')
212 user = wsgireq.env.get('REMOTE_USER')
213
213
214 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
214 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
215 if deny_read and (not user or ismember(ui, user, deny_read)):
215 if deny_read and (not user or ismember(ui, user, deny_read)):
216 return False
216 return False
217
217
218 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
218 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
219 # by default, allow reading if no allow_read option has been set
219 # by default, allow reading if no allow_read option has been set
220 if (not allow_read) or ismember(ui, user, allow_read):
220 if (not allow_read) or ismember(ui, user, allow_read):
221 return True
221 return True
222
222
223 return False
223 return False
224
224
225 def run_wsgi(self, wsgireq):
225 def run_wsgi(self, wsgireq):
226 profile = self.ui.configbool('profiling', 'enabled')
226 profile = self.ui.configbool('profiling', 'enabled')
227 with profiling.profile(self.ui, enabled=profile):
227 with profiling.profile(self.ui, enabled=profile):
228 for r in self._runwsgi(wsgireq):
228 for r in self._runwsgi(wsgireq):
229 yield r
229 yield r
230
230
231 def _runwsgi(self, wsgireq):
231 def _runwsgi(self, wsgireq):
232 req = wsgireq.req
232 req = wsgireq.req
233 res = wsgireq.res
233 res = wsgireq.res
234
234
235 try:
235 try:
236 self.refresh()
236 self.refresh()
237
237
238 csp, nonce = cspvalues(self.ui)
238 csp, nonce = cspvalues(self.ui)
239 if csp:
239 if csp:
240 res.headers['Content-Security-Policy'] = csp
240 res.headers['Content-Security-Policy'] = csp
241 wsgireq.headers.append(('Content-Security-Policy', csp))
241 wsgireq.headers.append(('Content-Security-Policy', csp))
242
242
243 virtual = wsgireq.env.get("PATH_INFO", "").strip('/')
243 virtual = wsgireq.env.get("PATH_INFO", "").strip('/')
244 tmpl = self.templater(wsgireq, nonce)
244 tmpl = self.templater(wsgireq, nonce)
245 ctype = tmpl('mimetype', encoding=encoding.encoding)
245 ctype = tmpl('mimetype', encoding=encoding.encoding)
246 ctype = templater.stringify(ctype)
246 ctype = templater.stringify(ctype)
247
247
248 # Global defaults. These can be overridden by any handler.
248 # Global defaults. These can be overridden by any handler.
249 res.status = '200 Script output follows'
249 res.status = '200 Script output follows'
250 res.headers['Content-Type'] = ctype
250 res.headers['Content-Type'] = ctype
251
251
252 # a static file
252 # a static file
253 if virtual.startswith('static/') or 'static' in req.qsparams:
253 if virtual.startswith('static/') or 'static' in req.qsparams:
254 if virtual.startswith('static/'):
254 if virtual.startswith('static/'):
255 fname = virtual[7:]
255 fname = virtual[7:]
256 else:
256 else:
257 fname = req.qsparams['static']
257 fname = req.qsparams['static']
258 static = self.ui.config("web", "static", None,
258 static = self.ui.config("web", "static", None,
259 untrusted=False)
259 untrusted=False)
260 if not static:
260 if not static:
261 tp = self.templatepath or templater.templatepaths()
261 tp = self.templatepath or templater.templatepaths()
262 if isinstance(tp, str):
262 if isinstance(tp, str):
263 tp = [tp]
263 tp = [tp]
264 static = [os.path.join(p, 'static') for p in tp]
264 static = [os.path.join(p, 'static') for p in tp]
265
265
266 staticfile(static, fname, res)
266 staticfile(static, fname, res)
267 return res.sendresponse()
267 return res.sendresponse()
268
268
269 # top-level index
269 # top-level index
270
270
271 repos = dict(self.repos)
271 repos = dict(self.repos)
272
272
273 if (not virtual or virtual == 'index') and virtual not in repos:
273 if (not virtual or virtual == 'index') and virtual not in repos:
274 wsgireq.respond(HTTP_OK, ctype)
274 wsgireq.respond(HTTP_OK, ctype)
275 return self.makeindex(wsgireq, tmpl)
275 return self.makeindex(wsgireq, tmpl)
276
276
277 # nested indexes and hgwebs
277 # nested indexes and hgwebs
278
278
279 if virtual.endswith('/index') and virtual not in repos:
279 if virtual.endswith('/index') and virtual not in repos:
280 subdir = virtual[:-len('index')]
280 subdir = virtual[:-len('index')]
281 if any(r.startswith(subdir) for r in repos):
281 if any(r.startswith(subdir) for r in repos):
282 wsgireq.respond(HTTP_OK, ctype)
282 wsgireq.respond(HTTP_OK, ctype)
283 return self.makeindex(wsgireq, tmpl, subdir)
283 return self.makeindex(wsgireq, tmpl, subdir)
284
284
285 def _virtualdirs():
285 def _virtualdirs():
286 # Check the full virtual path, each parent, and the root ('')
286 # Check the full virtual path, each parent, and the root ('')
287 if virtual != '':
287 if virtual != '':
288 yield virtual
288 yield virtual
289
289
290 for p in util.finddirs(virtual):
290 for p in util.finddirs(virtual):
291 yield p
291 yield p
292
292
293 yield ''
293 yield ''
294
294
295 for virtualrepo in _virtualdirs():
295 for virtualrepo in _virtualdirs():
296 real = repos.get(virtualrepo)
296 real = repos.get(virtualrepo)
297 if real:
297 if real:
298 wsgireq.env['REPO_NAME'] = virtualrepo
298 wsgireq.env['REPO_NAME'] = virtualrepo
299 # We have to re-parse because of updated environment
299 # We have to re-parse because of updated environment
300 # variable.
300 # variable.
301 # TODO this is kind of hacky and we should have a better
301 # TODO this is kind of hacky and we should have a better
302 # way of doing this than with REPO_NAME side-effects.
302 # way of doing this than with REPO_NAME side-effects.
303 wsgireq.req = requestmod.parserequestfromenv(
303 wsgireq.req = requestmod.parserequestfromenv(
304 wsgireq.env, wsgireq.req.bodyfh)
304 wsgireq.env, wsgireq.req.bodyfh)
305 try:
305 try:
306 # ensure caller gets private copy of ui
306 # ensure caller gets private copy of ui
307 repo = hg.repository(self.ui.copy(), real)
307 repo = hg.repository(self.ui.copy(), real)
308 return hgweb_mod.hgweb(repo).run_wsgi(wsgireq)
308 return hgweb_mod.hgweb(repo).run_wsgi(wsgireq)
309 except IOError as inst:
309 except IOError as inst:
310 msg = encoding.strtolocal(inst.strerror)
310 msg = encoding.strtolocal(inst.strerror)
311 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
311 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
312 except error.RepoError as inst:
312 except error.RepoError as inst:
313 raise ErrorResponse(HTTP_SERVER_ERROR, bytes(inst))
313 raise ErrorResponse(HTTP_SERVER_ERROR, bytes(inst))
314
314
315 # browse subdirectories
315 # browse subdirectories
316 subdir = virtual + '/'
316 subdir = virtual + '/'
317 if [r for r in repos if r.startswith(subdir)]:
317 if [r for r in repos if r.startswith(subdir)]:
318 wsgireq.respond(HTTP_OK, ctype)
318 wsgireq.respond(HTTP_OK, ctype)
319 return self.makeindex(wsgireq, tmpl, subdir)
319 return self.makeindex(wsgireq, tmpl, subdir)
320
320
321 # prefixes not found
321 # prefixes not found
322 wsgireq.respond(HTTP_NOT_FOUND, ctype)
322 wsgireq.respond(HTTP_NOT_FOUND, ctype)
323 return tmpl("notfound", repo=virtual)
323 return tmpl("notfound", repo=virtual)
324
324
325 except ErrorResponse as err:
325 except ErrorResponse as err:
326 wsgireq.respond(err, ctype)
326 wsgireq.respond(err, ctype)
327 return tmpl('error', error=err.message or '')
327 return tmpl('error', error=err.message or '')
328 finally:
328 finally:
329 tmpl = None
329 tmpl = None
330
330
331 def makeindex(self, wsgireq, tmpl, subdir=""):
331 def makeindex(self, wsgireq, tmpl, subdir=""):
332
332
333 def archivelist(ui, nodeid, url):
333 def archivelist(ui, nodeid, url):
334 allowed = ui.configlist("web", "allow_archive", untrusted=True)
334 allowed = ui.configlist("web", "allow_archive", untrusted=True)
335 archives = []
335 archives = []
336 for typ, spec in hgweb_mod.archivespecs.iteritems():
336 for typ, spec in hgweb_mod.archivespecs.iteritems():
337 if typ in allowed or ui.configbool("web", "allow" + typ,
337 if typ in allowed or ui.configbool("web", "allow" + typ,
338 untrusted=True):
338 untrusted=True):
339 archives.append({"type": typ, "extension": spec[2],
339 archives.append({"type": typ, "extension": spec[2],
340 "node": nodeid, "url": url})
340 "node": nodeid, "url": url})
341 return archives
341 return archives
342
342
343 def rawentries(subdir="", **map):
343 def rawentries(subdir="", **map):
344
344
345 descend = self.ui.configbool('web', 'descend')
345 descend = self.ui.configbool('web', 'descend')
346 collapse = self.ui.configbool('web', 'collapse')
346 collapse = self.ui.configbool('web', 'collapse')
347 seenrepos = set()
347 seenrepos = set()
348 seendirs = set()
348 seendirs = set()
349 for name, path in self.repos:
349 for name, path in self.repos:
350
350
351 if not name.startswith(subdir):
351 if not name.startswith(subdir):
352 continue
352 continue
353 name = name[len(subdir):]
353 name = name[len(subdir):]
354 directory = False
354 directory = False
355
355
356 if '/' in name:
356 if '/' in name:
357 if not descend:
357 if not descend:
358 continue
358 continue
359
359
360 nameparts = name.split('/')
360 nameparts = name.split('/')
361 rootname = nameparts[0]
361 rootname = nameparts[0]
362
362
363 if not collapse:
363 if not collapse:
364 pass
364 pass
365 elif rootname in seendirs:
365 elif rootname in seendirs:
366 continue
366 continue
367 elif rootname in seenrepos:
367 elif rootname in seenrepos:
368 pass
368 pass
369 else:
369 else:
370 directory = True
370 directory = True
371 name = rootname
371 name = rootname
372
372
373 # redefine the path to refer to the directory
373 # redefine the path to refer to the directory
374 discarded = '/'.join(nameparts[1:])
374 discarded = '/'.join(nameparts[1:])
375
375
376 # remove name parts plus accompanying slash
376 # remove name parts plus accompanying slash
377 path = path[:-len(discarded) - 1]
377 path = path[:-len(discarded) - 1]
378
378
379 try:
379 try:
380 r = hg.repository(self.ui, path)
380 r = hg.repository(self.ui, path)
381 directory = False
381 directory = False
382 except (IOError, error.RepoError):
382 except (IOError, error.RepoError):
383 pass
383 pass
384
384
385 parts = [name]
385 parts = [name]
386 parts.insert(0, '/' + subdir.rstrip('/'))
386 parts.insert(0, '/' + subdir.rstrip('/'))
387 if wsgireq.env['SCRIPT_NAME']:
387 if wsgireq.env['SCRIPT_NAME']:
388 parts.insert(0, wsgireq.env['SCRIPT_NAME'])
388 parts.insert(0, wsgireq.env['SCRIPT_NAME'])
389 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
389 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
390
390
391 # show either a directory entry or a repository
391 # show either a directory entry or a repository
392 if directory:
392 if directory:
393 # get the directory's time information
393 # get the directory's time information
394 try:
394 try:
395 d = (get_mtime(path), dateutil.makedate()[1])
395 d = (get_mtime(path), dateutil.makedate()[1])
396 except OSError:
396 except OSError:
397 continue
397 continue
398
398
399 # add '/' to the name to make it obvious that
399 # add '/' to the name to make it obvious that
400 # the entry is a directory, not a regular repository
400 # the entry is a directory, not a regular repository
401 row = {'contact': "",
401 row = {'contact': "",
402 'contact_sort': "",
402 'contact_sort': "",
403 'name': name + '/',
403 'name': name + '/',
404 'name_sort': name,
404 'name_sort': name,
405 'url': url,
405 'url': url,
406 'description': "",
406 'description': "",
407 'description_sort': "",
407 'description_sort': "",
408 'lastchange': d,
408 'lastchange': d,
409 'lastchange_sort': d[1]-d[0],
409 'lastchange_sort': d[1]-d[0],
410 'archives': [],
410 'archives': [],
411 'isdirectory': True,
411 'isdirectory': True,
412 'labels': [],
412 'labels': [],
413 }
413 }
414
414
415 seendirs.add(name)
415 seendirs.add(name)
416 yield row
416 yield row
417 continue
417 continue
418
418
419 u = self.ui.copy()
419 u = self.ui.copy()
420 try:
420 try:
421 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
421 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
422 except Exception as e:
422 except Exception as e:
423 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
423 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
424 continue
424 continue
425 def get(section, name, default=uimod._unset):
425 def get(section, name, default=uimod._unset):
426 return u.config(section, name, default, untrusted=True)
426 return u.config(section, name, default, untrusted=True)
427
427
428 if u.configbool("web", "hidden", untrusted=True):
428 if u.configbool("web", "hidden", untrusted=True):
429 continue
429 continue
430
430
431 if not self.read_allowed(u, wsgireq):
431 if not self.read_allowed(u, wsgireq):
432 continue
432 continue
433
433
434 # update time with local timezone
434 # update time with local timezone
435 try:
435 try:
436 r = hg.repository(self.ui, path)
436 r = hg.repository(self.ui, path)
437 except IOError:
437 except IOError:
438 u.warn(_('error accessing repository at %s\n') % path)
438 u.warn(_('error accessing repository at %s\n') % path)
439 continue
439 continue
440 except error.RepoError:
440 except error.RepoError:
441 u.warn(_('error accessing repository at %s\n') % path)
441 u.warn(_('error accessing repository at %s\n') % path)
442 continue
442 continue
443 try:
443 try:
444 d = (get_mtime(r.spath), dateutil.makedate()[1])
444 d = (get_mtime(r.spath), dateutil.makedate()[1])
445 except OSError:
445 except OSError:
446 continue
446 continue
447
447
448 contact = get_contact(get)
448 contact = get_contact(get)
449 description = get("web", "description")
449 description = get("web", "description")
450 seenrepos.add(name)
450 seenrepos.add(name)
451 name = get("web", "name", name)
451 name = get("web", "name", name)
452 row = {'contact': contact or "unknown",
452 row = {'contact': contact or "unknown",
453 'contact_sort': contact.upper() or "unknown",
453 'contact_sort': contact.upper() or "unknown",
454 'name': name,
454 'name': name,
455 'name_sort': name,
455 'name_sort': name,
456 'url': url,
456 'url': url,
457 'description': description or "unknown",
457 'description': description or "unknown",
458 'description_sort': description.upper() or "unknown",
458 'description_sort': description.upper() or "unknown",
459 'lastchange': d,
459 'lastchange': d,
460 'lastchange_sort': d[1]-d[0],
460 'lastchange_sort': d[1]-d[0],
461 'archives': archivelist(u, "tip", url),
461 'archives': archivelist(u, "tip", url),
462 'isdirectory': None,
462 'isdirectory': None,
463 'labels': u.configlist('web', 'labels', untrusted=True),
463 'labels': u.configlist('web', 'labels', untrusted=True),
464 }
464 }
465
465
466 yield row
466 yield row
467
467
468 sortdefault = None, False
468 sortdefault = None, False
469 def entries(sortcolumn="", descending=False, subdir="", **map):
469 def entries(sortcolumn="", descending=False, subdir="", **map):
470 rows = rawentries(subdir=subdir, **map)
470 rows = rawentries(subdir=subdir, **map)
471
471
472 if sortcolumn and sortdefault != (sortcolumn, descending):
472 if sortcolumn and sortdefault != (sortcolumn, descending):
473 sortkey = '%s_sort' % sortcolumn
473 sortkey = '%s_sort' % sortcolumn
474 rows = sorted(rows, key=lambda x: x[sortkey],
474 rows = sorted(rows, key=lambda x: x[sortkey],
475 reverse=descending)
475 reverse=descending)
476 for row, parity in zip(rows, paritygen(self.stripecount)):
476 for row, parity in zip(rows, paritygen(self.stripecount)):
477 row['parity'] = parity
477 row['parity'] = parity
478 yield row
478 yield row
479
479
480 self.refresh()
480 self.refresh()
481 sortable = ["name", "description", "contact", "lastchange"]
481 sortable = ["name", "description", "contact", "lastchange"]
482 sortcolumn, descending = sortdefault
482 sortcolumn, descending = sortdefault
483 if 'sort' in wsgireq.req.qsparams:
483 if 'sort' in wsgireq.req.qsparams:
484 sortcolum = wsgireq.req.qsparams['sort']
484 sortcolumn = wsgireq.req.qsparams['sort']
485 descending = sortcolumn.startswith('-')
485 descending = sortcolumn.startswith('-')
486 if descending:
486 if descending:
487 sortcolumn = sortcolumn[1:]
487 sortcolumn = sortcolumn[1:]
488 if sortcolumn not in sortable:
488 if sortcolumn not in sortable:
489 sortcolumn = ""
489 sortcolumn = ""
490
490
491 sort = [("sort_%s" % column,
491 sort = [("sort_%s" % column,
492 "%s%s" % ((not descending and column == sortcolumn)
492 "%s%s" % ((not descending and column == sortcolumn)
493 and "-" or "", column))
493 and "-" or "", column))
494 for column in sortable]
494 for column in sortable]
495
495
496 self.refresh()
496 self.refresh()
497 self.updatereqenv(wsgireq.env)
497 self.updatereqenv(wsgireq.env)
498
498
499 return tmpl("index", entries=entries, subdir=subdir,
499 return tmpl("index", entries=entries, subdir=subdir,
500 pathdef=hgweb_mod.makebreadcrumb('/' + subdir, self.prefix),
500 pathdef=hgweb_mod.makebreadcrumb('/' + subdir, self.prefix),
501 sortcolumn=sortcolumn, descending=descending,
501 sortcolumn=sortcolumn, descending=descending,
502 **dict(sort))
502 **dict(sort))
503
503
504 def templater(self, wsgireq, nonce):
504 def templater(self, wsgireq, nonce):
505
505
506 def motd(**map):
506 def motd(**map):
507 if self.motd is not None:
507 if self.motd is not None:
508 yield self.motd
508 yield self.motd
509 else:
509 else:
510 yield config('web', 'motd')
510 yield config('web', 'motd')
511
511
512 def config(section, name, default=uimod._unset, untrusted=True):
512 def config(section, name, default=uimod._unset, untrusted=True):
513 return self.ui.config(section, name, default, untrusted)
513 return self.ui.config(section, name, default, untrusted)
514
514
515 self.updatereqenv(wsgireq.env)
515 self.updatereqenv(wsgireq.env)
516
516
517 url = wsgireq.env.get('SCRIPT_NAME', '')
517 url = wsgireq.env.get('SCRIPT_NAME', '')
518 if not url.endswith('/'):
518 if not url.endswith('/'):
519 url += '/'
519 url += '/'
520
520
521 vars = {}
521 vars = {}
522 styles, (style, mapfile) = hgweb_mod.getstyle(wsgireq.req, config,
522 styles, (style, mapfile) = hgweb_mod.getstyle(wsgireq.req, config,
523 self.templatepath)
523 self.templatepath)
524 if style == styles[0]:
524 if style == styles[0]:
525 vars['style'] = style
525 vars['style'] = style
526
526
527 sessionvars = webutil.sessionvars(vars, r'?')
527 sessionvars = webutil.sessionvars(vars, r'?')
528 logourl = config('web', 'logourl')
528 logourl = config('web', 'logourl')
529 logoimg = config('web', 'logoimg')
529 logoimg = config('web', 'logoimg')
530 staticurl = config('web', 'staticurl') or url + 'static/'
530 staticurl = config('web', 'staticurl') or url + 'static/'
531 if not staticurl.endswith('/'):
531 if not staticurl.endswith('/'):
532 staticurl += '/'
532 staticurl += '/'
533
533
534 defaults = {
534 defaults = {
535 "encoding": encoding.encoding,
535 "encoding": encoding.encoding,
536 "motd": motd,
536 "motd": motd,
537 "url": url,
537 "url": url,
538 "logourl": logourl,
538 "logourl": logourl,
539 "logoimg": logoimg,
539 "logoimg": logoimg,
540 "staticurl": staticurl,
540 "staticurl": staticurl,
541 "sessionvars": sessionvars,
541 "sessionvars": sessionvars,
542 "style": style,
542 "style": style,
543 "nonce": nonce,
543 "nonce": nonce,
544 }
544 }
545 tmpl = templater.templater.frommapfile(mapfile, defaults=defaults)
545 tmpl = templater.templater.frommapfile(mapfile, defaults=defaults)
546 return tmpl
546 return tmpl
547
547
548 def updatereqenv(self, env):
548 def updatereqenv(self, env):
549 if self._baseurl is not None:
549 if self._baseurl is not None:
550 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
550 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
551 env['SERVER_NAME'] = name
551 env['SERVER_NAME'] = name
552 env['SERVER_PORT'] = port
552 env['SERVER_PORT'] = port
553 env['SCRIPT_NAME'] = path
553 env['SCRIPT_NAME'] = path
General Comments 0
You need to be logged in to leave comments. Login now