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