##// END OF EJS Templates
enhance the error output in case of failure during http push
Benoit Boissinot -
r7010:9141bebe default
parent child Browse files
Show More
@@ -1,458 +1,460 b''
1 # httprepo.py - HTTP repository proxy classes for mercurial
1 # httprepo.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
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 from node import bin, hex
9 from node import bin, hex
10 from i18n import _
10 from i18n import _
11 import repo, os, urllib, urllib2, urlparse, zlib, util, httplib
11 import repo, os, urllib, urllib2, urlparse, zlib, util, httplib
12 import errno, keepalive, socket, changegroup
12 import errno, keepalive, socket, changegroup
13
13
14 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
14 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
15 def __init__(self, ui):
15 def __init__(self, ui):
16 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
16 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
17 self.ui = ui
17 self.ui = ui
18
18
19 def find_user_password(self, realm, authuri):
19 def find_user_password(self, realm, authuri):
20 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
20 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
21 self, realm, authuri)
21 self, realm, authuri)
22 user, passwd = authinfo
22 user, passwd = authinfo
23 if user and passwd:
23 if user and passwd:
24 return (user, passwd)
24 return (user, passwd)
25
25
26 if not self.ui.interactive:
26 if not self.ui.interactive:
27 raise util.Abort(_('http authorization required'))
27 raise util.Abort(_('http authorization required'))
28
28
29 self.ui.write(_("http authorization required\n"))
29 self.ui.write(_("http authorization required\n"))
30 self.ui.status(_("realm: %s\n") % realm)
30 self.ui.status(_("realm: %s\n") % realm)
31 if user:
31 if user:
32 self.ui.status(_("user: %s\n") % user)
32 self.ui.status(_("user: %s\n") % user)
33 else:
33 else:
34 user = self.ui.prompt(_("user:"), default=None)
34 user = self.ui.prompt(_("user:"), default=None)
35
35
36 if not passwd:
36 if not passwd:
37 passwd = self.ui.getpass()
37 passwd = self.ui.getpass()
38
38
39 self.add_password(realm, authuri, user, passwd)
39 self.add_password(realm, authuri, user, passwd)
40 return (user, passwd)
40 return (user, passwd)
41
41
42 def netlocsplit(netloc):
42 def netlocsplit(netloc):
43 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
43 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
44
44
45 a = netloc.find('@')
45 a = netloc.find('@')
46 if a == -1:
46 if a == -1:
47 user, passwd = None, None
47 user, passwd = None, None
48 else:
48 else:
49 userpass, netloc = netloc[:a], netloc[a+1:]
49 userpass, netloc = netloc[:a], netloc[a+1:]
50 c = userpass.find(':')
50 c = userpass.find(':')
51 if c == -1:
51 if c == -1:
52 user, passwd = urllib.unquote(userpass), None
52 user, passwd = urllib.unquote(userpass), None
53 else:
53 else:
54 user = urllib.unquote(userpass[:c])
54 user = urllib.unquote(userpass[:c])
55 passwd = urllib.unquote(userpass[c+1:])
55 passwd = urllib.unquote(userpass[c+1:])
56 c = netloc.find(':')
56 c = netloc.find(':')
57 if c == -1:
57 if c == -1:
58 host, port = netloc, None
58 host, port = netloc, None
59 else:
59 else:
60 host, port = netloc[:c], netloc[c+1:]
60 host, port = netloc[:c], netloc[c+1:]
61 return host, port, user, passwd
61 return host, port, user, passwd
62
62
63 def netlocunsplit(host, port, user=None, passwd=None):
63 def netlocunsplit(host, port, user=None, passwd=None):
64 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
64 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
65 if port:
65 if port:
66 hostport = host + ':' + port
66 hostport = host + ':' + port
67 else:
67 else:
68 hostport = host
68 hostport = host
69 if user:
69 if user:
70 if passwd:
70 if passwd:
71 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
71 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
72 else:
72 else:
73 userpass = urllib.quote(user)
73 userpass = urllib.quote(user)
74 return userpass + '@' + hostport
74 return userpass + '@' + hostport
75 return hostport
75 return hostport
76
76
77 # work around a bug in Python < 2.4.2
77 # work around a bug in Python < 2.4.2
78 # (it leaves a "\n" at the end of Proxy-authorization headers)
78 # (it leaves a "\n" at the end of Proxy-authorization headers)
79 class request(urllib2.Request):
79 class request(urllib2.Request):
80 def add_header(self, key, val):
80 def add_header(self, key, val):
81 if key.lower() == 'proxy-authorization':
81 if key.lower() == 'proxy-authorization':
82 val = val.strip()
82 val = val.strip()
83 return urllib2.Request.add_header(self, key, val)
83 return urllib2.Request.add_header(self, key, val)
84
84
85 class httpsendfile(file):
85 class httpsendfile(file):
86 def __len__(self):
86 def __len__(self):
87 return os.fstat(self.fileno()).st_size
87 return os.fstat(self.fileno()).st_size
88
88
89 def _gen_sendfile(connection):
89 def _gen_sendfile(connection):
90 def _sendfile(self, data):
90 def _sendfile(self, data):
91 # send a file
91 # send a file
92 if isinstance(data, httpsendfile):
92 if isinstance(data, httpsendfile):
93 # if auth required, some data sent twice, so rewind here
93 # if auth required, some data sent twice, so rewind here
94 data.seek(0)
94 data.seek(0)
95 for chunk in util.filechunkiter(data):
95 for chunk in util.filechunkiter(data):
96 connection.send(self, chunk)
96 connection.send(self, chunk)
97 else:
97 else:
98 connection.send(self, data)
98 connection.send(self, data)
99 return _sendfile
99 return _sendfile
100
100
101 class httpconnection(keepalive.HTTPConnection):
101 class httpconnection(keepalive.HTTPConnection):
102 # must be able to send big bundle as stream.
102 # must be able to send big bundle as stream.
103 send = _gen_sendfile(keepalive.HTTPConnection)
103 send = _gen_sendfile(keepalive.HTTPConnection)
104
104
105 class httphandler(keepalive.HTTPHandler):
105 class httphandler(keepalive.HTTPHandler):
106 def http_open(self, req):
106 def http_open(self, req):
107 return self.do_open(httpconnection, req)
107 return self.do_open(httpconnection, req)
108
108
109 def __del__(self):
109 def __del__(self):
110 self.close_all()
110 self.close_all()
111
111
112 has_https = hasattr(urllib2, 'HTTPSHandler')
112 has_https = hasattr(urllib2, 'HTTPSHandler')
113 if has_https:
113 if has_https:
114 class httpsconnection(httplib.HTTPSConnection):
114 class httpsconnection(httplib.HTTPSConnection):
115 response_class = keepalive.HTTPResponse
115 response_class = keepalive.HTTPResponse
116 # must be able to send big bundle as stream.
116 # must be able to send big bundle as stream.
117 send = _gen_sendfile(httplib.HTTPSConnection)
117 send = _gen_sendfile(httplib.HTTPSConnection)
118
118
119 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
119 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
120 def https_open(self, req):
120 def https_open(self, req):
121 return self.do_open(httpsconnection, req)
121 return self.do_open(httpsconnection, req)
122
122
123 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
123 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
124 # it doesn't know about the auth type requested. This can happen if
124 # it doesn't know about the auth type requested. This can happen if
125 # somebody is using BasicAuth and types a bad password.
125 # somebody is using BasicAuth and types a bad password.
126 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
126 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
127 def http_error_auth_reqed(self, auth_header, host, req, headers):
127 def http_error_auth_reqed(self, auth_header, host, req, headers):
128 try:
128 try:
129 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
129 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
130 self, auth_header, host, req, headers)
130 self, auth_header, host, req, headers)
131 except ValueError, inst:
131 except ValueError, inst:
132 arg = inst.args[0]
132 arg = inst.args[0]
133 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
133 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
134 return
134 return
135 raise
135 raise
136
136
137 def zgenerator(f):
137 def zgenerator(f):
138 zd = zlib.decompressobj()
138 zd = zlib.decompressobj()
139 try:
139 try:
140 for chunk in util.filechunkiter(f):
140 for chunk in util.filechunkiter(f):
141 yield zd.decompress(chunk)
141 yield zd.decompress(chunk)
142 except httplib.HTTPException, inst:
142 except httplib.HTTPException, inst:
143 raise IOError(None, _('connection ended unexpectedly'))
143 raise IOError(None, _('connection ended unexpectedly'))
144 yield zd.flush()
144 yield zd.flush()
145
145
146 _safe = ('abcdefghijklmnopqrstuvwxyz'
146 _safe = ('abcdefghijklmnopqrstuvwxyz'
147 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
147 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
148 '0123456789' '_.-/')
148 '0123456789' '_.-/')
149 _safeset = None
149 _safeset = None
150 _hex = None
150 _hex = None
151 def quotepath(path):
151 def quotepath(path):
152 '''quote the path part of a URL
152 '''quote the path part of a URL
153
153
154 This is similar to urllib.quote, but it also tries to avoid
154 This is similar to urllib.quote, but it also tries to avoid
155 quoting things twice (inspired by wget):
155 quoting things twice (inspired by wget):
156
156
157 >>> quotepath('abc def')
157 >>> quotepath('abc def')
158 'abc%20def'
158 'abc%20def'
159 >>> quotepath('abc%20def')
159 >>> quotepath('abc%20def')
160 'abc%20def'
160 'abc%20def'
161 >>> quotepath('abc%20 def')
161 >>> quotepath('abc%20 def')
162 'abc%20%20def'
162 'abc%20%20def'
163 >>> quotepath('abc def%20')
163 >>> quotepath('abc def%20')
164 'abc%20def%20'
164 'abc%20def%20'
165 >>> quotepath('abc def%2')
165 >>> quotepath('abc def%2')
166 'abc%20def%252'
166 'abc%20def%252'
167 >>> quotepath('abc def%')
167 >>> quotepath('abc def%')
168 'abc%20def%25'
168 'abc%20def%25'
169 '''
169 '''
170 global _safeset, _hex
170 global _safeset, _hex
171 if _safeset is None:
171 if _safeset is None:
172 _safeset = util.set(_safe)
172 _safeset = util.set(_safe)
173 _hex = util.set('abcdefABCDEF0123456789')
173 _hex = util.set('abcdefABCDEF0123456789')
174 l = list(path)
174 l = list(path)
175 for i in xrange(len(l)):
175 for i in xrange(len(l)):
176 c = l[i]
176 c = l[i]
177 if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
177 if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
178 pass
178 pass
179 elif c not in _safeset:
179 elif c not in _safeset:
180 l[i] = '%%%02X' % ord(c)
180 l[i] = '%%%02X' % ord(c)
181 return ''.join(l)
181 return ''.join(l)
182
182
183 class httprepository(repo.repository):
183 class httprepository(repo.repository):
184 def __init__(self, ui, path):
184 def __init__(self, ui, path):
185 self.path = path
185 self.path = path
186 self.caps = None
186 self.caps = None
187 self.handler = None
187 self.handler = None
188 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
188 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
189 if query or frag:
189 if query or frag:
190 raise util.Abort(_('unsupported URL component: "%s"') %
190 raise util.Abort(_('unsupported URL component: "%s"') %
191 (query or frag))
191 (query or frag))
192 if not urlpath:
192 if not urlpath:
193 urlpath = '/'
193 urlpath = '/'
194 urlpath = quotepath(urlpath)
194 urlpath = quotepath(urlpath)
195 host, port, user, passwd = netlocsplit(netloc)
195 host, port, user, passwd = netlocsplit(netloc)
196
196
197 # urllib cannot handle URLs with embedded user or passwd
197 # urllib cannot handle URLs with embedded user or passwd
198 self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
198 self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
199 urlpath, '', ''))
199 urlpath, '', ''))
200 self.ui = ui
200 self.ui = ui
201 self.ui.debug(_('using %s\n') % self._url)
201 self.ui.debug(_('using %s\n') % self._url)
202
202
203 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
203 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
204 # XXX proxyauthinfo = None
204 # XXX proxyauthinfo = None
205 handlers = [httphandler()]
205 handlers = [httphandler()]
206 if has_https:
206 if has_https:
207 handlers.append(httpshandler())
207 handlers.append(httpshandler())
208
208
209 if proxyurl:
209 if proxyurl:
210 # proxy can be proper url or host[:port]
210 # proxy can be proper url or host[:port]
211 if not (proxyurl.startswith('http:') or
211 if not (proxyurl.startswith('http:') or
212 proxyurl.startswith('https:')):
212 proxyurl.startswith('https:')):
213 proxyurl = 'http://' + proxyurl + '/'
213 proxyurl = 'http://' + proxyurl + '/'
214 snpqf = urlparse.urlsplit(proxyurl)
214 snpqf = urlparse.urlsplit(proxyurl)
215 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
215 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
216 hpup = netlocsplit(proxynetloc)
216 hpup = netlocsplit(proxynetloc)
217
217
218 proxyhost, proxyport, proxyuser, proxypasswd = hpup
218 proxyhost, proxyport, proxyuser, proxypasswd = hpup
219 if not proxyuser:
219 if not proxyuser:
220 proxyuser = ui.config("http_proxy", "user")
220 proxyuser = ui.config("http_proxy", "user")
221 proxypasswd = ui.config("http_proxy", "passwd")
221 proxypasswd = ui.config("http_proxy", "passwd")
222
222
223 # see if we should use a proxy for this url
223 # see if we should use a proxy for this url
224 no_list = [ "localhost", "127.0.0.1" ]
224 no_list = [ "localhost", "127.0.0.1" ]
225 no_list.extend([p.lower() for
225 no_list.extend([p.lower() for
226 p in ui.configlist("http_proxy", "no")])
226 p in ui.configlist("http_proxy", "no")])
227 no_list.extend([p.strip().lower() for
227 no_list.extend([p.strip().lower() for
228 p in os.getenv("no_proxy", '').split(',')
228 p in os.getenv("no_proxy", '').split(',')
229 if p.strip()])
229 if p.strip()])
230 # "http_proxy.always" config is for running tests on localhost
230 # "http_proxy.always" config is for running tests on localhost
231 if (not ui.configbool("http_proxy", "always") and
231 if (not ui.configbool("http_proxy", "always") and
232 host.lower() in no_list):
232 host.lower() in no_list):
233 # avoid auto-detection of proxy settings by appending
233 # avoid auto-detection of proxy settings by appending
234 # a ProxyHandler with no proxies defined.
234 # a ProxyHandler with no proxies defined.
235 handlers.append(urllib2.ProxyHandler({}))
235 handlers.append(urllib2.ProxyHandler({}))
236 ui.debug(_('disabling proxy for %s\n') % host)
236 ui.debug(_('disabling proxy for %s\n') % host)
237 else:
237 else:
238 proxyurl = urlparse.urlunsplit((
238 proxyurl = urlparse.urlunsplit((
239 proxyscheme, netlocunsplit(proxyhost, proxyport,
239 proxyscheme, netlocunsplit(proxyhost, proxyport,
240 proxyuser, proxypasswd or ''),
240 proxyuser, proxypasswd or ''),
241 proxypath, proxyquery, proxyfrag))
241 proxypath, proxyquery, proxyfrag))
242 handlers.append(urllib2.ProxyHandler({scheme: proxyurl}))
242 handlers.append(urllib2.ProxyHandler({scheme: proxyurl}))
243 ui.debug(_('proxying through http://%s:%s\n') %
243 ui.debug(_('proxying through http://%s:%s\n') %
244 (proxyhost, proxyport))
244 (proxyhost, proxyport))
245
245
246 # urllib2 takes proxy values from the environment and those
246 # urllib2 takes proxy values from the environment and those
247 # will take precedence if found, so drop them
247 # will take precedence if found, so drop them
248 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
248 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
249 try:
249 try:
250 if env in os.environ:
250 if env in os.environ:
251 del os.environ[env]
251 del os.environ[env]
252 except OSError:
252 except OSError:
253 pass
253 pass
254
254
255 passmgr = passwordmgr(ui)
255 passmgr = passwordmgr(ui)
256 if user:
256 if user:
257 ui.debug(_('http auth: user %s, password %s\n') %
257 ui.debug(_('http auth: user %s, password %s\n') %
258 (user, passwd and '*' * len(passwd) or 'not set'))
258 (user, passwd and '*' * len(passwd) or 'not set'))
259 netloc = host
259 netloc = host
260 if port:
260 if port:
261 netloc += ':' + port
261 netloc += ':' + port
262 # Python < 2.4.3 uses only the netloc to search for a password
262 # Python < 2.4.3 uses only the netloc to search for a password
263 passmgr.add_password(None, (self._url, netloc), user, passwd or '')
263 passmgr.add_password(None, (self._url, netloc), user, passwd or '')
264
264
265 handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
265 handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
266 httpdigestauthhandler(passmgr)))
266 httpdigestauthhandler(passmgr)))
267 opener = urllib2.build_opener(*handlers)
267 opener = urllib2.build_opener(*handlers)
268
268
269 # 1.0 here is the _protocol_ version
269 # 1.0 here is the _protocol_ version
270 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
270 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
271 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
271 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
272 urllib2.install_opener(opener)
272 urllib2.install_opener(opener)
273
273
274 def url(self):
274 def url(self):
275 return self.path
275 return self.path
276
276
277 # look up capabilities only when needed
277 # look up capabilities only when needed
278
278
279 def get_caps(self):
279 def get_caps(self):
280 if self.caps is None:
280 if self.caps is None:
281 try:
281 try:
282 self.caps = util.set(self.do_read('capabilities').split())
282 self.caps = util.set(self.do_read('capabilities').split())
283 except repo.RepoError:
283 except repo.RepoError:
284 self.caps = util.set()
284 self.caps = util.set()
285 self.ui.debug(_('capabilities: %s\n') %
285 self.ui.debug(_('capabilities: %s\n') %
286 (' '.join(self.caps or ['none'])))
286 (' '.join(self.caps or ['none'])))
287 return self.caps
287 return self.caps
288
288
289 capabilities = property(get_caps)
289 capabilities = property(get_caps)
290
290
291 def lock(self):
291 def lock(self):
292 raise util.Abort(_('operation not supported over http'))
292 raise util.Abort(_('operation not supported over http'))
293
293
294 def do_cmd(self, cmd, **args):
294 def do_cmd(self, cmd, **args):
295 data = args.pop('data', None)
295 data = args.pop('data', None)
296 headers = args.pop('headers', {})
296 headers = args.pop('headers', {})
297 self.ui.debug(_("sending %s command\n") % cmd)
297 self.ui.debug(_("sending %s command\n") % cmd)
298 q = {"cmd": cmd}
298 q = {"cmd": cmd}
299 q.update(args)
299 q.update(args)
300 qs = '?%s' % urllib.urlencode(q)
300 qs = '?%s' % urllib.urlencode(q)
301 cu = "%s%s" % (self._url, qs)
301 cu = "%s%s" % (self._url, qs)
302 try:
302 try:
303 if data:
303 if data:
304 self.ui.debug(_("sending %s bytes\n") % len(data))
304 self.ui.debug(_("sending %s bytes\n") % len(data))
305 resp = urllib2.urlopen(request(cu, data, headers))
305 resp = urllib2.urlopen(request(cu, data, headers))
306 except urllib2.HTTPError, inst:
306 except urllib2.HTTPError, inst:
307 if inst.code == 401:
307 if inst.code == 401:
308 raise util.Abort(_('authorization failed'))
308 raise util.Abort(_('authorization failed'))
309 raise
309 raise
310 except httplib.HTTPException, inst:
310 except httplib.HTTPException, inst:
311 self.ui.debug(_('http error while sending %s command\n') % cmd)
311 self.ui.debug(_('http error while sending %s command\n') % cmd)
312 self.ui.print_exc()
312 self.ui.print_exc()
313 raise IOError(None, inst)
313 raise IOError(None, inst)
314 except IndexError:
314 except IndexError:
315 # this only happens with Python 2.3, later versions raise URLError
315 # this only happens with Python 2.3, later versions raise URLError
316 raise util.Abort(_('http error, possibly caused by proxy setting'))
316 raise util.Abort(_('http error, possibly caused by proxy setting'))
317 # record the url we got redirected to
317 # record the url we got redirected to
318 resp_url = resp.geturl()
318 resp_url = resp.geturl()
319 if resp_url.endswith(qs):
319 if resp_url.endswith(qs):
320 resp_url = resp_url[:-len(qs)]
320 resp_url = resp_url[:-len(qs)]
321 if self._url != resp_url:
321 if self._url != resp_url:
322 self.ui.status(_('real URL is %s\n') % resp_url)
322 self.ui.status(_('real URL is %s\n') % resp_url)
323 self._url = resp_url
323 self._url = resp_url
324 try:
324 try:
325 proto = resp.getheader('content-type')
325 proto = resp.getheader('content-type')
326 except AttributeError:
326 except AttributeError:
327 proto = resp.headers['content-type']
327 proto = resp.headers['content-type']
328
328
329 # accept old "text/plain" and "application/hg-changegroup" for now
329 # accept old "text/plain" and "application/hg-changegroup" for now
330 if not (proto.startswith('application/mercurial-') or
330 if not (proto.startswith('application/mercurial-') or
331 proto.startswith('text/plain') or
331 proto.startswith('text/plain') or
332 proto.startswith('application/hg-changegroup')):
332 proto.startswith('application/hg-changegroup')):
333 self.ui.debug(_("Requested URL: '%s'\n") % cu)
333 self.ui.debug(_("Requested URL: '%s'\n") % cu)
334 raise repo.RepoError(_("'%s' does not appear to be an hg repository")
334 raise repo.RepoError(_("'%s' does not appear to be an hg repository")
335 % self._url)
335 % self._url)
336
336
337 if proto.startswith('application/mercurial-'):
337 if proto.startswith('application/mercurial-'):
338 try:
338 try:
339 version = proto.split('-', 1)[1]
339 version = proto.split('-', 1)[1]
340 version_info = tuple([int(n) for n in version.split('.')])
340 version_info = tuple([int(n) for n in version.split('.')])
341 except ValueError:
341 except ValueError:
342 raise repo.RepoError(_("'%s' sent a broken Content-Type "
342 raise repo.RepoError(_("'%s' sent a broken Content-Type "
343 "header (%s)") % (self._url, proto))
343 "header (%s)") % (self._url, proto))
344 if version_info > (0, 1):
344 if version_info > (0, 1):
345 raise repo.RepoError(_("'%s' uses newer protocol %s") %
345 raise repo.RepoError(_("'%s' uses newer protocol %s") %
346 (self._url, version))
346 (self._url, version))
347
347
348 return resp
348 return resp
349
349
350 def do_read(self, cmd, **args):
350 def do_read(self, cmd, **args):
351 fp = self.do_cmd(cmd, **args)
351 fp = self.do_cmd(cmd, **args)
352 try:
352 try:
353 return fp.read()
353 return fp.read()
354 finally:
354 finally:
355 # if using keepalive, allow connection to be reused
355 # if using keepalive, allow connection to be reused
356 fp.close()
356 fp.close()
357
357
358 def lookup(self, key):
358 def lookup(self, key):
359 self.requirecap('lookup', _('look up remote revision'))
359 self.requirecap('lookup', _('look up remote revision'))
360 d = self.do_cmd("lookup", key = key).read()
360 d = self.do_cmd("lookup", key = key).read()
361 success, data = d[:-1].split(' ', 1)
361 success, data = d[:-1].split(' ', 1)
362 if int(success):
362 if int(success):
363 return bin(data)
363 return bin(data)
364 raise repo.RepoError(data)
364 raise repo.RepoError(data)
365
365
366 def heads(self):
366 def heads(self):
367 d = self.do_read("heads")
367 d = self.do_read("heads")
368 try:
368 try:
369 return map(bin, d[:-1].split(" "))
369 return map(bin, d[:-1].split(" "))
370 except:
370 except:
371 raise util.UnexpectedOutput(_("unexpected response:"), d)
371 raise util.UnexpectedOutput(_("unexpected response:"), d)
372
372
373 def branches(self, nodes):
373 def branches(self, nodes):
374 n = " ".join(map(hex, nodes))
374 n = " ".join(map(hex, nodes))
375 d = self.do_read("branches", nodes=n)
375 d = self.do_read("branches", nodes=n)
376 try:
376 try:
377 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
377 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
378 return br
378 return br
379 except:
379 except:
380 raise util.UnexpectedOutput(_("unexpected response:"), d)
380 raise util.UnexpectedOutput(_("unexpected response:"), d)
381
381
382 def between(self, pairs):
382 def between(self, pairs):
383 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
383 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
384 d = self.do_read("between", pairs=n)
384 d = self.do_read("between", pairs=n)
385 try:
385 try:
386 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
386 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
387 return p
387 return p
388 except:
388 except:
389 raise util.UnexpectedOutput(_("unexpected response:"), d)
389 raise util.UnexpectedOutput(_("unexpected response:"), d)
390
390
391 def changegroup(self, nodes, kind):
391 def changegroup(self, nodes, kind):
392 n = " ".join(map(hex, nodes))
392 n = " ".join(map(hex, nodes))
393 f = self.do_cmd("changegroup", roots=n)
393 f = self.do_cmd("changegroup", roots=n)
394 return util.chunkbuffer(zgenerator(f))
394 return util.chunkbuffer(zgenerator(f))
395
395
396 def changegroupsubset(self, bases, heads, source):
396 def changegroupsubset(self, bases, heads, source):
397 self.requirecap('changegroupsubset', _('look up remote changes'))
397 self.requirecap('changegroupsubset', _('look up remote changes'))
398 baselst = " ".join([hex(n) for n in bases])
398 baselst = " ".join([hex(n) for n in bases])
399 headlst = " ".join([hex(n) for n in heads])
399 headlst = " ".join([hex(n) for n in heads])
400 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
400 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
401 return util.chunkbuffer(zgenerator(f))
401 return util.chunkbuffer(zgenerator(f))
402
402
403 def unbundle(self, cg, heads, source):
403 def unbundle(self, cg, heads, source):
404 # have to stream bundle to a temp file because we do not have
404 # have to stream bundle to a temp file because we do not have
405 # http 1.1 chunked transfer.
405 # http 1.1 chunked transfer.
406
406
407 type = ""
407 type = ""
408 types = self.capable('unbundle')
408 types = self.capable('unbundle')
409 # servers older than d1b16a746db6 will send 'unbundle' as a
409 # servers older than d1b16a746db6 will send 'unbundle' as a
410 # boolean capability
410 # boolean capability
411 try:
411 try:
412 types = types.split(',')
412 types = types.split(',')
413 except AttributeError:
413 except AttributeError:
414 types = [""]
414 types = [""]
415 if types:
415 if types:
416 for x in types:
416 for x in types:
417 if x in changegroup.bundletypes:
417 if x in changegroup.bundletypes:
418 type = x
418 type = x
419 break
419 break
420
420
421 tempname = changegroup.writebundle(cg, None, type)
421 tempname = changegroup.writebundle(cg, None, type)
422 fp = httpsendfile(tempname, "rb")
422 fp = httpsendfile(tempname, "rb")
423 try:
423 try:
424 try:
424 try:
425 rfp = self.do_cmd(
425 resp = self.do_read(
426 'unbundle', data=fp,
426 'unbundle', data=fp,
427 headers={'Content-Type': 'application/octet-stream'},
427 headers={'Content-Type': 'application/octet-stream'},
428 heads=' '.join(map(hex, heads)))
428 heads=' '.join(map(hex, heads)))
429 resp_code, output = resp.split('\n', 1)
429 try:
430 try:
430 ret = int(rfp.readline())
431 ret = int(resp_code)
431 self.ui.write(rfp.read())
432 except ValueError, err:
432 return ret
433 raise util.UnexpectedOutput(
433 finally:
434 _('push failed (unexpected response):'), resp)
434 rfp.close()
435 self.ui.write(output)
436 return ret
435 except socket.error, err:
437 except socket.error, err:
436 if err[0] in (errno.ECONNRESET, errno.EPIPE):
438 if err[0] in (errno.ECONNRESET, errno.EPIPE):
437 raise util.Abort(_('push failed: %s') % err[1])
439 raise util.Abort(_('push failed: %s') % err[1])
438 raise util.Abort(err[1])
440 raise util.Abort(err[1])
439 finally:
441 finally:
440 fp.close()
442 fp.close()
441 os.unlink(tempname)
443 os.unlink(tempname)
442
444
443 def stream_out(self):
445 def stream_out(self):
444 return self.do_cmd('stream_out')
446 return self.do_cmd('stream_out')
445
447
446 class httpsrepository(httprepository):
448 class httpsrepository(httprepository):
447 def __init__(self, ui, path):
449 def __init__(self, ui, path):
448 if not has_https:
450 if not has_https:
449 raise util.Abort(_('Python support for SSL and HTTPS '
451 raise util.Abort(_('Python support for SSL and HTTPS '
450 'is not installed'))
452 'is not installed'))
451 httprepository.__init__(self, ui, path)
453 httprepository.__init__(self, ui, path)
452
454
453 def instance(ui, path, create):
455 def instance(ui, path, create):
454 if create:
456 if create:
455 raise util.Abort(_('cannot create new http repository'))
457 raise util.Abort(_('cannot create new http repository'))
456 if path.startswith('https:'):
458 if path.startswith('https:'):
457 return httpsrepository(ui, path)
459 return httpsrepository(ui, path)
458 return httprepository(ui, path)
460 return httprepository(ui, path)
General Comments 0
You need to be logged in to leave comments. Login now