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