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