##// END OF EJS Templates
Merge from crew-stable.
Dirkjan Ochtman -
r6460:a63aed91 merge default
parent child Browse files
Show More
@@ -0,0 +1,59 b''
1 #!/bin/sh
2 # This tests if hgweb and hgwebdir still work if the REQUEST_URI variable is
3 # no longer passed with the request. Instead, SCRIPT_NAME and PATH_INFO
4 # should be used from d74fc8dec2b4 onward to route the request.
5
6 mkdir repo
7 cd repo
8 hg init
9 echo foo > bar
10 hg add bar
11 hg commit -m "test" -d "0 0" -u "Testing"
12 hg tip
13
14 cat > request.py <<EOF
15 from mercurial.hgweb import hgweb, hgwebdir
16 from StringIO import StringIO
17 import os, sys
18
19 errors = StringIO()
20 input = StringIO()
21
22 def startrsp(headers, data):
23 print '---- HEADERS'
24 print headers
25 print '---- DATA'
26 print data
27 return output.write
28
29 env = {
30 'wsgi.version': (1, 0),
31 'wsgi.url_scheme': 'http',
32 'wsgi.errors': errors,
33 'wsgi.input': input,
34 'wsgi.multithread': False,
35 'wsgi.multiprocess': False,
36 'wsgi.run_once': False,
37 'REQUEST_METHOD': 'GET',
38 'SCRIPT_NAME': '',
39 'SERVER_NAME': '127.0.0.1',
40 'SERVER_PORT': os.environ['HGPORT'],
41 'SERVER_PROTOCOL': 'HTTP/1.0'
42 }
43
44 output = StringIO()
45 env['QUERY_STRING'] = 'style=atom'
46 hgweb('.', name = 'repo')(env, startrsp)
47 print output.getvalue()
48 print '---- ERRORS'
49 print errors.getvalue()
50
51 output = StringIO()
52 env['QUERY_STRING'] = 'style=raw'
53 hgwebdir({'repo': '.'})(env, startrsp)
54 print output.getvalue()
55 print '---- ERRORS'
56 print errors.getvalue()
57 EOF
58
59 python request.py | sed "s/http:\/\/127\.0\.0\.1:[0-9]*\//http:\/\/127.0.0.1\//"
@@ -0,0 +1,50 b''
1 changeset: 0:4cbec7e6f8c4
2 tag: tip
3 user: Testing
4 date: Thu Jan 01 00:00:00 1970 +0000
5 summary: test
6
7 ---- HEADERS
8 200 Script output follows
9 ---- DATA
10 [('Content-Type', 'application/atom+xml; charset=ascii')]
11 <?xml version="1.0" encoding="ascii"?>
12 <feed xmlns="http://www.w3.org/2005/Atom">
13 <!-- Changelog -->
14 <id>http://127.0.0.1/</id>
15 <link rel="self" href="http://127.0.0.1/atom-log"/>
16 <link rel="alternate" href="http://127.0.0.1/"/>
17 <title>repo Changelog</title>
18 <updated>1970-01-01T00:00:00+00:00</updated>
19
20 <entry>
21 <title>test</title>
22 <id>http://www.selenic.com/mercurial/#changeset-4cbec7e6f8c42eb52b6b52670e1f7560ae9a101e</id>
23 <link href="http://127.0.0.1/rev/4cbec7e6f8c42eb52b6b52670e1f7560ae9a101e"/>
24 <author>
25 <name>Testing</name>
26 <email>&#84;&#101;&#115;&#116;&#105;&#110;&#103;</email>
27 </author>
28 <updated>1970-01-01T00:00:00+00:00</updated>
29 <published>1970-01-01T00:00:00+00:00</published>
30 <content type="xhtml">
31 <div xmlns="http://www.w3.org/1999/xhtml">
32 <pre xml:space="preserve">test</pre>
33 </div>
34 </content>
35 </entry>
36
37 </feed>
38
39 ---- ERRORS
40
41 ---- HEADERS
42 200 Script output follows
43 ---- DATA
44 [('Content-Type', 'text/plain; charset=ascii')]
45
46 repo/
47
48
49 ---- ERRORS
50
@@ -1,379 +1,379 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 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, mimetypes
10 10 from mercurial.node import hex, nullid
11 11 from mercurial.repo import RepoError
12 12 from mercurial import mdiff, ui, hg, util, patch, hook
13 13 from mercurial import revlog, templater, templatefilters, changegroup
14 14 from common import get_mtime, style_map, paritygen, countgen, ErrorResponse
15 15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 16 from request import wsgirequest
17 17 import webcommands, protocol, webutil
18 18
19 19 shortcuts = {
20 20 'cl': [('cmd', ['changelog']), ('rev', None)],
21 21 'sl': [('cmd', ['shortlog']), ('rev', None)],
22 22 'cs': [('cmd', ['changeset']), ('node', None)],
23 23 'f': [('cmd', ['file']), ('filenode', None)],
24 24 'fl': [('cmd', ['filelog']), ('filenode', None)],
25 25 'fd': [('cmd', ['filediff']), ('node', None)],
26 26 'fa': [('cmd', ['annotate']), ('filenode', None)],
27 27 'mf': [('cmd', ['manifest']), ('manifest', None)],
28 28 'ca': [('cmd', ['archive']), ('node', None)],
29 29 'tags': [('cmd', ['tags'])],
30 30 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
31 31 'static': [('cmd', ['static']), ('file', None)]
32 32 }
33 33
34 34 class hgweb(object):
35 35 def __init__(self, repo, name=None):
36 36 if isinstance(repo, str):
37 37 parentui = ui.ui(report_untrusted=False, interactive=False)
38 38 self.repo = hg.repository(parentui, repo)
39 39 else:
40 40 self.repo = repo
41 41
42 42 hook.redirect(True)
43 43 self.mtime = -1
44 44 self.reponame = name
45 45 self.archives = 'zip', 'gz', 'bz2'
46 46 self.stripecount = 1
47 47 self._capabilities = None
48 48 # a repo owner may set web.templates in .hg/hgrc to get any file
49 49 # readable by the user running the CGI script
50 50 self.templatepath = self.config("web", "templates",
51 51 templater.templatepath(),
52 52 untrusted=False)
53 53
54 54 # The CGI scripts are often run by a user different from the repo owner.
55 55 # Trust the settings from the .hg/hgrc files by default.
56 56 def config(self, section, name, default=None, untrusted=True):
57 57 return self.repo.ui.config(section, name, default,
58 58 untrusted=untrusted)
59 59
60 60 def configbool(self, section, name, default=False, untrusted=True):
61 61 return self.repo.ui.configbool(section, name, default,
62 62 untrusted=untrusted)
63 63
64 64 def configlist(self, section, name, default=None, untrusted=True):
65 65 return self.repo.ui.configlist(section, name, default,
66 66 untrusted=untrusted)
67 67
68 68 def refresh(self):
69 69 mtime = get_mtime(self.repo.root)
70 70 if mtime != self.mtime:
71 71 self.mtime = mtime
72 72 self.repo = hg.repository(self.repo.ui, self.repo.root)
73 73 self.maxchanges = int(self.config("web", "maxchanges", 10))
74 74 self.stripecount = int(self.config("web", "stripes", 1))
75 75 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
76 76 self.maxfiles = int(self.config("web", "maxfiles", 10))
77 77 self.allowpull = self.configbool("web", "allowpull", True)
78 78 self.encoding = self.config("web", "encoding", util._encoding)
79 79 self._capabilities = None
80 80
81 81 def capabilities(self):
82 82 if self._capabilities is not None:
83 83 return self._capabilities
84 84 caps = ['lookup', 'changegroupsubset']
85 85 if self.configbool('server', 'uncompressed'):
86 86 caps.append('stream=%d' % self.repo.changelog.version)
87 87 if changegroup.bundlepriority:
88 88 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
89 89 self._capabilities = caps
90 90 return caps
91 91
92 92 def run(self):
93 93 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
94 94 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
95 95 import mercurial.hgweb.wsgicgi as wsgicgi
96 96 wsgicgi.launch(self)
97 97
98 98 def __call__(self, env, respond):
99 99 req = wsgirequest(env, respond)
100 100 self.run_wsgi(req)
101 101 return req
102 102
103 103 def run_wsgi(self, req):
104 104
105 105 self.refresh()
106 106
107 107 # expand form shortcuts
108 108
109 109 for k in shortcuts.iterkeys():
110 110 if k in req.form:
111 111 for name, value in shortcuts[k]:
112 112 if value is None:
113 113 value = req.form[k]
114 114 req.form[name] = value
115 115 del req.form[k]
116 116
117 117 # work with CGI variables to create coherent structure
118 118 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
119 119
120 120 req.url = req.env['SCRIPT_NAME']
121 121 if not req.url.endswith('/'):
122 122 req.url += '/'
123 123 if 'REPO_NAME' in req.env:
124 124 req.url += req.env['REPO_NAME'] + '/'
125 125
126 if req.env.get('PATH_INFO'):
127 parts = req.env.get('PATH_INFO').strip('/').split('/')
126 if 'PATH_INFO' in req.env:
127 parts = req.env['PATH_INFO'].strip('/').split('/')
128 128 repo_parts = req.env.get('REPO_NAME', '').split('/')
129 129 if parts[:len(repo_parts)] == repo_parts:
130 130 parts = parts[len(repo_parts):]
131 131 query = '/'.join(parts)
132 132 else:
133 133 query = req.env['QUERY_STRING'].split('&', 1)[0]
134 134 query = query.split(';', 1)[0]
135 135
136 136 # translate user-visible url structure to internal structure
137 137
138 138 args = query.split('/', 2)
139 139 if 'cmd' not in req.form and args and args[0]:
140 140
141 141 cmd = args.pop(0)
142 142 style = cmd.rfind('-')
143 143 if style != -1:
144 144 req.form['style'] = [cmd[:style]]
145 145 cmd = cmd[style+1:]
146 146
147 147 # avoid accepting e.g. style parameter as command
148 148 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
149 149 req.form['cmd'] = [cmd]
150 150
151 151 if args and args[0]:
152 152 node = args.pop(0)
153 153 req.form['node'] = [node]
154 154 if args:
155 155 req.form['file'] = args
156 156
157 157 if cmd == 'static':
158 158 req.form['file'] = req.form['node']
159 159 elif cmd == 'archive':
160 160 fn = req.form['node'][0]
161 161 for type_, spec in self.archive_specs.iteritems():
162 162 ext = spec[2]
163 163 if fn.endswith(ext):
164 164 req.form['node'] = [fn[:-len(ext)]]
165 165 req.form['type'] = [type_]
166 166
167 167 # process this if it's a protocol request
168 168
169 169 cmd = req.form.get('cmd', [''])[0]
170 170 if cmd in protocol.__all__:
171 171 method = getattr(protocol, cmd)
172 172 method(self, req)
173 173 return
174 174
175 175 # process the web interface request
176 176
177 177 try:
178 178
179 179 tmpl = self.templater(req)
180 180 ctype = tmpl('mimetype', encoding=self.encoding)
181 181 ctype = templater.stringify(ctype)
182 182
183 183 if cmd == '':
184 184 req.form['cmd'] = [tmpl.cache['default']]
185 185 cmd = req.form['cmd'][0]
186 186
187 187 if cmd not in webcommands.__all__:
188 188 msg = 'no such method: %s' % cmd
189 189 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
190 190 elif cmd == 'file' and 'raw' in req.form.get('style', []):
191 191 self.ctype = ctype
192 192 content = webcommands.rawfile(self, req, tmpl)
193 193 else:
194 194 content = getattr(webcommands, cmd)(self, req, tmpl)
195 195 req.respond(HTTP_OK, ctype)
196 196
197 197 req.write(content)
198 198 del tmpl
199 199
200 200 except revlog.LookupError, err:
201 201 req.respond(HTTP_NOT_FOUND, ctype)
202 202 msg = str(err)
203 203 if 'manifest' not in msg:
204 204 msg = 'revision not found: %s' % err.name
205 205 req.write(tmpl('error', error=msg))
206 206 except (RepoError, revlog.RevlogError), inst:
207 207 req.respond(HTTP_SERVER_ERROR, ctype)
208 208 req.write(tmpl('error', error=str(inst)))
209 209 except ErrorResponse, inst:
210 210 req.respond(inst.code, ctype)
211 211 req.write(tmpl('error', error=inst.message))
212 212
213 213 def templater(self, req):
214 214
215 215 # determine scheme, port and server name
216 216 # this is needed to create absolute urls
217 217
218 218 proto = req.env.get('wsgi.url_scheme')
219 219 if proto == 'https':
220 220 proto = 'https'
221 221 default_port = "443"
222 222 else:
223 223 proto = 'http'
224 224 default_port = "80"
225 225
226 226 port = req.env["SERVER_PORT"]
227 227 port = port != default_port and (":" + port) or ""
228 228 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
229 229 staticurl = self.config("web", "staticurl") or req.url + 'static/'
230 230 if not staticurl.endswith('/'):
231 231 staticurl += '/'
232 232
233 233 # some functions for the templater
234 234
235 235 def header(**map):
236 236 yield tmpl('header', encoding=self.encoding, **map)
237 237
238 238 def footer(**map):
239 239 yield tmpl("footer", **map)
240 240
241 241 def motd(**map):
242 242 yield self.config("web", "motd", "")
243 243
244 244 def sessionvars(**map):
245 245 fields = []
246 246 if 'style' in req.form:
247 247 style = req.form['style'][0]
248 248 if style != self.config('web', 'style', ''):
249 249 fields.append(('style', style))
250 250
251 251 separator = req.url[-1] == '?' and ';' or '?'
252 252 for name, value in fields:
253 253 yield dict(name=name, value=value, separator=separator)
254 254 separator = ';'
255 255
256 256 # figure out which style to use
257 257
258 258 style = self.config("web", "style", "")
259 259 if 'style' in req.form:
260 260 style = req.form['style'][0]
261 261 mapfile = style_map(self.templatepath, style)
262 262
263 263 if not self.reponame:
264 264 self.reponame = (self.config("web", "name")
265 265 or req.env.get('REPO_NAME')
266 266 or req.url.strip('/') or self.repo.root)
267 267
268 268 # create the templater
269 269
270 270 tmpl = templater.templater(mapfile, templatefilters.filters,
271 271 defaults={"url": req.url,
272 272 "staticurl": staticurl,
273 273 "urlbase": urlbase,
274 274 "repo": self.reponame,
275 275 "header": header,
276 276 "footer": footer,
277 277 "motd": motd,
278 278 "sessionvars": sessionvars
279 279 })
280 280 return tmpl
281 281
282 282 def archivelist(self, nodeid):
283 283 allowed = self.configlist("web", "allow_archive")
284 284 for i, spec in self.archive_specs.iteritems():
285 285 if i in allowed or self.configbool("web", "allow" + i):
286 286 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
287 287
288 288 def listfilediffs(self, tmpl, files, changeset):
289 289 for f in files[:self.maxfiles]:
290 290 yield tmpl("filedifflink", node=hex(changeset), file=f)
291 291 if len(files) > self.maxfiles:
292 292 yield tmpl("fileellipses")
293 293
294 294 def diff(self, tmpl, node1, node2, files):
295 295 def filterfiles(filters, files):
296 296 l = [x for x in files if x in filters]
297 297
298 298 for t in filters:
299 299 if t and t[-1] != os.sep:
300 300 t += os.sep
301 301 l += [x for x in files if x.startswith(t)]
302 302 return l
303 303
304 304 parity = paritygen(self.stripecount)
305 305 def diffblock(diff, f, fn):
306 306 yield tmpl("diffblock",
307 307 lines=prettyprintlines(diff),
308 308 parity=parity.next(),
309 309 file=f,
310 310 filenode=hex(fn or nullid))
311 311
312 312 blockcount = countgen()
313 313 def prettyprintlines(diff):
314 314 blockno = blockcount.next()
315 315 for lineno, l in enumerate(diff.splitlines(1)):
316 316 if blockno == 0:
317 317 lineno = lineno + 1
318 318 else:
319 319 lineno = "%d.%d" % (blockno, lineno + 1)
320 320 if l.startswith('+'):
321 321 ltype = "difflineplus"
322 322 elif l.startswith('-'):
323 323 ltype = "difflineminus"
324 324 elif l.startswith('@'):
325 325 ltype = "difflineat"
326 326 else:
327 327 ltype = "diffline"
328 328 yield tmpl(ltype,
329 329 line=l,
330 330 lineid="l%s" % lineno,
331 331 linenumber="% 8s" % lineno)
332 332
333 333 r = self.repo
334 334 c1 = r.changectx(node1)
335 335 c2 = r.changectx(node2)
336 336 date1 = util.datestr(c1.date())
337 337 date2 = util.datestr(c2.date())
338 338
339 339 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
340 340 if files:
341 341 modified, added, removed = map(lambda x: filterfiles(files, x),
342 342 (modified, added, removed))
343 343
344 344 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
345 345 for f in modified:
346 346 to = c1.filectx(f).data()
347 347 tn = c2.filectx(f).data()
348 348 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
349 349 opts=diffopts), f, tn)
350 350 for f in added:
351 351 to = None
352 352 tn = c2.filectx(f).data()
353 353 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
354 354 opts=diffopts), f, tn)
355 355 for f in removed:
356 356 to = c1.filectx(f).data()
357 357 tn = None
358 358 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
359 359 opts=diffopts), f, tn)
360 360
361 361 archive_specs = {
362 362 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
363 363 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
364 364 'zip': ('application/zip', 'zip', '.zip', None),
365 365 }
366 366
367 367 def check_perm(self, req, op, default):
368 368 '''check permission for operation based on user auth.
369 369 return true if op allowed, else false.
370 370 default is policy to use if no config given.'''
371 371
372 372 user = req.env.get('REMOTE_USER')
373 373
374 374 deny = self.configlist('web', 'deny_' + op)
375 375 if deny and (not user or deny == ['*'] or user in deny):
376 376 return False
377 377
378 378 allow = self.configlist('web', 'allow_' + op)
379 379 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,288 +1,290 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 gettext as _
11 11 from mercurial.repo import RepoError
12 12 from mercurial import ui, hg, util, templater, templatefilters
13 13 from common import ErrorResponse, get_mtime, staticfile, style_map, 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 18 # This is a stopgap
19 19 class hgwebdir(object):
20 20 def __init__(self, config, parentui=None):
21 21 def cleannames(items):
22 22 return [(util.pconvert(name).strip('/'), path)
23 23 for name, path in items]
24 24
25 25 self.parentui = parentui or ui.ui(report_untrusted=False,
26 26 interactive = False)
27 27 self.motd = None
28 28 self.style = None
29 29 self.stripecount = None
30 30 self.repos_sorted = ('name', False)
31 31 self._baseurl = None
32 32 if isinstance(config, (list, tuple)):
33 33 self.repos = cleannames(config)
34 34 self.repos_sorted = ('', False)
35 35 elif isinstance(config, dict):
36 36 self.repos = cleannames(config.items())
37 37 self.repos.sort()
38 38 else:
39 39 if isinstance(config, util.configparser):
40 40 cp = config
41 41 else:
42 42 cp = util.configparser()
43 43 cp.read(config)
44 44 self.repos = []
45 45 if cp.has_section('web'):
46 46 if cp.has_option('web', 'motd'):
47 47 self.motd = cp.get('web', 'motd')
48 48 if cp.has_option('web', 'style'):
49 49 self.style = cp.get('web', 'style')
50 50 if cp.has_option('web', 'stripes'):
51 51 self.stripecount = int(cp.get('web', 'stripes'))
52 52 if cp.has_option('web', 'baseurl'):
53 53 self._baseurl = cp.get('web', 'baseurl')
54 54 if cp.has_section('paths'):
55 55 self.repos.extend(cleannames(cp.items('paths')))
56 56 if cp.has_section('collections'):
57 57 for prefix, root in cp.items('collections'):
58 58 for path in util.walkrepos(root, followsym=True):
59 59 repo = os.path.normpath(path)
60 60 name = repo
61 61 if name.startswith(prefix):
62 62 name = name[len(prefix):]
63 63 self.repos.append((name.lstrip(os.sep), repo))
64 64 self.repos.sort()
65 65
66 66 def run(self):
67 67 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
68 68 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
69 69 import mercurial.hgweb.wsgicgi as wsgicgi
70 70 wsgicgi.launch(self)
71 71
72 72 def __call__(self, env, respond):
73 73 req = wsgirequest(env, respond)
74 74 self.run_wsgi(req)
75 75 return req
76 76
77 77 def run_wsgi(self, req):
78 78
79 79 try:
80 80 try:
81 81
82 82 virtual = req.env.get("PATH_INFO", "").strip('/')
83 83 tmpl = self.templater(req)
84 84 ctype = tmpl('mimetype', encoding=util._encoding)
85 85 ctype = templater.stringify(ctype)
86 86
87 87 # a static file
88 88 if virtual.startswith('static/') or 'static' in req.form:
89 89 static = os.path.join(templater.templatepath(), 'static')
90 90 if virtual.startswith('static/'):
91 91 fname = virtual[7:]
92 92 else:
93 93 fname = req.form['static'][0]
94 94 req.write(staticfile(static, fname, req))
95 95 return
96 96
97 97 # top-level index
98 98 elif not virtual:
99 99 req.respond(HTTP_OK, ctype)
100 100 req.write(self.makeindex(req, tmpl))
101 101 return
102 102
103 103 # nested indexes and hgwebs
104 104
105 105 repos = dict(self.repos)
106 106 while virtual:
107 107 real = repos.get(virtual)
108 108 if real:
109 109 req.env['REPO_NAME'] = virtual
110 110 try:
111 111 repo = hg.repository(self.parentui, real)
112 112 hgweb(repo).run_wsgi(req)
113 113 return
114 114 except IOError, inst:
115 115 msg = inst.strerror
116 116 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
117 117 except RepoError, inst:
118 118 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
119 119
120 120 # browse subdirectories
121 121 subdir = virtual + '/'
122 122 if [r for r in repos if r.startswith(subdir)]:
123 123 req.respond(HTTP_OK, ctype)
124 124 req.write(self.makeindex(req, tmpl, subdir))
125 125 return
126 126
127 127 up = virtual.rfind('/')
128 128 if up < 0:
129 129 break
130 130 virtual = virtual[:up]
131 131
132 132 # prefixes not found
133 133 req.respond(HTTP_NOT_FOUND, ctype)
134 134 req.write(tmpl("notfound", repo=virtual))
135 135
136 136 except ErrorResponse, err:
137 137 req.respond(err.code, ctype)
138 138 req.write(tmpl('error', error=err.message or ''))
139 139 finally:
140 140 tmpl = None
141 141
142 142 def makeindex(self, req, tmpl, subdir=""):
143 143
144 144 def archivelist(ui, nodeid, url):
145 145 allowed = ui.configlist("web", "allow_archive", untrusted=True)
146 146 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
147 147 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
148 148 untrusted=True):
149 149 yield {"type" : i[0], "extension": i[1],
150 150 "node": nodeid, "url": url}
151 151
152 152 def entries(sortcolumn="", descending=False, subdir="", **map):
153 153 def sessionvars(**map):
154 154 fields = []
155 155 if 'style' in req.form:
156 156 style = req.form['style'][0]
157 157 if style != get('web', 'style', ''):
158 158 fields.append(('style', style))
159 159
160 160 separator = url[-1] == '?' and ';' or '?'
161 161 for name, value in fields:
162 162 yield dict(name=name, value=value, separator=separator)
163 163 separator = ';'
164 164
165 165 rows = []
166 166 parity = paritygen(self.stripecount)
167 167 for name, path in self.repos:
168 168 if not name.startswith(subdir):
169 169 continue
170 170 name = name[len(subdir):]
171 171
172 172 u = ui.ui(parentui=self.parentui)
173 173 try:
174 174 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
175 175 except Exception, e:
176 176 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
177 177 continue
178 178 def get(section, name, default=None):
179 179 return u.config(section, name, default, untrusted=True)
180 180
181 181 if u.configbool("web", "hidden", untrusted=True):
182 182 continue
183 183
184 parts = [req.env['PATH_INFO'].rstrip('/'), name]
184 parts = [name]
185 if 'PATH_INFO' in req.env:
186 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
185 187 if req.env['SCRIPT_NAME']:
186 188 parts.insert(0, req.env['SCRIPT_NAME'])
187 189 url = ('/'.join(parts).replace("//", "/")) + '/'
188 190
189 191 # update time with local timezone
190 192 try:
191 193 d = (get_mtime(path), util.makedate()[1])
192 194 except OSError:
193 195 continue
194 196
195 197 contact = get_contact(get)
196 198 description = get("web", "description", "")
197 199 name = get("web", "name", name)
198 200 row = dict(contact=contact or "unknown",
199 201 contact_sort=contact.upper() or "unknown",
200 202 name=name,
201 203 name_sort=name,
202 204 url=url,
203 205 description=description or "unknown",
204 206 description_sort=description.upper() or "unknown",
205 207 lastchange=d,
206 208 lastchange_sort=d[1]-d[0],
207 209 sessionvars=sessionvars,
208 210 archives=archivelist(u, "tip", url))
209 211 if (not sortcolumn
210 212 or (sortcolumn, descending) == self.repos_sorted):
211 213 # fast path for unsorted output
212 214 row['parity'] = parity.next()
213 215 yield row
214 216 else:
215 217 rows.append((row["%s_sort" % sortcolumn], row))
216 218 if rows:
217 219 rows.sort()
218 220 if descending:
219 221 rows.reverse()
220 222 for key, row in rows:
221 223 row['parity'] = parity.next()
222 224 yield row
223 225
224 226 sortable = ["name", "description", "contact", "lastchange"]
225 227 sortcolumn, descending = self.repos_sorted
226 228 if 'sort' in req.form:
227 229 sortcolumn = req.form['sort'][0]
228 230 descending = sortcolumn.startswith('-')
229 231 if descending:
230 232 sortcolumn = sortcolumn[1:]
231 233 if sortcolumn not in sortable:
232 234 sortcolumn = ""
233 235
234 236 sort = [("sort_%s" % column,
235 237 "%s%s" % ((not descending and column == sortcolumn)
236 238 and "-" or "", column))
237 239 for column in sortable]
238 240
239 241 if self._baseurl is not None:
240 242 req.env['SCRIPT_NAME'] = self._baseurl
241 243
242 244 return tmpl("index", entries=entries, subdir=subdir,
243 245 sortcolumn=sortcolumn, descending=descending,
244 246 **dict(sort))
245 247
246 248 def templater(self, req):
247 249
248 250 def header(**map):
249 251 yield tmpl('header', encoding=util._encoding, **map)
250 252
251 253 def footer(**map):
252 254 yield tmpl("footer", **map)
253 255
254 256 def motd(**map):
255 257 if self.motd is not None:
256 258 yield self.motd
257 259 else:
258 260 yield config('web', 'motd', '')
259 261
260 262 def config(section, name, default=None, untrusted=True):
261 263 return self.parentui.config(section, name, default, untrusted)
262 264
263 265 if self._baseurl is not None:
264 266 req.env['SCRIPT_NAME'] = self._baseurl
265 267
266 268 url = req.env.get('SCRIPT_NAME', '')
267 269 if not url.endswith('/'):
268 270 url += '/'
269 271
270 272 staticurl = config('web', 'staticurl') or url + 'static/'
271 273 if not staticurl.endswith('/'):
272 274 staticurl += '/'
273 275
274 276 style = self.style
275 277 if style is None:
276 278 style = config('web', 'style', '')
277 279 if 'style' in req.form:
278 280 style = req.form['style'][0]
279 281 if self.stripecount is None:
280 282 self.stripecount = int(config('web', 'stripes', 1))
281 283 mapfile = style_map(templater.templatepath(), style)
282 284 tmpl = templater.templater(mapfile, templatefilters.filters,
283 285 defaults={"header": header,
284 286 "footer": footer,
285 287 "motd": motd,
286 288 "url": url,
287 289 "staticurl": staticurl})
288 290 return tmpl
General Comments 0
You need to be logged in to leave comments. Login now