##// END OF EJS Templates
httprepo: give self._url and the netloc to the password manager...
Alexis S. L. Carvalho -
r5526:d5b9c74c default
parent child Browse files
Show More
@@ -1,458 +1,462 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 repo, os, urllib, urllib2, urlparse, zlib, util, httplib
12 import repo, 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
124 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
125 # it doesn't know about the auth type requested. This can happen 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.
126 # somebody is using BasicAuth and types a bad password.
127 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
127 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
128 def http_error_auth_reqed(self, auth_header, host, req, headers):
128 def http_error_auth_reqed(self, auth_header, host, req, headers):
129 try:
129 try:
130 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
130 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
131 self, auth_header, host, req, headers)
131 self, auth_header, host, req, headers)
132 except ValueError, inst:
132 except ValueError, inst:
133 arg = inst.args[0]
133 arg = inst.args[0]
134 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
134 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
135 return
135 return
136 raise
136 raise
137
137
138 def zgenerator(f):
138 def zgenerator(f):
139 zd = zlib.decompressobj()
139 zd = zlib.decompressobj()
140 try:
140 try:
141 for chunk in util.filechunkiter(f):
141 for chunk in util.filechunkiter(f):
142 yield zd.decompress(chunk)
142 yield zd.decompress(chunk)
143 except httplib.HTTPException, inst:
143 except httplib.HTTPException, inst:
144 raise IOError(None, _('connection ended unexpectedly'))
144 raise IOError(None, _('connection ended unexpectedly'))
145 yield zd.flush()
145 yield zd.flush()
146
146
147 _safe = ('abcdefghijklmnopqrstuvwxyz'
147 _safe = ('abcdefghijklmnopqrstuvwxyz'
148 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
148 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
149 '0123456789' '_.-/')
149 '0123456789' '_.-/')
150 _safeset = None
150 _safeset = None
151 _hex = None
151 _hex = None
152 def quotepath(path):
152 def quotepath(path):
153 '''quote the path part of a URL
153 '''quote the path part of a URL
154
154
155 This is similar to urllib.quote, but it also tries to avoid
155 This is similar to urllib.quote, but it also tries to avoid
156 quoting things twice (inspired by wget):
156 quoting things twice (inspired by wget):
157
157
158 >>> quotepath('abc def')
158 >>> quotepath('abc def')
159 'abc%20def'
159 'abc%20def'
160 >>> quotepath('abc%20def')
160 >>> quotepath('abc%20def')
161 'abc%20def'
161 'abc%20def'
162 >>> quotepath('abc%20 def')
162 >>> quotepath('abc%20 def')
163 'abc%20%20def'
163 'abc%20%20def'
164 >>> quotepath('abc def%20')
164 >>> quotepath('abc def%20')
165 'abc%20def%20'
165 'abc%20def%20'
166 >>> quotepath('abc def%2')
166 >>> quotepath('abc def%2')
167 'abc%20def%252'
167 'abc%20def%252'
168 >>> quotepath('abc def%')
168 >>> quotepath('abc def%')
169 'abc%20def%25'
169 'abc%20def%25'
170 '''
170 '''
171 global _safeset, _hex
171 global _safeset, _hex
172 if _safeset is None:
172 if _safeset is None:
173 _safeset = util.set(_safe)
173 _safeset = util.set(_safe)
174 _hex = util.set('abcdefABCDEF0123456789')
174 _hex = util.set('abcdefABCDEF0123456789')
175 l = list(path)
175 l = list(path)
176 for i in xrange(len(l)):
176 for i in xrange(len(l)):
177 c = l[i]
177 c = l[i]
178 if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
178 if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
179 pass
179 pass
180 elif c not in _safeset:
180 elif c not in _safeset:
181 l[i] = '%%%02X' % ord(c)
181 l[i] = '%%%02X' % ord(c)
182 return ''.join(l)
182 return ''.join(l)
183
183
184 class httprepository(remoterepository):
184 class httprepository(remoterepository):
185 def __init__(self, ui, path):
185 def __init__(self, ui, path):
186 self.path = path
186 self.path = path
187 self.caps = None
187 self.caps = None
188 self.handler = None
188 self.handler = None
189 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
189 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
190 if query or frag:
190 if query or frag:
191 raise util.Abort(_('unsupported URL component: "%s"') %
191 raise util.Abort(_('unsupported URL component: "%s"') %
192 (query or frag))
192 (query or frag))
193 if not urlpath:
193 if not urlpath:
194 urlpath = '/'
194 urlpath = '/'
195 urlpath = quotepath(urlpath)
195 urlpath = quotepath(urlpath)
196 host, port, user, passwd = netlocsplit(netloc)
196 host, port, user, passwd = netlocsplit(netloc)
197
197
198 # urllib cannot handle URLs with embedded user or passwd
198 # urllib cannot handle URLs with embedded user or passwd
199 self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
199 self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
200 urlpath, '', ''))
200 urlpath, '', ''))
201 self.ui = ui
201 self.ui = ui
202 self.ui.debug(_('using %s\n') % self._url)
202 self.ui.debug(_('using %s\n') % self._url)
203
203
204 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
204 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
205 # XXX proxyauthinfo = None
205 # XXX proxyauthinfo = None
206 self.handler = httphandler()
206 self.handler = httphandler()
207 handlers = [self.handler]
207 handlers = [self.handler]
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 os.environ.has_key(env):
250 if os.environ.has_key(env):
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 passmgr.add_password(None, host, user, passwd or '')
259 netloc = host
260 if port:
261 netloc += ':' + port
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 '')
260
264
261 handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
265 handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
262 httpdigestauthhandler(passmgr)))
266 httpdigestauthhandler(passmgr)))
263 opener = urllib2.build_opener(*handlers)
267 opener = urllib2.build_opener(*handlers)
264
268
265 # 1.0 here is the _protocol_ version
269 # 1.0 here is the _protocol_ version
266 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
270 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
267 urllib2.install_opener(opener)
271 urllib2.install_opener(opener)
268
272
269 def __del__(self):
273 def __del__(self):
270 if self.handler:
274 if self.handler:
271 self.handler.close_all()
275 self.handler.close_all()
272 self.handler = None
276 self.handler = None
273
277
274 def url(self):
278 def url(self):
275 return self.path
279 return self.path
276
280
277 # look up capabilities only when needed
281 # look up capabilities only when needed
278
282
279 def get_caps(self):
283 def get_caps(self):
280 if self.caps is None:
284 if self.caps is None:
281 try:
285 try:
282 self.caps = util.set(self.do_read('capabilities').split())
286 self.caps = util.set(self.do_read('capabilities').split())
283 except repo.RepoError:
287 except repo.RepoError:
284 self.caps = util.set()
288 self.caps = util.set()
285 self.ui.debug(_('capabilities: %s\n') %
289 self.ui.debug(_('capabilities: %s\n') %
286 (' '.join(self.caps or ['none'])))
290 (' '.join(self.caps or ['none'])))
287 return self.caps
291 return self.caps
288
292
289 capabilities = property(get_caps)
293 capabilities = property(get_caps)
290
294
291 def lock(self):
295 def lock(self):
292 raise util.Abort(_('operation not supported over http'))
296 raise util.Abort(_('operation not supported over http'))
293
297
294 def do_cmd(self, cmd, **args):
298 def do_cmd(self, cmd, **args):
295 data = args.pop('data', None)
299 data = args.pop('data', None)
296 headers = args.pop('headers', {})
300 headers = args.pop('headers', {})
297 self.ui.debug(_("sending %s command\n") % cmd)
301 self.ui.debug(_("sending %s command\n") % cmd)
298 q = {"cmd": cmd}
302 q = {"cmd": cmd}
299 q.update(args)
303 q.update(args)
300 qs = '?%s' % urllib.urlencode(q)
304 qs = '?%s' % urllib.urlencode(q)
301 cu = "%s%s" % (self._url, qs)
305 cu = "%s%s" % (self._url, qs)
302 try:
306 try:
303 if data:
307 if data:
304 self.ui.debug(_("sending %s bytes\n") % len(data))
308 self.ui.debug(_("sending %s bytes\n") % len(data))
305 resp = urllib2.urlopen(request(cu, data, headers))
309 resp = urllib2.urlopen(request(cu, data, headers))
306 except urllib2.HTTPError, inst:
310 except urllib2.HTTPError, inst:
307 if inst.code == 401:
311 if inst.code == 401:
308 raise util.Abort(_('authorization failed'))
312 raise util.Abort(_('authorization failed'))
309 raise
313 raise
310 except httplib.HTTPException, inst:
314 except httplib.HTTPException, inst:
311 self.ui.debug(_('http error while sending %s command\n') % cmd)
315 self.ui.debug(_('http error while sending %s command\n') % cmd)
312 self.ui.print_exc()
316 self.ui.print_exc()
313 raise IOError(None, inst)
317 raise IOError(None, inst)
314 except IndexError:
318 except IndexError:
315 # this only happens with Python 2.3, later versions raise URLError
319 # this only happens with Python 2.3, later versions raise URLError
316 raise util.Abort(_('http error, possibly caused by proxy setting'))
320 raise util.Abort(_('http error, possibly caused by proxy setting'))
317 # record the url we got redirected to
321 # record the url we got redirected to
318 resp_url = resp.geturl()
322 resp_url = resp.geturl()
319 if resp_url.endswith(qs):
323 if resp_url.endswith(qs):
320 resp_url = resp_url[:-len(qs)]
324 resp_url = resp_url[:-len(qs)]
321 if self._url != resp_url:
325 if self._url != resp_url:
322 self.ui.status(_('real URL is %s\n') % resp_url)
326 self.ui.status(_('real URL is %s\n') % resp_url)
323 self._url = resp_url
327 self._url = resp_url
324 try:
328 try:
325 proto = resp.getheader('content-type')
329 proto = resp.getheader('content-type')
326 except AttributeError:
330 except AttributeError:
327 proto = resp.headers['content-type']
331 proto = resp.headers['content-type']
328
332
329 # accept old "text/plain" and "application/hg-changegroup" for now
333 # accept old "text/plain" and "application/hg-changegroup" for now
330 if not (proto.startswith('application/mercurial-') or
334 if not (proto.startswith('application/mercurial-') or
331 proto.startswith('text/plain') or
335 proto.startswith('text/plain') or
332 proto.startswith('application/hg-changegroup')):
336 proto.startswith('application/hg-changegroup')):
333 self.ui.debug(_("Requested URL: '%s'\n") % cu)
337 self.ui.debug(_("Requested URL: '%s'\n") % cu)
334 raise repo.RepoError(_("'%s' does not appear to be an hg repository")
338 raise repo.RepoError(_("'%s' does not appear to be an hg repository")
335 % self._url)
339 % self._url)
336
340
337 if proto.startswith('application/mercurial-'):
341 if proto.startswith('application/mercurial-'):
338 try:
342 try:
339 version = proto.split('-', 1)[1]
343 version = proto.split('-', 1)[1]
340 version_info = tuple([int(n) for n in version.split('.')])
344 version_info = tuple([int(n) for n in version.split('.')])
341 except ValueError:
345 except ValueError:
342 raise repo.RepoError(_("'%s' sent a broken Content-type "
346 raise repo.RepoError(_("'%s' sent a broken Content-type "
343 "header (%s)") % (self._url, proto))
347 "header (%s)") % (self._url, proto))
344 if version_info > (0, 1):
348 if version_info > (0, 1):
345 raise repo.RepoError(_("'%s' uses newer protocol %s") %
349 raise repo.RepoError(_("'%s' uses newer protocol %s") %
346 (self._url, version))
350 (self._url, version))
347
351
348 return resp
352 return resp
349
353
350 def do_read(self, cmd, **args):
354 def do_read(self, cmd, **args):
351 fp = self.do_cmd(cmd, **args)
355 fp = self.do_cmd(cmd, **args)
352 try:
356 try:
353 return fp.read()
357 return fp.read()
354 finally:
358 finally:
355 # if using keepalive, allow connection to be reused
359 # if using keepalive, allow connection to be reused
356 fp.close()
360 fp.close()
357
361
358 def lookup(self, key):
362 def lookup(self, key):
359 self.requirecap('lookup', _('look up remote revision'))
363 self.requirecap('lookup', _('look up remote revision'))
360 d = self.do_cmd("lookup", key = key).read()
364 d = self.do_cmd("lookup", key = key).read()
361 success, data = d[:-1].split(' ', 1)
365 success, data = d[:-1].split(' ', 1)
362 if int(success):
366 if int(success):
363 return bin(data)
367 return bin(data)
364 raise repo.RepoError(data)
368 raise repo.RepoError(data)
365
369
366 def heads(self):
370 def heads(self):
367 d = self.do_read("heads")
371 d = self.do_read("heads")
368 try:
372 try:
369 return map(bin, d[:-1].split(" "))
373 return map(bin, d[:-1].split(" "))
370 except:
374 except:
371 raise util.UnexpectedOutput(_("unexpected response:"), d)
375 raise util.UnexpectedOutput(_("unexpected response:"), d)
372
376
373 def branches(self, nodes):
377 def branches(self, nodes):
374 n = " ".join(map(hex, nodes))
378 n = " ".join(map(hex, nodes))
375 d = self.do_read("branches", nodes=n)
379 d = self.do_read("branches", nodes=n)
376 try:
380 try:
377 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
381 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
378 return br
382 return br
379 except:
383 except:
380 raise util.UnexpectedOutput(_("unexpected response:"), d)
384 raise util.UnexpectedOutput(_("unexpected response:"), d)
381
385
382 def between(self, pairs):
386 def between(self, pairs):
383 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
387 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
384 d = self.do_read("between", pairs=n)
388 d = self.do_read("between", pairs=n)
385 try:
389 try:
386 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
390 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
387 return p
391 return p
388 except:
392 except:
389 raise util.UnexpectedOutput(_("unexpected response:"), d)
393 raise util.UnexpectedOutput(_("unexpected response:"), d)
390
394
391 def changegroup(self, nodes, kind):
395 def changegroup(self, nodes, kind):
392 n = " ".join(map(hex, nodes))
396 n = " ".join(map(hex, nodes))
393 f = self.do_cmd("changegroup", roots=n)
397 f = self.do_cmd("changegroup", roots=n)
394 return util.chunkbuffer(zgenerator(f))
398 return util.chunkbuffer(zgenerator(f))
395
399
396 def changegroupsubset(self, bases, heads, source):
400 def changegroupsubset(self, bases, heads, source):
397 self.requirecap('changegroupsubset', _('look up remote changes'))
401 self.requirecap('changegroupsubset', _('look up remote changes'))
398 baselst = " ".join([hex(n) for n in bases])
402 baselst = " ".join([hex(n) for n in bases])
399 headlst = " ".join([hex(n) for n in heads])
403 headlst = " ".join([hex(n) for n in heads])
400 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
404 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
401 return util.chunkbuffer(zgenerator(f))
405 return util.chunkbuffer(zgenerator(f))
402
406
403 def unbundle(self, cg, heads, source):
407 def unbundle(self, cg, heads, source):
404 # have to stream bundle to a temp file because we do not have
408 # have to stream bundle to a temp file because we do not have
405 # http 1.1 chunked transfer.
409 # http 1.1 chunked transfer.
406
410
407 type = ""
411 type = ""
408 types = self.capable('unbundle')
412 types = self.capable('unbundle')
409 # servers older than d1b16a746db6 will send 'unbundle' as a
413 # servers older than d1b16a746db6 will send 'unbundle' as a
410 # boolean capability
414 # boolean capability
411 try:
415 try:
412 types = types.split(',')
416 types = types.split(',')
413 except AttributeError:
417 except AttributeError:
414 types = [""]
418 types = [""]
415 if types:
419 if types:
416 for x in types:
420 for x in types:
417 if x in changegroup.bundletypes:
421 if x in changegroup.bundletypes:
418 type = x
422 type = x
419 break
423 break
420
424
421 tempname = changegroup.writebundle(cg, None, type)
425 tempname = changegroup.writebundle(cg, None, type)
422 fp = httpsendfile(tempname, "rb")
426 fp = httpsendfile(tempname, "rb")
423 try:
427 try:
424 try:
428 try:
425 rfp = self.do_cmd(
429 rfp = self.do_cmd(
426 'unbundle', data=fp,
430 'unbundle', data=fp,
427 headers={'content-type': 'application/octet-stream'},
431 headers={'content-type': 'application/octet-stream'},
428 heads=' '.join(map(hex, heads)))
432 heads=' '.join(map(hex, heads)))
429 try:
433 try:
430 ret = int(rfp.readline())
434 ret = int(rfp.readline())
431 self.ui.write(rfp.read())
435 self.ui.write(rfp.read())
432 return ret
436 return ret
433 finally:
437 finally:
434 rfp.close()
438 rfp.close()
435 except socket.error, err:
439 except socket.error, err:
436 if err[0] in (errno.ECONNRESET, errno.EPIPE):
440 if err[0] in (errno.ECONNRESET, errno.EPIPE):
437 raise util.Abort(_('push failed: %s') % err[1])
441 raise util.Abort(_('push failed: %s') % err[1])
438 raise util.Abort(err[1])
442 raise util.Abort(err[1])
439 finally:
443 finally:
440 fp.close()
444 fp.close()
441 os.unlink(tempname)
445 os.unlink(tempname)
442
446
443 def stream_out(self):
447 def stream_out(self):
444 return self.do_cmd('stream_out')
448 return self.do_cmd('stream_out')
445
449
446 class httpsrepository(httprepository):
450 class httpsrepository(httprepository):
447 def __init__(self, ui, path):
451 def __init__(self, ui, path):
448 if not has_https:
452 if not has_https:
449 raise util.Abort(_('Python support for SSL and HTTPS '
453 raise util.Abort(_('Python support for SSL and HTTPS '
450 'is not installed'))
454 'is not installed'))
451 httprepository.__init__(self, ui, path)
455 httprepository.__init__(self, ui, path)
452
456
453 def instance(ui, path, create):
457 def instance(ui, path, create):
454 if create:
458 if create:
455 raise util.Abort(_('cannot create new http repository'))
459 raise util.Abort(_('cannot create new http repository'))
456 if path.startswith('https:'):
460 if path.startswith('https:'):
457 return httpsrepository(ui, path)
461 return httpsrepository(ui, path)
458 return httprepository(ui, path)
462 return httprepository(ui, path)
General Comments 0
You need to be logged in to leave comments. Login now