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