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