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