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