##// END OF EJS Templates
merge another backout
Dirkjan Ochtman -
r6797:8909070f merge default
parent child Browse files
Show More
@@ -1,376 +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
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 ''.join(content),
172 return []
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 return ''.join(tmpl('error', error=msg)),
179 req.write(tmpl('error', error=msg))
180 return []
180 181 except (RepoError, revlog.RevlogError), inst:
181 182 req.respond(HTTP_SERVER_ERROR, ctype)
182 return ''.join(tmpl('error', error=str(inst))),
183 req.write(tmpl('error', error=str(inst)))
184 return []
183 185 except ErrorResponse, inst:
184 186 req.respond(inst.code, ctype)
185 return ''.join(tmpl('error', error=inst.message)),
187 req.write(tmpl('error', error=inst.message))
188 return []
186 189
187 190 def templater(self, req):
188 191
189 192 # determine scheme, port and server name
190 193 # this is needed to create absolute urls
191 194
192 195 proto = req.env.get('wsgi.url_scheme')
193 196 if proto == 'https':
194 197 proto = 'https'
195 198 default_port = "443"
196 199 else:
197 200 proto = 'http'
198 201 default_port = "80"
199 202
200 203 port = req.env["SERVER_PORT"]
201 204 port = port != default_port and (":" + port) or ""
202 205 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
203 206 staticurl = self.config("web", "staticurl") or req.url + 'static/'
204 207 if not staticurl.endswith('/'):
205 208 staticurl += '/'
206 209
207 210 # some functions for the templater
208 211
209 212 def header(**map):
210 213 yield tmpl('header', encoding=self.encoding, **map)
211 214
212 215 def footer(**map):
213 216 yield tmpl("footer", **map)
214 217
215 218 def motd(**map):
216 219 yield self.config("web", "motd", "")
217 220
218 221 def sessionvars(**map):
219 222 fields = []
220 223 if 'style' in req.form:
221 224 style = req.form['style'][0]
222 225 if style != self.config('web', 'style', ''):
223 226 fields.append(('style', style))
224 227
225 228 separator = req.url[-1] == '?' and ';' or '?'
226 229 for name, value in fields:
227 230 yield dict(name=name, value=value, separator=separator)
228 231 separator = ';'
229 232
230 233 # figure out which style to use
231 234
232 235 style = self.config("web", "style", "")
233 236 if 'style' in req.form:
234 237 style = req.form['style'][0]
235 238 mapfile = style_map(self.templatepath, style)
236 239
237 240 if not self.reponame:
238 241 self.reponame = (self.config("web", "name")
239 242 or req.env.get('REPO_NAME')
240 243 or req.url.strip('/') or self.repo.root)
241 244
242 245 # create the templater
243 246
244 247 tmpl = templater.templater(mapfile, templatefilters.filters,
245 248 defaults={"url": req.url,
246 249 "staticurl": staticurl,
247 250 "urlbase": urlbase,
248 251 "repo": self.reponame,
249 252 "header": header,
250 253 "footer": footer,
251 254 "motd": motd,
252 255 "sessionvars": sessionvars
253 256 })
254 257 return tmpl
255 258
256 259 def archivelist(self, nodeid):
257 260 allowed = self.configlist("web", "allow_archive")
258 261 for i, spec in self.archive_specs.iteritems():
259 262 if i in allowed or self.configbool("web", "allow" + i):
260 263 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
261 264
262 265 def listfilediffs(self, tmpl, files, changeset):
263 266 for f in files[:self.maxfiles]:
264 267 yield tmpl("filedifflink", node=hex(changeset), file=f)
265 268 if len(files) > self.maxfiles:
266 269 yield tmpl("fileellipses")
267 270
268 271 def diff(self, tmpl, node1, node2, files):
269 272 def filterfiles(filters, files):
270 273 l = [x for x in files if x in filters]
271 274
272 275 for t in filters:
273 276 if t and t[-1] != os.sep:
274 277 t += os.sep
275 278 l += [x for x in files if x.startswith(t)]
276 279 return l
277 280
278 281 parity = paritygen(self.stripecount)
279 282 def diffblock(diff, f, fn):
280 283 yield tmpl("diffblock",
281 284 lines=prettyprintlines(diff),
282 285 parity=parity.next(),
283 286 file=f,
284 287 filenode=hex(fn or nullid))
285 288
286 289 blockcount = countgen()
287 290 def prettyprintlines(diff):
288 291 blockno = blockcount.next()
289 292 for lineno, l in enumerate(diff.splitlines(1)):
290 293 if blockno == 0:
291 294 lineno = lineno + 1
292 295 else:
293 296 lineno = "%d.%d" % (blockno, lineno + 1)
294 297 if l.startswith('+'):
295 298 ltype = "difflineplus"
296 299 elif l.startswith('-'):
297 300 ltype = "difflineminus"
298 301 elif l.startswith('@'):
299 302 ltype = "difflineat"
300 303 else:
301 304 ltype = "diffline"
302 305 yield tmpl(ltype,
303 306 line=l,
304 307 lineid="l%s" % lineno,
305 308 linenumber="% 8s" % lineno)
306 309
307 310 r = self.repo
308 311 c1 = r[node1]
309 312 c2 = r[node2]
310 313 date1 = util.datestr(c1.date())
311 314 date2 = util.datestr(c2.date())
312 315
313 316 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
314 317 if files:
315 318 modified, added, removed = map(lambda x: filterfiles(files, x),
316 319 (modified, added, removed))
317 320
318 321 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
319 322 for f in modified:
320 323 to = c1.filectx(f).data()
321 324 tn = c2.filectx(f).data()
322 325 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
323 326 opts=diffopts), f, tn)
324 327 for f in added:
325 328 to = None
326 329 tn = c2.filectx(f).data()
327 330 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
328 331 opts=diffopts), f, tn)
329 332 for f in removed:
330 333 to = c1.filectx(f).data()
331 334 tn = None
332 335 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
333 336 opts=diffopts), f, tn)
334 337
335 338 archive_specs = {
336 339 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
337 340 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
338 341 'zip': ('application/zip', 'zip', '.zip', None),
339 342 }
340 343
341 344 def check_perm(self, req, op):
342 345 '''Check permission for operation based on request data (including
343 346 authentication info. Return true if op allowed, else false.'''
344 347
345 348 def error(status, message):
346 349 req.respond(status, protocol.HGTYPE)
347 350 req.write('0\n%s\n' % message)
348 351
349 352 if op == 'pull':
350 353 return self.allowpull
351 354
352 355 # enforce that you can only push using POST requests
353 356 if req.env['REQUEST_METHOD'] != 'POST':
354 357 error('405 Method Not Allowed', 'push requires POST request')
355 358 return False
356 359
357 360 # require ssl by default for pushing, auth info cannot be sniffed
358 361 # and replayed
359 362 scheme = req.env.get('wsgi.url_scheme')
360 363 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
361 364 error(HTTP_OK, 'ssl required')
362 365 return False
363 366
364 367 user = req.env.get('REMOTE_USER')
365 368
366 369 deny = self.configlist('web', 'deny_push')
367 370 if deny and (not user or deny == ['*'] or user in deny):
368 371 error('401 Unauthorized', 'push not authorized')
369 372 return False
370 373
371 374 allow = self.configlist('web', 'allow_push')
372 375 result = allow and (allow == ['*'] or user in allow)
373 376 if not result:
374 377 error('401 Unauthorized', 'push not authorized')
375 378
376 379 return result
@@ -1,284 +1,289 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 73 return self.run_wsgi(req)
74 74
75 75 def run_wsgi(self, req):
76 76
77 77 try:
78 78 try:
79 79
80 80 virtual = req.env.get("PATH_INFO", "").strip('/')
81 81 tmpl = self.templater(req)
82 82 ctype = tmpl('mimetype', encoding=util._encoding)
83 83 ctype = templater.stringify(ctype)
84 84
85 85 # a static file
86 86 if virtual.startswith('static/') or 'static' in req.form:
87 87 static = os.path.join(templater.templatepath(), 'static')
88 88 if virtual.startswith('static/'):
89 89 fname = virtual[7:]
90 90 else:
91 91 fname = req.form['static'][0]
92 return staticfile(static, fname, req),
92 req.write(staticfile(static, fname, req))
93 return []
93 94
94 95 # top-level index
95 96 elif not virtual:
96 97 req.respond(HTTP_OK, ctype)
97 return ''.join(self.makeindex(req, tmpl)),
98 req.write(self.makeindex(req, tmpl))
99 return []
98 100
99 101 # nested indexes and hgwebs
100 102
101 103 repos = dict(self.repos)
102 104 while virtual:
103 105 real = repos.get(virtual)
104 106 if real:
105 107 req.env['REPO_NAME'] = virtual
106 108 try:
107 109 repo = hg.repository(self.parentui, real)
108 110 return hgweb(repo).run_wsgi(req)
109 111 except IOError, inst:
110 112 msg = inst.strerror
111 113 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
112 114 except RepoError, inst:
113 115 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
114 116
115 117 # browse subdirectories
116 118 subdir = virtual + '/'
117 119 if [r for r in repos if r.startswith(subdir)]:
118 120 req.respond(HTTP_OK, ctype)
119 return ''.join(self.makeindex(req, tmpl, subdir)),
121 req.write(self.makeindex(req, tmpl, subdir))
122 return []
120 123
121 124 up = virtual.rfind('/')
122 125 if up < 0:
123 126 break
124 127 virtual = virtual[:up]
125 128
126 129 # prefixes not found
127 130 req.respond(HTTP_NOT_FOUND, ctype)
128 return ''.join(tmpl("notfound", repo=virtual)),
131 req.write(tmpl("notfound", repo=virtual))
132 return []
129 133
130 134 except ErrorResponse, err:
131 135 req.respond(err.code, ctype)
132 return ''.join(tmpl('error', error=err.message or '')),
136 req.write(tmpl('error', error=err.message or ''))
137 return []
133 138 finally:
134 139 tmpl = None
135 140
136 141 def makeindex(self, req, tmpl, subdir=""):
137 142
138 143 def archivelist(ui, nodeid, url):
139 144 allowed = ui.configlist("web", "allow_archive", untrusted=True)
140 145 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
141 146 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
142 147 untrusted=True):
143 148 yield {"type" : i[0], "extension": i[1],
144 149 "node": nodeid, "url": url}
145 150
146 151 def entries(sortcolumn="", descending=False, subdir="", **map):
147 152 def sessionvars(**map):
148 153 fields = []
149 154 if 'style' in req.form:
150 155 style = req.form['style'][0]
151 156 if style != get('web', 'style', ''):
152 157 fields.append(('style', style))
153 158
154 159 separator = url[-1] == '?' and ';' or '?'
155 160 for name, value in fields:
156 161 yield dict(name=name, value=value, separator=separator)
157 162 separator = ';'
158 163
159 164 rows = []
160 165 parity = paritygen(self.stripecount)
161 166 for name, path in self.repos:
162 167 if not name.startswith(subdir):
163 168 continue
164 169 name = name[len(subdir):]
165 170
166 171 u = ui.ui(parentui=self.parentui)
167 172 try:
168 173 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
169 174 except Exception, e:
170 175 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
171 176 continue
172 177 def get(section, name, default=None):
173 178 return u.config(section, name, default, untrusted=True)
174 179
175 180 if u.configbool("web", "hidden", untrusted=True):
176 181 continue
177 182
178 183 parts = [name]
179 184 if 'PATH_INFO' in req.env:
180 185 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
181 186 if req.env['SCRIPT_NAME']:
182 187 parts.insert(0, req.env['SCRIPT_NAME'])
183 188 url = ('/'.join(parts).replace("//", "/")) + '/'
184 189
185 190 # update time with local timezone
186 191 try:
187 192 d = (get_mtime(path), util.makedate()[1])
188 193 except OSError:
189 194 continue
190 195
191 196 contact = get_contact(get)
192 197 description = get("web", "description", "")
193 198 name = get("web", "name", name)
194 199 row = dict(contact=contact or "unknown",
195 200 contact_sort=contact.upper() or "unknown",
196 201 name=name,
197 202 name_sort=name,
198 203 url=url,
199 204 description=description or "unknown",
200 205 description_sort=description.upper() or "unknown",
201 206 lastchange=d,
202 207 lastchange_sort=d[1]-d[0],
203 208 sessionvars=sessionvars,
204 209 archives=archivelist(u, "tip", url))
205 210 if (not sortcolumn
206 211 or (sortcolumn, descending) == self.repos_sorted):
207 212 # fast path for unsorted output
208 213 row['parity'] = parity.next()
209 214 yield row
210 215 else:
211 216 rows.append((row["%s_sort" % sortcolumn], row))
212 217 if rows:
213 218 rows.sort()
214 219 if descending:
215 220 rows.reverse()
216 221 for key, row in rows:
217 222 row['parity'] = parity.next()
218 223 yield row
219 224
220 225 sortable = ["name", "description", "contact", "lastchange"]
221 226 sortcolumn, descending = self.repos_sorted
222 227 if 'sort' in req.form:
223 228 sortcolumn = req.form['sort'][0]
224 229 descending = sortcolumn.startswith('-')
225 230 if descending:
226 231 sortcolumn = sortcolumn[1:]
227 232 if sortcolumn not in sortable:
228 233 sortcolumn = ""
229 234
230 235 sort = [("sort_%s" % column,
231 236 "%s%s" % ((not descending and column == sortcolumn)
232 237 and "-" or "", column))
233 238 for column in sortable]
234 239
235 240 if self._baseurl is not None:
236 241 req.env['SCRIPT_NAME'] = self._baseurl
237 242
238 243 return tmpl("index", entries=entries, subdir=subdir,
239 244 sortcolumn=sortcolumn, descending=descending,
240 245 **dict(sort))
241 246
242 247 def templater(self, req):
243 248
244 249 def header(**map):
245 250 yield tmpl('header', encoding=util._encoding, **map)
246 251
247 252 def footer(**map):
248 253 yield tmpl("footer", **map)
249 254
250 255 def motd(**map):
251 256 if self.motd is not None:
252 257 yield self.motd
253 258 else:
254 259 yield config('web', 'motd', '')
255 260
256 261 def config(section, name, default=None, untrusted=True):
257 262 return self.parentui.config(section, name, default, untrusted)
258 263
259 264 if self._baseurl is not None:
260 265 req.env['SCRIPT_NAME'] = self._baseurl
261 266
262 267 url = req.env.get('SCRIPT_NAME', '')
263 268 if not url.endswith('/'):
264 269 url += '/'
265 270
266 271 staticurl = config('web', 'staticurl') or url + 'static/'
267 272 if not staticurl.endswith('/'):
268 273 staticurl += '/'
269 274
270 275 style = self.style
271 276 if style is None:
272 277 style = config('web', 'style', '')
273 278 if 'style' in req.form:
274 279 style = req.form['style'][0]
275 280 if self.stripecount is None:
276 281 self.stripecount = int(config('web', 'stripes', 1))
277 282 mapfile = style_map(templater.templatepath(), style)
278 283 tmpl = templater.templater(mapfile, templatefilters.filters,
279 284 defaults={"header": header,
280 285 "footer": footer,
281 286 "motd": motd,
282 287 "url": url,
283 288 "staticurl": staticurl})
284 289 return tmpl
@@ -1,61 +1,59 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 content = hgweb('.', name = 'repo')(env, startrsp)
47 sys.stdout.write(output.getvalue())
48 sys.stdout.write(''.join(content))
46 hgweb('.', name = 'repo')(env, startrsp)
47 print output.getvalue()
49 48 print '---- ERRORS'
50 49 print errors.getvalue()
51 50
52 51 output = StringIO()
53 52 env['QUERY_STRING'] = 'style=raw'
54 content = hgwebdir({'repo': '.'})(env, startrsp)
55 sys.stdout.write(output.getvalue())
56 sys.stdout.write(''.join(content))
53 hgwebdir({'repo': '.'})(env, startrsp)
54 print output.getvalue()
57 55 print '---- ERRORS'
58 56 print errors.getvalue()
59 57 EOF
60 58
61 59 python request.py | sed "s/http:\/\/127\.0\.0\.1:[0-9]*\//http:\/\/127.0.0.1\//"
@@ -1,48 +1,50 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
38 39 ---- ERRORS
39 40
40 41 ---- HEADERS
41 42 200 Script output follows
42 43 ---- DATA
43 44 [('Content-Type', 'text/plain; charset=ascii')]
44 45
45 46 repo/
46 47
48
47 49 ---- ERRORS
48 50
@@ -1,81 +1,77 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 content = hgweb('.', name = 'repo')(env, startrsp)
48 sys.stdout.write(output.getvalue())
49 sys.stdout.write(''.join(content))
47 hgweb('.', name = 'repo')(env, startrsp)
48 print output.getvalue()
50 49 print '---- ERRORS'
51 50 print errors.getvalue()
52 51
53 52 output = StringIO()
54 53 env['PATH_INFO'] = '/file/tip/'
55 54 env['QUERY_STRING'] = 'style=raw'
56 content = hgweb('.', name = 'repo')(env, startrsp)
57 sys.stdout.write(output.getvalue())
58 sys.stdout.write(''.join(content))
55 hgweb('.', name = 'repo')(env, startrsp)
56 print output.getvalue()
59 57 print '---- ERRORS'
60 58 print errors.getvalue()
61 59
62 60 output = StringIO()
63 61 env['PATH_INFO'] = '/'
64 62 env['QUERY_STRING'] = 'style=raw'
65 content = hgwebdir({'repo': '.'})(env, startrsp)
66 sys.stdout.write(output.getvalue())
67 sys.stdout.write(''.join(content))
63 hgwebdir({'repo': '.'})(env, startrsp)
64 print output.getvalue()
68 65 print '---- ERRORS'
69 66 print errors.getvalue()
70 67
71 68 output = StringIO()
72 69 env['PATH_INFO'] = '/repo/file/tip/'
73 70 env['QUERY_STRING'] = 'style=raw'
74 content = hgwebdir({'repo': '.'})(env, startrsp)
75 sys.stdout.write(output.getvalue())
76 sys.stdout.write(''.join(content))
71 hgwebdir({'repo': '.'})(env, startrsp)
72 print output.getvalue()
77 73 print '---- ERRORS'
78 74 print errors.getvalue()
79 75 EOF
80 76
81 77 python request.py | sed "s/http:\/\/127\.0\.0\.1:[0-9]*\//http:\/\/127.0.0.1\//"
@@ -1,68 +1,72 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
38 39 ---- ERRORS
39 40
40 41 ---- HEADERS
41 42 200 Script output follows
42 43 ---- DATA
43 44 [('Content-Type', 'text/plain; charset=ascii')]
44 45
45 46 -rw-r--r-- 4 bar
46 47
47 48
49
48 50 ---- ERRORS
49 51
50 52 ---- HEADERS
51 53 200 Script output follows
52 54 ---- DATA
53 55 [('Content-Type', 'text/plain; charset=ascii')]
54 56
55 57 /repo/
56 58
59
57 60 ---- ERRORS
58 61
59 62 ---- HEADERS
60 63 200 Script output follows
61 64 ---- DATA
62 65 [('Content-Type', 'text/plain; charset=ascii')]
63 66
64 67 -rw-r--r-- 4 bar
65 68
66 69
70
67 71 ---- ERRORS
68 72
General Comments 0
You need to be logged in to leave comments. Login now