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