##// END OF EJS Templates
py3: handle keyword arguments correctly in httppeer.py...
Pulkit Goyal -
r35360:98bc4c43 default
parent child Browse files
Show More
@@ -1,472 +1,473 b''
1 # httppeer.py - HTTP repository proxy classes for mercurial
1 # httppeer.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import io
12 import io
13 import os
13 import os
14 import socket
14 import socket
15 import struct
15 import struct
16 import tempfile
16 import tempfile
17
17
18 from .i18n import _
18 from .i18n import _
19 from .node import nullid
19 from .node import nullid
20 from . import (
20 from . import (
21 bundle2,
21 bundle2,
22 error,
22 error,
23 httpconnection,
23 httpconnection,
24 pycompat,
24 pycompat,
25 statichttprepo,
25 statichttprepo,
26 url,
26 url,
27 util,
27 util,
28 wireproto,
28 wireproto,
29 )
29 )
30
30
31 httplib = util.httplib
31 httplib = util.httplib
32 urlerr = util.urlerr
32 urlerr = util.urlerr
33 urlreq = util.urlreq
33 urlreq = util.urlreq
34
34
35 def encodevalueinheaders(value, header, limit):
35 def encodevalueinheaders(value, header, limit):
36 """Encode a string value into multiple HTTP headers.
36 """Encode a string value into multiple HTTP headers.
37
37
38 ``value`` will be encoded into 1 or more HTTP headers with the names
38 ``value`` will be encoded into 1 or more HTTP headers with the names
39 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
39 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
40 name + value will be at most ``limit`` bytes long.
40 name + value will be at most ``limit`` bytes long.
41
41
42 Returns an iterable of 2-tuples consisting of header names and
42 Returns an iterable of 2-tuples consisting of header names and
43 values as native strings.
43 values as native strings.
44 """
44 """
45 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
45 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
46 # not bytes. This function always takes bytes in as arguments.
46 # not bytes. This function always takes bytes in as arguments.
47 fmt = pycompat.strurl(header) + r'-%s'
47 fmt = pycompat.strurl(header) + r'-%s'
48 # Note: it is *NOT* a bug that the last bit here is a bytestring
48 # Note: it is *NOT* a bug that the last bit here is a bytestring
49 # and not a unicode: we're just getting the encoded length anyway,
49 # and not a unicode: we're just getting the encoded length anyway,
50 # and using an r-string to make it portable between Python 2 and 3
50 # and using an r-string to make it portable between Python 2 and 3
51 # doesn't work because then the \r is a literal backslash-r
51 # doesn't work because then the \r is a literal backslash-r
52 # instead of a carriage return.
52 # instead of a carriage return.
53 valuelen = limit - len(fmt % r'000') - len(': \r\n')
53 valuelen = limit - len(fmt % r'000') - len(': \r\n')
54 result = []
54 result = []
55
55
56 n = 0
56 n = 0
57 for i in xrange(0, len(value), valuelen):
57 for i in xrange(0, len(value), valuelen):
58 n += 1
58 n += 1
59 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
59 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
60
60
61 return result
61 return result
62
62
63 def _wraphttpresponse(resp):
63 def _wraphttpresponse(resp):
64 """Wrap an HTTPResponse with common error handlers.
64 """Wrap an HTTPResponse with common error handlers.
65
65
66 This ensures that any I/O from any consumer raises the appropriate
66 This ensures that any I/O from any consumer raises the appropriate
67 error and messaging.
67 error and messaging.
68 """
68 """
69 origread = resp.read
69 origread = resp.read
70
70
71 class readerproxy(resp.__class__):
71 class readerproxy(resp.__class__):
72 def read(self, size=None):
72 def read(self, size=None):
73 try:
73 try:
74 return origread(size)
74 return origread(size)
75 except httplib.IncompleteRead as e:
75 except httplib.IncompleteRead as e:
76 # e.expected is an integer if length known or None otherwise.
76 # e.expected is an integer if length known or None otherwise.
77 if e.expected:
77 if e.expected:
78 msg = _('HTTP request error (incomplete response; '
78 msg = _('HTTP request error (incomplete response; '
79 'expected %d bytes got %d)') % (e.expected,
79 'expected %d bytes got %d)') % (e.expected,
80 len(e.partial))
80 len(e.partial))
81 else:
81 else:
82 msg = _('HTTP request error (incomplete response)')
82 msg = _('HTTP request error (incomplete response)')
83
83
84 raise error.PeerTransportError(
84 raise error.PeerTransportError(
85 msg,
85 msg,
86 hint=_('this may be an intermittent network failure; '
86 hint=_('this may be an intermittent network failure; '
87 'if the error persists, consider contacting the '
87 'if the error persists, consider contacting the '
88 'network or server operator'))
88 'network or server operator'))
89 except httplib.HTTPException as e:
89 except httplib.HTTPException as e:
90 raise error.PeerTransportError(
90 raise error.PeerTransportError(
91 _('HTTP request error (%s)') % e,
91 _('HTTP request error (%s)') % e,
92 hint=_('this may be an intermittent network failure; '
92 hint=_('this may be an intermittent network failure; '
93 'if the error persists, consider contacting the '
93 'if the error persists, consider contacting the '
94 'network or server operator'))
94 'network or server operator'))
95
95
96 resp.__class__ = readerproxy
96 resp.__class__ = readerproxy
97
97
98 class _multifile(object):
98 class _multifile(object):
99 def __init__(self, *fileobjs):
99 def __init__(self, *fileobjs):
100 for f in fileobjs:
100 for f in fileobjs:
101 if not util.safehasattr(f, 'length'):
101 if not util.safehasattr(f, 'length'):
102 raise ValueError(
102 raise ValueError(
103 '_multifile only supports file objects that '
103 '_multifile only supports file objects that '
104 'have a length but this one does not:', type(f), f)
104 'have a length but this one does not:', type(f), f)
105 self._fileobjs = fileobjs
105 self._fileobjs = fileobjs
106 self._index = 0
106 self._index = 0
107
107
108 @property
108 @property
109 def length(self):
109 def length(self):
110 return sum(f.length for f in self._fileobjs)
110 return sum(f.length for f in self._fileobjs)
111
111
112 def read(self, amt=None):
112 def read(self, amt=None):
113 if amt <= 0:
113 if amt <= 0:
114 return ''.join(f.read() for f in self._fileobjs)
114 return ''.join(f.read() for f in self._fileobjs)
115 parts = []
115 parts = []
116 while amt and self._index < len(self._fileobjs):
116 while amt and self._index < len(self._fileobjs):
117 parts.append(self._fileobjs[self._index].read(amt))
117 parts.append(self._fileobjs[self._index].read(amt))
118 got = len(parts[-1])
118 got = len(parts[-1])
119 if got < amt:
119 if got < amt:
120 self._index += 1
120 self._index += 1
121 amt -= got
121 amt -= got
122 return ''.join(parts)
122 return ''.join(parts)
123
123
124 def seek(self, offset, whence=os.SEEK_SET):
124 def seek(self, offset, whence=os.SEEK_SET):
125 if whence != os.SEEK_SET:
125 if whence != os.SEEK_SET:
126 raise NotImplementedError(
126 raise NotImplementedError(
127 '_multifile does not support anything other'
127 '_multifile does not support anything other'
128 ' than os.SEEK_SET for whence on seek()')
128 ' than os.SEEK_SET for whence on seek()')
129 if offset != 0:
129 if offset != 0:
130 raise NotImplementedError(
130 raise NotImplementedError(
131 '_multifile only supports seeking to start, but that '
131 '_multifile only supports seeking to start, but that '
132 'could be fixed if you need it')
132 'could be fixed if you need it')
133 for f in self._fileobjs:
133 for f in self._fileobjs:
134 f.seek(0)
134 f.seek(0)
135 self._index = 0
135 self._index = 0
136
136
137 class httppeer(wireproto.wirepeer):
137 class httppeer(wireproto.wirepeer):
138 def __init__(self, ui, path):
138 def __init__(self, ui, path):
139 self._path = path
139 self._path = path
140 self._caps = None
140 self._caps = None
141 self._urlopener = None
141 self._urlopener = None
142 self._requestbuilder = None
142 self._requestbuilder = None
143 u = util.url(path)
143 u = util.url(path)
144 if u.query or u.fragment:
144 if u.query or u.fragment:
145 raise error.Abort(_('unsupported URL component: "%s"') %
145 raise error.Abort(_('unsupported URL component: "%s"') %
146 (u.query or u.fragment))
146 (u.query or u.fragment))
147
147
148 # urllib cannot handle URLs with embedded user or passwd
148 # urllib cannot handle URLs with embedded user or passwd
149 self._url, authinfo = u.authinfo()
149 self._url, authinfo = u.authinfo()
150
150
151 self._ui = ui
151 self._ui = ui
152 ui.debug('using %s\n' % self._url)
152 ui.debug('using %s\n' % self._url)
153
153
154 self._urlopener = url.opener(ui, authinfo)
154 self._urlopener = url.opener(ui, authinfo)
155 self._requestbuilder = urlreq.request
155 self._requestbuilder = urlreq.request
156
156
157 def __del__(self):
157 def __del__(self):
158 urlopener = getattr(self, '_urlopener', None)
158 urlopener = getattr(self, '_urlopener', None)
159 if urlopener:
159 if urlopener:
160 for h in urlopener.handlers:
160 for h in urlopener.handlers:
161 h.close()
161 h.close()
162 getattr(h, "close_all", lambda: None)()
162 getattr(h, "close_all", lambda: None)()
163
163
164 # Begin of _basepeer interface.
164 # Begin of _basepeer interface.
165
165
166 @util.propertycache
166 @util.propertycache
167 def ui(self):
167 def ui(self):
168 return self._ui
168 return self._ui
169
169
170 def url(self):
170 def url(self):
171 return self._path
171 return self._path
172
172
173 def local(self):
173 def local(self):
174 return None
174 return None
175
175
176 def peer(self):
176 def peer(self):
177 return self
177 return self
178
178
179 def canpush(self):
179 def canpush(self):
180 return True
180 return True
181
181
182 def close(self):
182 def close(self):
183 pass
183 pass
184
184
185 # End of _basepeer interface.
185 # End of _basepeer interface.
186
186
187 # Begin of _basewirepeer interface.
187 # Begin of _basewirepeer interface.
188
188
189 def capabilities(self):
189 def capabilities(self):
190 if self._caps is None:
190 if self._caps is None:
191 try:
191 try:
192 self._fetchcaps()
192 self._fetchcaps()
193 except error.RepoError:
193 except error.RepoError:
194 self._caps = set()
194 self._caps = set()
195 self.ui.debug('capabilities: %s\n' %
195 self.ui.debug('capabilities: %s\n' %
196 (' '.join(self._caps or ['none'])))
196 (' '.join(self._caps or ['none'])))
197 return self._caps
197 return self._caps
198
198
199 # End of _basewirepeer interface.
199 # End of _basewirepeer interface.
200
200
201 # look up capabilities only when needed
201 # look up capabilities only when needed
202
202
203 def _fetchcaps(self):
203 def _fetchcaps(self):
204 self._caps = set(self._call('capabilities').split())
204 self._caps = set(self._call('capabilities').split())
205
205
206 def _callstream(self, cmd, _compressible=False, **args):
206 def _callstream(self, cmd, _compressible=False, **args):
207 args = pycompat.byteskwargs(args)
207 if cmd == 'pushkey':
208 if cmd == 'pushkey':
208 args['data'] = ''
209 args['data'] = ''
209 data = args.pop('data', None)
210 data = args.pop('data', None)
210 headers = args.pop('headers', {})
211 headers = args.pop('headers', {})
211
212
212 self.ui.debug("sending %s command\n" % cmd)
213 self.ui.debug("sending %s command\n" % cmd)
213 q = [('cmd', cmd)]
214 q = [('cmd', cmd)]
214 headersize = 0
215 headersize = 0
215 varyheaders = []
216 varyheaders = []
216 # Important: don't use self.capable() here or else you end up
217 # Important: don't use self.capable() here or else you end up
217 # with infinite recursion when trying to look up capabilities
218 # with infinite recursion when trying to look up capabilities
218 # for the first time.
219 # for the first time.
219 postargsok = self._caps is not None and 'httppostargs' in self._caps
220 postargsok = self._caps is not None and 'httppostargs' in self._caps
220 if postargsok and args:
221 if postargsok and args:
221 strargs = urlreq.urlencode(sorted(args.items()))
222 strargs = urlreq.urlencode(sorted(args.items()))
222 if not data:
223 if not data:
223 data = strargs
224 data = strargs
224 else:
225 else:
225 if isinstance(data, bytes):
226 if isinstance(data, bytes):
226 i = io.BytesIO(data)
227 i = io.BytesIO(data)
227 i.length = len(data)
228 i.length = len(data)
228 data = i
229 data = i
229 argsio = io.BytesIO(strargs)
230 argsio = io.BytesIO(strargs)
230 argsio.length = len(strargs)
231 argsio.length = len(strargs)
231 data = _multifile(argsio, data)
232 data = _multifile(argsio, data)
232 headers[r'X-HgArgs-Post'] = len(strargs)
233 headers[r'X-HgArgs-Post'] = len(strargs)
233 else:
234 else:
234 if len(args) > 0:
235 if len(args) > 0:
235 httpheader = self.capable('httpheader')
236 httpheader = self.capable('httpheader')
236 if httpheader:
237 if httpheader:
237 headersize = int(httpheader.split(',', 1)[0])
238 headersize = int(httpheader.split(',', 1)[0])
238 if headersize > 0:
239 if headersize > 0:
239 # The headers can typically carry more data than the URL.
240 # The headers can typically carry more data than the URL.
240 encargs = urlreq.urlencode(sorted(args.items()))
241 encargs = urlreq.urlencode(sorted(args.items()))
241 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
242 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
242 headersize):
243 headersize):
243 headers[header] = value
244 headers[header] = value
244 varyheaders.append(header)
245 varyheaders.append(header)
245 else:
246 else:
246 q += sorted(args.items())
247 q += sorted(args.items())
247 qs = '?%s' % urlreq.urlencode(q)
248 qs = '?%s' % urlreq.urlencode(q)
248 cu = "%s%s" % (self._url, qs)
249 cu = "%s%s" % (self._url, qs)
249 size = 0
250 size = 0
250 if util.safehasattr(data, 'length'):
251 if util.safehasattr(data, 'length'):
251 size = data.length
252 size = data.length
252 elif data is not None:
253 elif data is not None:
253 size = len(data)
254 size = len(data)
254 if size and self.ui.configbool('ui', 'usehttp2'):
255 if size and self.ui.configbool('ui', 'usehttp2'):
255 headers[r'Expect'] = r'100-Continue'
256 headers[r'Expect'] = r'100-Continue'
256 headers[r'X-HgHttp2'] = r'1'
257 headers[r'X-HgHttp2'] = r'1'
257 if data is not None and r'Content-Type' not in headers:
258 if data is not None and r'Content-Type' not in headers:
258 headers[r'Content-Type'] = r'application/mercurial-0.1'
259 headers[r'Content-Type'] = r'application/mercurial-0.1'
259
260
260 # Tell the server we accept application/mercurial-0.2 and multiple
261 # Tell the server we accept application/mercurial-0.2 and multiple
261 # compression formats if the server is capable of emitting those
262 # compression formats if the server is capable of emitting those
262 # payloads.
263 # payloads.
263 protoparams = []
264 protoparams = []
264
265
265 mediatypes = set()
266 mediatypes = set()
266 if self._caps is not None:
267 if self._caps is not None:
267 mt = self.capable('httpmediatype')
268 mt = self.capable('httpmediatype')
268 if mt:
269 if mt:
269 protoparams.append('0.1')
270 protoparams.append('0.1')
270 mediatypes = set(mt.split(','))
271 mediatypes = set(mt.split(','))
271
272
272 if '0.2tx' in mediatypes:
273 if '0.2tx' in mediatypes:
273 protoparams.append('0.2')
274 protoparams.append('0.2')
274
275
275 if '0.2tx' in mediatypes and self.capable('compression'):
276 if '0.2tx' in mediatypes and self.capable('compression'):
276 # We /could/ compare supported compression formats and prune
277 # We /could/ compare supported compression formats and prune
277 # non-mutually supported or error if nothing is mutually supported.
278 # non-mutually supported or error if nothing is mutually supported.
278 # For now, send the full list to the server and have it error.
279 # For now, send the full list to the server and have it error.
279 comps = [e.wireprotosupport().name for e in
280 comps = [e.wireprotosupport().name for e in
280 util.compengines.supportedwireengines(util.CLIENTROLE)]
281 util.compengines.supportedwireengines(util.CLIENTROLE)]
281 protoparams.append('comp=%s' % ','.join(comps))
282 protoparams.append('comp=%s' % ','.join(comps))
282
283
283 if protoparams:
284 if protoparams:
284 protoheaders = encodevalueinheaders(' '.join(protoparams),
285 protoheaders = encodevalueinheaders(' '.join(protoparams),
285 'X-HgProto',
286 'X-HgProto',
286 headersize or 1024)
287 headersize or 1024)
287 for header, value in protoheaders:
288 for header, value in protoheaders:
288 headers[header] = value
289 headers[header] = value
289 varyheaders.append(header)
290 varyheaders.append(header)
290
291
291 if varyheaders:
292 if varyheaders:
292 headers[r'Vary'] = r','.join(varyheaders)
293 headers[r'Vary'] = r','.join(varyheaders)
293
294
294 req = self._requestbuilder(pycompat.strurl(cu), data, headers)
295 req = self._requestbuilder(pycompat.strurl(cu), data, headers)
295
296
296 if data is not None:
297 if data is not None:
297 self.ui.debug("sending %s bytes\n" % size)
298 self.ui.debug("sending %s bytes\n" % size)
298 req.add_unredirected_header('Content-Length', '%d' % size)
299 req.add_unredirected_header('Content-Length', '%d' % size)
299 try:
300 try:
300 resp = self._urlopener.open(req)
301 resp = self._urlopener.open(req)
301 except urlerr.httperror as inst:
302 except urlerr.httperror as inst:
302 if inst.code == 401:
303 if inst.code == 401:
303 raise error.Abort(_('authorization failed'))
304 raise error.Abort(_('authorization failed'))
304 raise
305 raise
305 except httplib.HTTPException as inst:
306 except httplib.HTTPException as inst:
306 self.ui.debug('http error while sending %s command\n' % cmd)
307 self.ui.debug('http error while sending %s command\n' % cmd)
307 self.ui.traceback()
308 self.ui.traceback()
308 raise IOError(None, inst)
309 raise IOError(None, inst)
309
310
310 # Insert error handlers for common I/O failures.
311 # Insert error handlers for common I/O failures.
311 _wraphttpresponse(resp)
312 _wraphttpresponse(resp)
312
313
313 # record the url we got redirected to
314 # record the url we got redirected to
314 resp_url = pycompat.bytesurl(resp.geturl())
315 resp_url = pycompat.bytesurl(resp.geturl())
315 if resp_url.endswith(qs):
316 if resp_url.endswith(qs):
316 resp_url = resp_url[:-len(qs)]
317 resp_url = resp_url[:-len(qs)]
317 if self._url.rstrip('/') != resp_url.rstrip('/'):
318 if self._url.rstrip('/') != resp_url.rstrip('/'):
318 if not self.ui.quiet:
319 if not self.ui.quiet:
319 self.ui.warn(_('real URL is %s\n') % resp_url)
320 self.ui.warn(_('real URL is %s\n') % resp_url)
320 self._url = resp_url
321 self._url = resp_url
321 try:
322 try:
322 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
323 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
323 except AttributeError:
324 except AttributeError:
324 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
325 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
325
326
326 safeurl = util.hidepassword(self._url)
327 safeurl = util.hidepassword(self._url)
327 if proto.startswith('application/hg-error'):
328 if proto.startswith('application/hg-error'):
328 raise error.OutOfBandError(resp.read())
329 raise error.OutOfBandError(resp.read())
329 # accept old "text/plain" and "application/hg-changegroup" for now
330 # accept old "text/plain" and "application/hg-changegroup" for now
330 if not (proto.startswith('application/mercurial-') or
331 if not (proto.startswith('application/mercurial-') or
331 (proto.startswith('text/plain')
332 (proto.startswith('text/plain')
332 and not resp.headers.get('content-length')) or
333 and not resp.headers.get('content-length')) or
333 proto.startswith('application/hg-changegroup')):
334 proto.startswith('application/hg-changegroup')):
334 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
335 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
335 raise error.RepoError(
336 raise error.RepoError(
336 _("'%s' does not appear to be an hg repository:\n"
337 _("'%s' does not appear to be an hg repository:\n"
337 "---%%<--- (%s)\n%s\n---%%<---\n")
338 "---%%<--- (%s)\n%s\n---%%<---\n")
338 % (safeurl, proto or 'no content-type', resp.read(1024)))
339 % (safeurl, proto or 'no content-type', resp.read(1024)))
339
340
340 if proto.startswith('application/mercurial-'):
341 if proto.startswith('application/mercurial-'):
341 try:
342 try:
342 version = proto.split('-', 1)[1]
343 version = proto.split('-', 1)[1]
343 version_info = tuple([int(n) for n in version.split('.')])
344 version_info = tuple([int(n) for n in version.split('.')])
344 except ValueError:
345 except ValueError:
345 raise error.RepoError(_("'%s' sent a broken Content-Type "
346 raise error.RepoError(_("'%s' sent a broken Content-Type "
346 "header (%s)") % (safeurl, proto))
347 "header (%s)") % (safeurl, proto))
347
348
348 # TODO consider switching to a decompression reader that uses
349 # TODO consider switching to a decompression reader that uses
349 # generators.
350 # generators.
350 if version_info == (0, 1):
351 if version_info == (0, 1):
351 if _compressible:
352 if _compressible:
352 return util.compengines['zlib'].decompressorreader(resp)
353 return util.compengines['zlib'].decompressorreader(resp)
353 return resp
354 return resp
354 elif version_info == (0, 2):
355 elif version_info == (0, 2):
355 # application/mercurial-0.2 always identifies the compression
356 # application/mercurial-0.2 always identifies the compression
356 # engine in the payload header.
357 # engine in the payload header.
357 elen = struct.unpack('B', resp.read(1))[0]
358 elen = struct.unpack('B', resp.read(1))[0]
358 ename = resp.read(elen)
359 ename = resp.read(elen)
359 engine = util.compengines.forwiretype(ename)
360 engine = util.compengines.forwiretype(ename)
360 return engine.decompressorreader(resp)
361 return engine.decompressorreader(resp)
361 else:
362 else:
362 raise error.RepoError(_("'%s' uses newer protocol %s") %
363 raise error.RepoError(_("'%s' uses newer protocol %s") %
363 (safeurl, version))
364 (safeurl, version))
364
365
365 if _compressible:
366 if _compressible:
366 return util.compengines['zlib'].decompressorreader(resp)
367 return util.compengines['zlib'].decompressorreader(resp)
367
368
368 return resp
369 return resp
369
370
370 def _call(self, cmd, **args):
371 def _call(self, cmd, **args):
371 fp = self._callstream(cmd, **args)
372 fp = self._callstream(cmd, **args)
372 try:
373 try:
373 return fp.read()
374 return fp.read()
374 finally:
375 finally:
375 # if using keepalive, allow connection to be reused
376 # if using keepalive, allow connection to be reused
376 fp.close()
377 fp.close()
377
378
378 def _callpush(self, cmd, cg, **args):
379 def _callpush(self, cmd, cg, **args):
379 # have to stream bundle to a temp file because we do not have
380 # have to stream bundle to a temp file because we do not have
380 # http 1.1 chunked transfer.
381 # http 1.1 chunked transfer.
381
382
382 types = self.capable('unbundle')
383 types = self.capable('unbundle')
383 try:
384 try:
384 types = types.split(',')
385 types = types.split(',')
385 except AttributeError:
386 except AttributeError:
386 # servers older than d1b16a746db6 will send 'unbundle' as a
387 # servers older than d1b16a746db6 will send 'unbundle' as a
387 # boolean capability. They only support headerless/uncompressed
388 # boolean capability. They only support headerless/uncompressed
388 # bundles.
389 # bundles.
389 types = [""]
390 types = [""]
390 for x in types:
391 for x in types:
391 if x in bundle2.bundletypes:
392 if x in bundle2.bundletypes:
392 type = x
393 type = x
393 break
394 break
394
395
395 tempname = bundle2.writebundle(self.ui, cg, None, type)
396 tempname = bundle2.writebundle(self.ui, cg, None, type)
396 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
397 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
397 headers = {'Content-Type': 'application/mercurial-0.1'}
398 headers = {'Content-Type': 'application/mercurial-0.1'}
398
399
399 try:
400 try:
400 r = self._call(cmd, data=fp, headers=headers, **args)
401 r = self._call(cmd, data=fp, headers=headers, **args)
401 vals = r.split('\n', 1)
402 vals = r.split('\n', 1)
402 if len(vals) < 2:
403 if len(vals) < 2:
403 raise error.ResponseError(_("unexpected response:"), r)
404 raise error.ResponseError(_("unexpected response:"), r)
404 return vals
405 return vals
405 except socket.error as err:
406 except socket.error as err:
406 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
407 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
407 raise error.Abort(_('push failed: %s') % err.args[1])
408 raise error.Abort(_('push failed: %s') % err.args[1])
408 raise error.Abort(err.args[1])
409 raise error.Abort(err.args[1])
409 finally:
410 finally:
410 fp.close()
411 fp.close()
411 os.unlink(tempname)
412 os.unlink(tempname)
412
413
413 def _calltwowaystream(self, cmd, fp, **args):
414 def _calltwowaystream(self, cmd, fp, **args):
414 fh = None
415 fh = None
415 fp_ = None
416 fp_ = None
416 filename = None
417 filename = None
417 try:
418 try:
418 # dump bundle to disk
419 # dump bundle to disk
419 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
420 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
420 fh = os.fdopen(fd, pycompat.sysstr("wb"))
421 fh = os.fdopen(fd, pycompat.sysstr("wb"))
421 d = fp.read(4096)
422 d = fp.read(4096)
422 while d:
423 while d:
423 fh.write(d)
424 fh.write(d)
424 d = fp.read(4096)
425 d = fp.read(4096)
425 fh.close()
426 fh.close()
426 # start http push
427 # start http push
427 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
428 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
428 headers = {'Content-Type': 'application/mercurial-0.1'}
429 headers = {'Content-Type': 'application/mercurial-0.1'}
429 return self._callstream(cmd, data=fp_, headers=headers, **args)
430 return self._callstream(cmd, data=fp_, headers=headers, **args)
430 finally:
431 finally:
431 if fp_ is not None:
432 if fp_ is not None:
432 fp_.close()
433 fp_.close()
433 if fh is not None:
434 if fh is not None:
434 fh.close()
435 fh.close()
435 os.unlink(filename)
436 os.unlink(filename)
436
437
437 def _callcompressable(self, cmd, **args):
438 def _callcompressable(self, cmd, **args):
438 return self._callstream(cmd, _compressible=True, **args)
439 return self._callstream(cmd, _compressible=True, **args)
439
440
440 def _abort(self, exception):
441 def _abort(self, exception):
441 raise exception
442 raise exception
442
443
443 class httpspeer(httppeer):
444 class httpspeer(httppeer):
444 def __init__(self, ui, path):
445 def __init__(self, ui, path):
445 if not url.has_https:
446 if not url.has_https:
446 raise error.Abort(_('Python support for SSL and HTTPS '
447 raise error.Abort(_('Python support for SSL and HTTPS '
447 'is not installed'))
448 'is not installed'))
448 httppeer.__init__(self, ui, path)
449 httppeer.__init__(self, ui, path)
449
450
450 def instance(ui, path, create):
451 def instance(ui, path, create):
451 if create:
452 if create:
452 raise error.Abort(_('cannot create new http repository'))
453 raise error.Abort(_('cannot create new http repository'))
453 try:
454 try:
454 if path.startswith('https:'):
455 if path.startswith('https:'):
455 inst = httpspeer(ui, path)
456 inst = httpspeer(ui, path)
456 else:
457 else:
457 inst = httppeer(ui, path)
458 inst = httppeer(ui, path)
458 try:
459 try:
459 # Try to do useful work when checking compatibility.
460 # Try to do useful work when checking compatibility.
460 # Usually saves a roundtrip since we want the caps anyway.
461 # Usually saves a roundtrip since we want the caps anyway.
461 inst._fetchcaps()
462 inst._fetchcaps()
462 except error.RepoError:
463 except error.RepoError:
463 # No luck, try older compatibility check.
464 # No luck, try older compatibility check.
464 inst.between([(nullid, nullid)])
465 inst.between([(nullid, nullid)])
465 return inst
466 return inst
466 except error.RepoError as httpexception:
467 except error.RepoError as httpexception:
467 try:
468 try:
468 r = statichttprepo.instance(ui, "static-" + path, create)
469 r = statichttprepo.instance(ui, "static-" + path, create)
469 ui.note(_('(falling back to static-http)\n'))
470 ui.note(_('(falling back to static-http)\n'))
470 return r
471 return r
471 except error.RepoError:
472 except error.RepoError:
472 raise httpexception # use the original http RepoError instead
473 raise httpexception # use the original http RepoError instead
General Comments 0
You need to be logged in to leave comments. Login now