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