##// END OF EJS Templates
hgweb: extract config values after reading webdir-config
Dirkjan Ochtman -
r8621:13613221 default
parent child Browse files
Show More
@@ -1,328 +1,328 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, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
8
8
9 import os, time
9 import os, time
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import ui, hg, util, templater
11 from mercurial import ui, hg, util, templater
12 from mercurial import error, encoding
12 from mercurial import error, encoding
13 from common import ErrorResponse, get_mtime, staticfile, paritygen,\
13 from common import ErrorResponse, get_mtime, staticfile, paritygen,\
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from hgweb_mod import hgweb
15 from hgweb_mod import hgweb
16 from request import wsgirequest
16 from request import wsgirequest
17 import webutil
17 import webutil
18
18
19 def cleannames(items):
19 def cleannames(items):
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
21
21
22 def findrepos(paths):
22 def findrepos(paths):
23 repos = {}
23 repos = {}
24 for prefix, root in cleannames(paths):
24 for prefix, root in cleannames(paths):
25 roothead, roottail = os.path.split(root)
25 roothead, roottail = os.path.split(root)
26 # "foo = /bar/*" makes every subrepo of /bar/ to be
26 # "foo = /bar/*" makes every subrepo of /bar/ to be
27 # mounted as foo/subrepo
27 # mounted as foo/subrepo
28 # and "foo = /bar/**" also recurses into the subdirectories,
28 # and "foo = /bar/**" also recurses into the subdirectories,
29 # remember to use it without working dir.
29 # remember to use it without working dir.
30 try:
30 try:
31 recurse = {'*': False, '**': True}[roottail]
31 recurse = {'*': False, '**': True}[roottail]
32 except KeyError:
32 except KeyError:
33 repos[prefix] = root
33 repos[prefix] = root
34 continue
34 continue
35 roothead = os.path.normpath(roothead)
35 roothead = os.path.normpath(roothead)
36 for path in util.walkrepos(roothead, followsym=True, recurse=recurse):
36 for path in util.walkrepos(roothead, followsym=True, recurse=recurse):
37 path = os.path.normpath(path)
37 path = os.path.normpath(path)
38 name = util.pconvert(path[len(roothead):]).strip('/')
38 name = util.pconvert(path[len(roothead):]).strip('/')
39 if prefix:
39 if prefix:
40 name = prefix + '/' + name
40 name = prefix + '/' + name
41 repos[name] = path
41 repos[name] = path
42 return repos.items()
42 return repos.items()
43
43
44 class hgwebdir(object):
44 class hgwebdir(object):
45 refreshinterval = 20
45 refreshinterval = 20
46
46
47 def __init__(self, conf, baseui=None):
47 def __init__(self, conf, baseui=None):
48 self.conf = conf
48 self.conf = conf
49 self.baseui = baseui
49 self.baseui = baseui
50 self.lastrefresh = 0
50 self.lastrefresh = 0
51 self.refresh()
51 self.refresh()
52
52
53 def refresh(self):
53 def refresh(self):
54 if self.lastrefresh + self.refreshinterval > time.time():
54 if self.lastrefresh + self.refreshinterval > time.time():
55 return
55 return
56
56
57 if self.baseui:
57 if self.baseui:
58 self.ui = self.baseui.copy()
58 self.ui = self.baseui.copy()
59 else:
59 else:
60 self.ui = ui.ui()
60 self.ui = ui.ui()
61 self.ui.setconfig('ui', 'report_untrusted', 'off')
61 self.ui.setconfig('ui', 'report_untrusted', 'off')
62 self.ui.setconfig('ui', 'interactive', 'off')
62 self.ui.setconfig('ui', 'interactive', 'off')
63
63
64 self.motd = self.ui.config('web', 'motd')
65 self.style = self.ui.config('web', 'style', 'paper')
66 self.stripecount = self.ui.config('web', 'stripes', 1)
67 if self.stripecount:
68 self.stripecount = int(self.stripecount)
69 self._baseurl = self.ui.config('web', 'baseurl')
70
71 if not isinstance(self.conf, (dict, list, tuple)):
64 if not isinstance(self.conf, (dict, list, tuple)):
72 map = {'paths': 'hgweb-paths'}
65 map = {'paths': 'hgweb-paths'}
73 self.ui.readconfig(self.conf, remap=map, trust=True)
66 self.ui.readconfig(self.conf, remap=map, trust=True)
74 paths = self.ui.configitems('hgweb-paths')
67 paths = self.ui.configitems('hgweb-paths')
75 elif isinstance(self.conf, (list, tuple)):
68 elif isinstance(self.conf, (list, tuple)):
76 paths = self.conf
69 paths = self.conf
77 elif isinstance(self.conf, dict):
70 elif isinstance(self.conf, dict):
78 paths = self.conf.items()
71 paths = self.conf.items()
79
72
73 self.motd = self.ui.config('web', 'motd')
74 self.style = self.ui.config('web', 'style', 'paper')
75 self.stripecount = self.ui.config('web', 'stripes', 1)
76 if self.stripecount:
77 self.stripecount = int(self.stripecount)
78 self._baseurl = self.ui.config('web', 'baseurl')
79
80 self.repos = findrepos(paths)
80 self.repos = findrepos(paths)
81 for prefix, root in self.ui.configitems('collections'):
81 for prefix, root in self.ui.configitems('collections'):
82 prefix = util.pconvert(prefix)
82 prefix = util.pconvert(prefix)
83 for path in util.walkrepos(root, followsym=True):
83 for path in util.walkrepos(root, followsym=True):
84 repo = os.path.normpath(path)
84 repo = os.path.normpath(path)
85 name = util.pconvert(repo)
85 name = util.pconvert(repo)
86 if name.startswith(prefix):
86 if name.startswith(prefix):
87 name = name[len(prefix):]
87 name = name[len(prefix):]
88 self.repos.append((name.lstrip('/'), repo))
88 self.repos.append((name.lstrip('/'), repo))
89
89
90 self.repos.sort()
90 self.repos.sort()
91 self.lastrefresh = time.time()
91 self.lastrefresh = time.time()
92
92
93 def run(self):
93 def run(self):
94 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
94 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
95 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
95 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
96 import mercurial.hgweb.wsgicgi as wsgicgi
96 import mercurial.hgweb.wsgicgi as wsgicgi
97 wsgicgi.launch(self)
97 wsgicgi.launch(self)
98
98
99 def __call__(self, env, respond):
99 def __call__(self, env, respond):
100 req = wsgirequest(env, respond)
100 req = wsgirequest(env, respond)
101 return self.run_wsgi(req)
101 return self.run_wsgi(req)
102
102
103 def read_allowed(self, ui, req):
103 def read_allowed(self, ui, req):
104 """Check allow_read and deny_read config options of a repo's ui object
104 """Check allow_read and deny_read config options of a repo's ui object
105 to determine user permissions. By default, with neither option set (or
105 to determine user permissions. By default, with neither option set (or
106 both empty), allow all users to read the repo. There are two ways a
106 both empty), allow all users to read the repo. There are two ways a
107 user can be denied read access: (1) deny_read is not empty, and the
107 user can be denied read access: (1) deny_read is not empty, and the
108 user is unauthenticated or deny_read contains user (or *), and (2)
108 user is unauthenticated or deny_read contains user (or *), and (2)
109 allow_read is not empty and the user is not in allow_read. Return True
109 allow_read is not empty and the user is not in allow_read. Return True
110 if user is allowed to read the repo, else return False."""
110 if user is allowed to read the repo, else return False."""
111
111
112 user = req.env.get('REMOTE_USER')
112 user = req.env.get('REMOTE_USER')
113
113
114 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
114 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
115 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
115 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
116 return False
116 return False
117
117
118 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
118 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
119 # by default, allow reading if no allow_read option has been set
119 # by default, allow reading if no allow_read option has been set
120 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
120 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
121 return True
121 return True
122
122
123 return False
123 return False
124
124
125 def run_wsgi(self, req):
125 def run_wsgi(self, req):
126 try:
126 try:
127 try:
127 try:
128 self.refresh()
128 self.refresh()
129
129
130 virtual = req.env.get("PATH_INFO", "").strip('/')
130 virtual = req.env.get("PATH_INFO", "").strip('/')
131 tmpl = self.templater(req)
131 tmpl = self.templater(req)
132 ctype = tmpl('mimetype', encoding=encoding.encoding)
132 ctype = tmpl('mimetype', encoding=encoding.encoding)
133 ctype = templater.stringify(ctype)
133 ctype = templater.stringify(ctype)
134
134
135 # a static file
135 # a static file
136 if virtual.startswith('static/') or 'static' in req.form:
136 if virtual.startswith('static/') or 'static' in req.form:
137 if virtual.startswith('static/'):
137 if virtual.startswith('static/'):
138 fname = virtual[7:]
138 fname = virtual[7:]
139 else:
139 else:
140 fname = req.form['static'][0]
140 fname = req.form['static'][0]
141 static = templater.templatepath('static')
141 static = templater.templatepath('static')
142 return (staticfile(static, fname, req),)
142 return (staticfile(static, fname, req),)
143
143
144 # top-level index
144 # top-level index
145 elif not virtual:
145 elif not virtual:
146 req.respond(HTTP_OK, ctype)
146 req.respond(HTTP_OK, ctype)
147 return self.makeindex(req, tmpl)
147 return self.makeindex(req, tmpl)
148
148
149 # nested indexes and hgwebs
149 # nested indexes and hgwebs
150
150
151 repos = dict(self.repos)
151 repos = dict(self.repos)
152 while virtual:
152 while virtual:
153 real = repos.get(virtual)
153 real = repos.get(virtual)
154 if real:
154 if real:
155 req.env['REPO_NAME'] = virtual
155 req.env['REPO_NAME'] = virtual
156 try:
156 try:
157 repo = hg.repository(self.ui, real)
157 repo = hg.repository(self.ui, real)
158 return hgweb(repo).run_wsgi(req)
158 return hgweb(repo).run_wsgi(req)
159 except IOError, inst:
159 except IOError, inst:
160 msg = inst.strerror
160 msg = inst.strerror
161 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
161 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
162 except error.RepoError, inst:
162 except error.RepoError, inst:
163 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
163 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
164
164
165 # browse subdirectories
165 # browse subdirectories
166 subdir = virtual + '/'
166 subdir = virtual + '/'
167 if [r for r in repos if r.startswith(subdir)]:
167 if [r for r in repos if r.startswith(subdir)]:
168 req.respond(HTTP_OK, ctype)
168 req.respond(HTTP_OK, ctype)
169 return self.makeindex(req, tmpl, subdir)
169 return self.makeindex(req, tmpl, subdir)
170
170
171 up = virtual.rfind('/')
171 up = virtual.rfind('/')
172 if up < 0:
172 if up < 0:
173 break
173 break
174 virtual = virtual[:up]
174 virtual = virtual[:up]
175
175
176 # prefixes not found
176 # prefixes not found
177 req.respond(HTTP_NOT_FOUND, ctype)
177 req.respond(HTTP_NOT_FOUND, ctype)
178 return tmpl("notfound", repo=virtual)
178 return tmpl("notfound", repo=virtual)
179
179
180 except ErrorResponse, err:
180 except ErrorResponse, err:
181 req.respond(err, ctype)
181 req.respond(err, ctype)
182 return tmpl('error', error=err.message or '')
182 return tmpl('error', error=err.message or '')
183 finally:
183 finally:
184 tmpl = None
184 tmpl = None
185
185
186 def makeindex(self, req, tmpl, subdir=""):
186 def makeindex(self, req, tmpl, subdir=""):
187
187
188 def archivelist(ui, nodeid, url):
188 def archivelist(ui, nodeid, url):
189 allowed = ui.configlist("web", "allow_archive", untrusted=True)
189 allowed = ui.configlist("web", "allow_archive", untrusted=True)
190 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
190 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
191 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
191 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
192 untrusted=True):
192 untrusted=True):
193 yield {"type" : i[0], "extension": i[1],
193 yield {"type" : i[0], "extension": i[1],
194 "node": nodeid, "url": url}
194 "node": nodeid, "url": url}
195
195
196 sortdefault = 'name', False
196 sortdefault = 'name', False
197 def entries(sortcolumn="", descending=False, subdir="", **map):
197 def entries(sortcolumn="", descending=False, subdir="", **map):
198 rows = []
198 rows = []
199 parity = paritygen(self.stripecount)
199 parity = paritygen(self.stripecount)
200 for name, path in self.repos:
200 for name, path in self.repos:
201 if not name.startswith(subdir):
201 if not name.startswith(subdir):
202 continue
202 continue
203 name = name[len(subdir):]
203 name = name[len(subdir):]
204
204
205 u = self.ui.copy()
205 u = self.ui.copy()
206 try:
206 try:
207 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
207 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
208 except Exception, e:
208 except Exception, e:
209 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
209 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
210 continue
210 continue
211 def get(section, name, default=None):
211 def get(section, name, default=None):
212 return u.config(section, name, default, untrusted=True)
212 return u.config(section, name, default, untrusted=True)
213
213
214 if u.configbool("web", "hidden", untrusted=True):
214 if u.configbool("web", "hidden", untrusted=True):
215 continue
215 continue
216
216
217 if not self.read_allowed(u, req):
217 if not self.read_allowed(u, req):
218 continue
218 continue
219
219
220 parts = [name]
220 parts = [name]
221 if 'PATH_INFO' in req.env:
221 if 'PATH_INFO' in req.env:
222 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
222 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
223 if req.env['SCRIPT_NAME']:
223 if req.env['SCRIPT_NAME']:
224 parts.insert(0, req.env['SCRIPT_NAME'])
224 parts.insert(0, req.env['SCRIPT_NAME'])
225 url = ('/'.join(parts).replace("//", "/")) + '/'
225 url = ('/'.join(parts).replace("//", "/")) + '/'
226
226
227 # update time with local timezone
227 # update time with local timezone
228 try:
228 try:
229 d = (get_mtime(path), util.makedate()[1])
229 d = (get_mtime(path), util.makedate()[1])
230 except OSError:
230 except OSError:
231 continue
231 continue
232
232
233 contact = get_contact(get)
233 contact = get_contact(get)
234 description = get("web", "description", "")
234 description = get("web", "description", "")
235 name = get("web", "name", name)
235 name = get("web", "name", name)
236 row = dict(contact=contact or "unknown",
236 row = dict(contact=contact or "unknown",
237 contact_sort=contact.upper() or "unknown",
237 contact_sort=contact.upper() or "unknown",
238 name=name,
238 name=name,
239 name_sort=name,
239 name_sort=name,
240 url=url,
240 url=url,
241 description=description or "unknown",
241 description=description or "unknown",
242 description_sort=description.upper() or "unknown",
242 description_sort=description.upper() or "unknown",
243 lastchange=d,
243 lastchange=d,
244 lastchange_sort=d[1]-d[0],
244 lastchange_sort=d[1]-d[0],
245 archives=archivelist(u, "tip", url))
245 archives=archivelist(u, "tip", url))
246 if (not sortcolumn or (sortcolumn, descending) == sortdefault):
246 if (not sortcolumn or (sortcolumn, descending) == sortdefault):
247 # fast path for unsorted output
247 # fast path for unsorted output
248 row['parity'] = parity.next()
248 row['parity'] = parity.next()
249 yield row
249 yield row
250 else:
250 else:
251 rows.append((row["%s_sort" % sortcolumn], row))
251 rows.append((row["%s_sort" % sortcolumn], row))
252 if rows:
252 if rows:
253 rows.sort()
253 rows.sort()
254 if descending:
254 if descending:
255 rows.reverse()
255 rows.reverse()
256 for key, row in rows:
256 for key, row in rows:
257 row['parity'] = parity.next()
257 row['parity'] = parity.next()
258 yield row
258 yield row
259
259
260 self.refresh()
260 self.refresh()
261 sortable = ["name", "description", "contact", "lastchange"]
261 sortable = ["name", "description", "contact", "lastchange"]
262 sortcolumn, descending = sortdefault
262 sortcolumn, descending = sortdefault
263 if 'sort' in req.form:
263 if 'sort' in req.form:
264 sortcolumn = req.form['sort'][0]
264 sortcolumn = req.form['sort'][0]
265 descending = sortcolumn.startswith('-')
265 descending = sortcolumn.startswith('-')
266 if descending:
266 if descending:
267 sortcolumn = sortcolumn[1:]
267 sortcolumn = sortcolumn[1:]
268 if sortcolumn not in sortable:
268 if sortcolumn not in sortable:
269 sortcolumn = ""
269 sortcolumn = ""
270
270
271 sort = [("sort_%s" % column,
271 sort = [("sort_%s" % column,
272 "%s%s" % ((not descending and column == sortcolumn)
272 "%s%s" % ((not descending and column == sortcolumn)
273 and "-" or "", column))
273 and "-" or "", column))
274 for column in sortable]
274 for column in sortable]
275
275
276 self.refresh()
276 self.refresh()
277 if self._baseurl is not None:
277 if self._baseurl is not None:
278 req.env['SCRIPT_NAME'] = self._baseurl
278 req.env['SCRIPT_NAME'] = self._baseurl
279
279
280 return tmpl("index", entries=entries, subdir=subdir,
280 return tmpl("index", entries=entries, subdir=subdir,
281 sortcolumn=sortcolumn, descending=descending,
281 sortcolumn=sortcolumn, descending=descending,
282 **dict(sort))
282 **dict(sort))
283
283
284 def templater(self, req):
284 def templater(self, req):
285
285
286 def header(**map):
286 def header(**map):
287 yield tmpl('header', encoding=encoding.encoding, **map)
287 yield tmpl('header', encoding=encoding.encoding, **map)
288
288
289 def footer(**map):
289 def footer(**map):
290 yield tmpl("footer", **map)
290 yield tmpl("footer", **map)
291
291
292 def motd(**map):
292 def motd(**map):
293 if self.motd is not None:
293 if self.motd is not None:
294 yield self.motd
294 yield self.motd
295 else:
295 else:
296 yield config('web', 'motd', '')
296 yield config('web', 'motd', '')
297
297
298 def config(section, name, default=None, untrusted=True):
298 def config(section, name, default=None, untrusted=True):
299 return self.ui.config(section, name, default, untrusted)
299 return self.ui.config(section, name, default, untrusted)
300
300
301 if self._baseurl is not None:
301 if self._baseurl is not None:
302 req.env['SCRIPT_NAME'] = self._baseurl
302 req.env['SCRIPT_NAME'] = self._baseurl
303
303
304 url = req.env.get('SCRIPT_NAME', '')
304 url = req.env.get('SCRIPT_NAME', '')
305 if not url.endswith('/'):
305 if not url.endswith('/'):
306 url += '/'
306 url += '/'
307
307
308 vars = {}
308 vars = {}
309 style = self.style
309 style = self.style
310 if 'style' in req.form:
310 if 'style' in req.form:
311 vars['style'] = style = req.form['style'][0]
311 vars['style'] = style = req.form['style'][0]
312 start = url[-1] == '?' and '&' or '?'
312 start = url[-1] == '?' and '&' or '?'
313 sessionvars = webutil.sessionvars(vars, start)
313 sessionvars = webutil.sessionvars(vars, start)
314
314
315 staticurl = config('web', 'staticurl') or url + 'static/'
315 staticurl = config('web', 'staticurl') or url + 'static/'
316 if not staticurl.endswith('/'):
316 if not staticurl.endswith('/'):
317 staticurl += '/'
317 staticurl += '/'
318
318
319 style = 'style' in req.form and req.form['style'][0] or self.style
319 style = 'style' in req.form and req.form['style'][0] or self.style
320 mapfile = templater.stylemap(style)
320 mapfile = templater.stylemap(style)
321 tmpl = templater.templater(mapfile,
321 tmpl = templater.templater(mapfile,
322 defaults={"header": header,
322 defaults={"header": header,
323 "footer": footer,
323 "footer": footer,
324 "motd": motd,
324 "motd": motd,
325 "url": url,
325 "url": url,
326 "staticurl": staticurl,
326 "staticurl": staticurl,
327 "sessionvars": sessionvars})
327 "sessionvars": sessionvars})
328 return tmpl
328 return tmpl
General Comments 0
You need to be logged in to leave comments. Login now