Mercurial
view mercurial/hgweb/protocol.py @ 6265:be76e54570f0
Issue937: error messages from hooks not sent over HTTP.
Turns out that stderr - where ui.warn would send messages - was not
being proxied over the HTTP connection. stdout was, and it seems you
need both. (The streams are interleaved for readability.)
Tested on Ubuntu 7.10 with lighttpd on hgweb.cgi with HTTP Basic auth,
no SSL, using a changeset failing win32text.forbidcrlf.
| author | Jesse Glick <jesse.glick@sun.com> |
|---|---|
| date | Mon, 25 Feb 2008 09:55:57 -0500 |
| parents | e75aab656f46 |
| children | e29557d687c9 |
line source
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
8 import cStringIO, zlib, tempfile, errno, os, sys
9 from mercurial import util, streamclone
10 from mercurial.node import bin, hex
11 from mercurial import changegroup as changegroupmod
12 from common import HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 # __all__ is populated with the allowed commands. Be sure to add to it if
15 # you're adding a new command, or the new command won't work.
17 __all__ = [
18 'lookup', 'heads', 'branches', 'between', 'changegroup',
19 'changegroupsubset', 'capabilities', 'unbundle', 'stream_out',
20 ]
22 HGTYPE = 'application/mercurial-0.1'
24 def lookup(web, req):
25 try:
26 r = hex(web.repo.lookup(req.form['key'][0]))
27 success = 1
28 except Exception,inst:
29 r = str(inst)
30 success = 0
31 resp = "%s %s\n" % (success, r)
32 req.respond(HTTP_OK, HGTYPE, length=len(resp))
33 req.write(resp)
35 def heads(web, req):
36 resp = " ".join(map(hex, web.repo.heads())) + "\n"
37 req.respond(HTTP_OK, HGTYPE, length=len(resp))
38 req.write(resp)
40 def branches(web, req):
41 nodes = []
42 if 'nodes' in req.form:
43 nodes = map(bin, req.form['nodes'][0].split(" "))
44 resp = cStringIO.StringIO()
45 for b in web.repo.branches(nodes):
46 resp.write(" ".join(map(hex, b)) + "\n")
47 resp = resp.getvalue()
48 req.respond(HTTP_OK, HGTYPE, length=len(resp))
49 req.write(resp)
51 def between(web, req):
52 if 'pairs' in req.form:
53 pairs = [map(bin, p.split("-"))
54 for p in req.form['pairs'][0].split(" ")]
55 resp = cStringIO.StringIO()
56 for b in web.repo.between(pairs):
57 resp.write(" ".join(map(hex, b)) + "\n")
58 resp = resp.getvalue()
59 req.respond(HTTP_OK, HGTYPE, length=len(resp))
60 req.write(resp)
62 def changegroup(web, req):
63 req.respond(HTTP_OK, HGTYPE)
64 nodes = []
65 if not web.allowpull:
66 return
68 if 'roots' in req.form:
69 nodes = map(bin, req.form['roots'][0].split(" "))
71 z = zlib.compressobj()
72 f = web.repo.changegroup(nodes, 'serve')
73 while 1:
74 chunk = f.read(4096)
75 if not chunk:
76 break
77 req.write(z.compress(chunk))
79 req.write(z.flush())
81 def changegroupsubset(web, req):
82 req.respond(HTTP_OK, HGTYPE)
83 bases = []
84 heads = []
85 if not web.allowpull:
86 return
88 if 'bases' in req.form:
89 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
90 if 'heads' in req.form:
91 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
93 z = zlib.compressobj()
94 f = web.repo.changegroupsubset(bases, heads, 'serve')
95 while 1:
96 chunk = f.read(4096)
97 if not chunk:
98 break
99 req.write(z.compress(chunk))
101 req.write(z.flush())
103 def capabilities(web, req):
104 resp = ' '.join(web.capabilities())
105 req.respond(HTTP_OK, HGTYPE, length=len(resp))
106 req.write(resp)
108 def unbundle(web, req):
109 def bail(response, headers={}):
110 length = int(req.env['CONTENT_LENGTH'])
111 for s in util.filechunkiter(req, limit=length):
112 # drain incoming bundle, else client will not see
113 # response when run outside cgi script
114 pass
115 req.header(headers.items())
116 req.respond(HTTP_OK, HGTYPE)
117 req.write('0\n')
118 req.write(response)
120 # require ssl by default, auth info cannot be sniffed and
121 # replayed
122 ssl_req = web.configbool('web', 'push_ssl', True)
123 if ssl_req:
124 if req.env.get('wsgi.url_scheme') != 'https':
125 bail('ssl required\n')
126 return
127 proto = 'https'
128 else:
129 proto = 'http'
131 # do not allow push unless explicitly allowed
132 if not web.check_perm(req, 'push', False):
133 bail('push not authorized\n',
134 headers={'status': '401 Unauthorized'})
135 return
137 their_heads = req.form['heads'][0].split(' ')
139 def check_heads():
140 heads = map(hex, web.repo.heads())
141 return their_heads == [hex('force')] or their_heads == heads
143 # fail early if possible
144 if not check_heads():
145 bail('unsynced changes\n')
146 return
148 req.respond(HTTP_OK, HGTYPE)
150 # do not lock repo until all changegroup data is
151 # streamed. save to temporary file.
153 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
154 fp = os.fdopen(fd, 'wb+')
155 try:
156 length = int(req.env['CONTENT_LENGTH'])
157 for s in util.filechunkiter(req, limit=length):
158 fp.write(s)
160 try:
161 lock = web.repo.lock()
162 try:
163 if not check_heads():
164 req.write('0\n')
165 req.write('unsynced changes\n')
166 return
168 fp.seek(0)
169 header = fp.read(6)
170 if header.startswith('HG') and not header.startswith('HG10'):
171 raise ValueError('unknown bundle version')
172 elif header not in changegroupmod.bundletypes:
173 raise ValueError('unknown bundle compression type')
174 gen = changegroupmod.unbundle(header, fp)
176 # send addchangegroup output to client
178 oldio = sys.stdout, sys.stderr
179 sys.stderr = sys.stdout = cStringIO.StringIO()
181 try:
182 url = 'remote:%s:%s' % (proto,
183 req.env.get('REMOTE_HOST', ''))
184 try:
185 ret = web.repo.addchangegroup(gen, 'serve', url)
186 except util.Abort, inst:
187 sys.stdout.write("abort: %s\n" % inst)
188 ret = 0
189 finally:
190 val = sys.stdout.getvalue()
191 sys.stdout, sys.stderr = oldio
192 req.write('%d\n' % ret)
193 req.write(val)
194 finally:
195 del lock
196 except ValueError, inst:
197 req.write('0\n')
198 req.write(str(inst) + '\n')
199 except (OSError, IOError), inst:
200 req.write('0\n')
201 filename = getattr(inst, 'filename', '')
202 # Don't send our filesystem layout to the client
203 if filename.startswith(web.repo.root):
204 filename = filename[len(web.repo.root)+1:]
205 else:
206 filename = ''
207 error = getattr(inst, 'strerror', 'Unknown error')
208 if inst.errno == errno.ENOENT:
209 code = HTTP_NOT_FOUND
210 else:
211 code = HTTP_SERVER_ERROR
212 req.respond(code)
213 req.write('%s: %s\n' % (error, filename))
214 finally:
215 fp.close()
216 os.unlink(tempname)
218 def stream_out(web, req):
219 req.respond(HTTP_OK, HGTYPE)
220 streamclone.stream_out(web.repo, req, untrusted=True)
