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