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