##// END OF EJS Templates
http: len(x) fails if it doesn't fit into an int, use __len__() instead...
Benoit Boissinot -
r10491:d7e582ca stable
parent child Browse files
Show More
@@ -1,269 +1,274 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 of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from node import bin, hex, nullid
9 from node import bin, hex, nullid
10 from i18n import _
10 from i18n import _
11 import repo, changegroup, statichttprepo, error, url, util
11 import repo, changegroup, statichttprepo, error, url, util
12 import os, urllib, urllib2, urlparse, zlib, httplib
12 import os, urllib, urllib2, urlparse, zlib, httplib
13 import errno, socket
13 import errno, socket
14 import encoding
14 import encoding
15
15
16 def zgenerator(f):
16 def zgenerator(f):
17 zd = zlib.decompressobj()
17 zd = zlib.decompressobj()
18 try:
18 try:
19 for chunk in util.filechunkiter(f):
19 for chunk in util.filechunkiter(f):
20 yield zd.decompress(chunk)
20 yield zd.decompress(chunk)
21 except httplib.HTTPException:
21 except httplib.HTTPException:
22 raise IOError(None, _('connection ended unexpectedly'))
22 raise IOError(None, _('connection ended unexpectedly'))
23 yield zd.flush()
23 yield zd.flush()
24
24
25 class httprepository(repo.repository):
25 class httprepository(repo.repository):
26 def __init__(self, ui, path):
26 def __init__(self, ui, path):
27 self.path = path
27 self.path = path
28 self.caps = None
28 self.caps = None
29 self.handler = None
29 self.handler = None
30 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
30 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
31 if query or frag:
31 if query or frag:
32 raise util.Abort(_('unsupported URL component: "%s"') %
32 raise util.Abort(_('unsupported URL component: "%s"') %
33 (query or frag))
33 (query or frag))
34
34
35 # urllib cannot handle URLs with embedded user or passwd
35 # urllib cannot handle URLs with embedded user or passwd
36 self._url, authinfo = url.getauthinfo(path)
36 self._url, authinfo = url.getauthinfo(path)
37
37
38 self.ui = ui
38 self.ui = ui
39 self.ui.debug('using %s\n' % self._url)
39 self.ui.debug('using %s\n' % self._url)
40
40
41 self.urlopener = url.opener(ui, authinfo)
41 self.urlopener = url.opener(ui, authinfo)
42
42
43 def __del__(self):
43 def __del__(self):
44 for h in self.urlopener.handlers:
44 for h in self.urlopener.handlers:
45 h.close()
45 h.close()
46 if hasattr(h, "close_all"):
46 if hasattr(h, "close_all"):
47 h.close_all()
47 h.close_all()
48
48
49 def url(self):
49 def url(self):
50 return self.path
50 return self.path
51
51
52 # look up capabilities only when needed
52 # look up capabilities only when needed
53
53
54 def get_caps(self):
54 def get_caps(self):
55 if self.caps is None:
55 if self.caps is None:
56 try:
56 try:
57 self.caps = set(self.do_read('capabilities').split())
57 self.caps = set(self.do_read('capabilities').split())
58 except error.RepoError:
58 except error.RepoError:
59 self.caps = set()
59 self.caps = set()
60 self.ui.debug('capabilities: %s\n' %
60 self.ui.debug('capabilities: %s\n' %
61 (' '.join(self.caps or ['none'])))
61 (' '.join(self.caps or ['none'])))
62 return self.caps
62 return self.caps
63
63
64 capabilities = property(get_caps)
64 capabilities = property(get_caps)
65
65
66 def lock(self):
66 def lock(self):
67 raise util.Abort(_('operation not supported over http'))
67 raise util.Abort(_('operation not supported over http'))
68
68
69 def do_cmd(self, cmd, **args):
69 def do_cmd(self, cmd, **args):
70 data = args.pop('data', None)
70 data = args.pop('data', None)
71 headers = args.pop('headers', {})
71 headers = args.pop('headers', {})
72 self.ui.debug("sending %s command\n" % cmd)
72 self.ui.debug("sending %s command\n" % cmd)
73 q = {"cmd": cmd}
73 q = {"cmd": cmd}
74 q.update(args)
74 q.update(args)
75 qs = '?%s' % urllib.urlencode(q)
75 qs = '?%s' % urllib.urlencode(q)
76 cu = "%s%s" % (self._url, qs)
76 cu = "%s%s" % (self._url, qs)
77 req = urllib2.Request(cu, data, headers)
78 if data is not None:
79 # len(data) is broken if data doesn't fit into Py_ssize_t
80 # add the header ourself to avoid OverflowError
81 size = data.__len__()
82 self.ui.debug("sending %s bytes\n" % size)
83 req.add_unredirected_header('Content-Length', '%d' % size)
77 try:
84 try:
78 if data:
85 resp = self.urlopener.open(req)
79 self.ui.debug("sending %s bytes\n" % len(data))
80 resp = self.urlopener.open(urllib2.Request(cu, data, headers))
81 except urllib2.HTTPError, inst:
86 except urllib2.HTTPError, inst:
82 if inst.code == 401:
87 if inst.code == 401:
83 raise util.Abort(_('authorization failed'))
88 raise util.Abort(_('authorization failed'))
84 raise
89 raise
85 except httplib.HTTPException, inst:
90 except httplib.HTTPException, inst:
86 self.ui.debug('http error while sending %s command\n' % cmd)
91 self.ui.debug('http error while sending %s command\n' % cmd)
87 self.ui.traceback()
92 self.ui.traceback()
88 raise IOError(None, inst)
93 raise IOError(None, inst)
89 except IndexError:
94 except IndexError:
90 # this only happens with Python 2.3, later versions raise URLError
95 # this only happens with Python 2.3, later versions raise URLError
91 raise util.Abort(_('http error, possibly caused by proxy setting'))
96 raise util.Abort(_('http error, possibly caused by proxy setting'))
92 # record the url we got redirected to
97 # record the url we got redirected to
93 resp_url = resp.geturl()
98 resp_url = resp.geturl()
94 if resp_url.endswith(qs):
99 if resp_url.endswith(qs):
95 resp_url = resp_url[:-len(qs)]
100 resp_url = resp_url[:-len(qs)]
96 if self._url.rstrip('/') != resp_url.rstrip('/'):
101 if self._url.rstrip('/') != resp_url.rstrip('/'):
97 self.ui.status(_('real URL is %s\n') % resp_url)
102 self.ui.status(_('real URL is %s\n') % resp_url)
98 self._url = resp_url
103 self._url = resp_url
99 try:
104 try:
100 proto = resp.getheader('content-type')
105 proto = resp.getheader('content-type')
101 except AttributeError:
106 except AttributeError:
102 proto = resp.headers['content-type']
107 proto = resp.headers['content-type']
103
108
104 safeurl = url.hidepassword(self._url)
109 safeurl = url.hidepassword(self._url)
105 # accept old "text/plain" and "application/hg-changegroup" for now
110 # accept old "text/plain" and "application/hg-changegroup" for now
106 if not (proto.startswith('application/mercurial-') or
111 if not (proto.startswith('application/mercurial-') or
107 proto.startswith('text/plain') or
112 proto.startswith('text/plain') or
108 proto.startswith('application/hg-changegroup')):
113 proto.startswith('application/hg-changegroup')):
109 self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu))
114 self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu))
110 raise error.RepoError(
115 raise error.RepoError(
111 _("'%s' does not appear to be an hg repository:\n"
116 _("'%s' does not appear to be an hg repository:\n"
112 "---%%<--- (%s)\n%s\n---%%<---\n")
117 "---%%<--- (%s)\n%s\n---%%<---\n")
113 % (safeurl, proto, resp.read()))
118 % (safeurl, proto, resp.read()))
114
119
115 if proto.startswith('application/mercurial-'):
120 if proto.startswith('application/mercurial-'):
116 try:
121 try:
117 version = proto.split('-', 1)[1]
122 version = proto.split('-', 1)[1]
118 version_info = tuple([int(n) for n in version.split('.')])
123 version_info = tuple([int(n) for n in version.split('.')])
119 except ValueError:
124 except ValueError:
120 raise error.RepoError(_("'%s' sent a broken Content-Type "
125 raise error.RepoError(_("'%s' sent a broken Content-Type "
121 "header (%s)") % (safeurl, proto))
126 "header (%s)") % (safeurl, proto))
122 if version_info > (0, 1):
127 if version_info > (0, 1):
123 raise error.RepoError(_("'%s' uses newer protocol %s") %
128 raise error.RepoError(_("'%s' uses newer protocol %s") %
124 (safeurl, version))
129 (safeurl, version))
125
130
126 return resp
131 return resp
127
132
128 def do_read(self, cmd, **args):
133 def do_read(self, cmd, **args):
129 fp = self.do_cmd(cmd, **args)
134 fp = self.do_cmd(cmd, **args)
130 try:
135 try:
131 return fp.read()
136 return fp.read()
132 finally:
137 finally:
133 # if using keepalive, allow connection to be reused
138 # if using keepalive, allow connection to be reused
134 fp.close()
139 fp.close()
135
140
136 def lookup(self, key):
141 def lookup(self, key):
137 self.requirecap('lookup', _('look up remote revision'))
142 self.requirecap('lookup', _('look up remote revision'))
138 d = self.do_cmd("lookup", key = key).read()
143 d = self.do_cmd("lookup", key = key).read()
139 success, data = d[:-1].split(' ', 1)
144 success, data = d[:-1].split(' ', 1)
140 if int(success):
145 if int(success):
141 return bin(data)
146 return bin(data)
142 raise error.RepoError(data)
147 raise error.RepoError(data)
143
148
144 def heads(self):
149 def heads(self):
145 d = self.do_read("heads")
150 d = self.do_read("heads")
146 try:
151 try:
147 return map(bin, d[:-1].split(" "))
152 return map(bin, d[:-1].split(" "))
148 except:
153 except:
149 raise error.ResponseError(_("unexpected response:"), d)
154 raise error.ResponseError(_("unexpected response:"), d)
150
155
151 def branchmap(self):
156 def branchmap(self):
152 d = self.do_read("branchmap")
157 d = self.do_read("branchmap")
153 try:
158 try:
154 branchmap = {}
159 branchmap = {}
155 for branchpart in d.splitlines():
160 for branchpart in d.splitlines():
156 branchheads = branchpart.split(' ')
161 branchheads = branchpart.split(' ')
157 branchname = urllib.unquote(branchheads[0])
162 branchname = urllib.unquote(branchheads[0])
158 # Earlier servers (1.3.x) send branch names in (their) local
163 # Earlier servers (1.3.x) send branch names in (their) local
159 # charset. The best we can do is assume it's identical to our
164 # charset. The best we can do is assume it's identical to our
160 # own local charset, in case it's not utf-8.
165 # own local charset, in case it's not utf-8.
161 try:
166 try:
162 branchname.decode('utf-8')
167 branchname.decode('utf-8')
163 except UnicodeDecodeError:
168 except UnicodeDecodeError:
164 branchname = encoding.fromlocal(branchname)
169 branchname = encoding.fromlocal(branchname)
165 branchheads = [bin(x) for x in branchheads[1:]]
170 branchheads = [bin(x) for x in branchheads[1:]]
166 branchmap[branchname] = branchheads
171 branchmap[branchname] = branchheads
167 return branchmap
172 return branchmap
168 except:
173 except:
169 raise error.ResponseError(_("unexpected response:"), d)
174 raise error.ResponseError(_("unexpected response:"), d)
170
175
171 def branches(self, nodes):
176 def branches(self, nodes):
172 n = " ".join(map(hex, nodes))
177 n = " ".join(map(hex, nodes))
173 d = self.do_read("branches", nodes=n)
178 d = self.do_read("branches", nodes=n)
174 try:
179 try:
175 br = [tuple(map(bin, b.split(" "))) for b in d.splitlines()]
180 br = [tuple(map(bin, b.split(" "))) for b in d.splitlines()]
176 return br
181 return br
177 except:
182 except:
178 raise error.ResponseError(_("unexpected response:"), d)
183 raise error.ResponseError(_("unexpected response:"), d)
179
184
180 def between(self, pairs):
185 def between(self, pairs):
181 batch = 8 # avoid giant requests
186 batch = 8 # avoid giant requests
182 r = []
187 r = []
183 for i in xrange(0, len(pairs), batch):
188 for i in xrange(0, len(pairs), batch):
184 n = " ".join(["-".join(map(hex, p)) for p in pairs[i:i + batch]])
189 n = " ".join(["-".join(map(hex, p)) for p in pairs[i:i + batch]])
185 d = self.do_read("between", pairs=n)
190 d = self.do_read("between", pairs=n)
186 try:
191 try:
187 r += [l and map(bin, l.split(" ")) or []
192 r += [l and map(bin, l.split(" ")) or []
188 for l in d.splitlines()]
193 for l in d.splitlines()]
189 except:
194 except:
190 raise error.ResponseError(_("unexpected response:"), d)
195 raise error.ResponseError(_("unexpected response:"), d)
191 return r
196 return r
192
197
193 def changegroup(self, nodes, kind):
198 def changegroup(self, nodes, kind):
194 n = " ".join(map(hex, nodes))
199 n = " ".join(map(hex, nodes))
195 f = self.do_cmd("changegroup", roots=n)
200 f = self.do_cmd("changegroup", roots=n)
196 return util.chunkbuffer(zgenerator(f))
201 return util.chunkbuffer(zgenerator(f))
197
202
198 def changegroupsubset(self, bases, heads, source):
203 def changegroupsubset(self, bases, heads, source):
199 self.requirecap('changegroupsubset', _('look up remote changes'))
204 self.requirecap('changegroupsubset', _('look up remote changes'))
200 baselst = " ".join([hex(n) for n in bases])
205 baselst = " ".join([hex(n) for n in bases])
201 headlst = " ".join([hex(n) for n in heads])
206 headlst = " ".join([hex(n) for n in heads])
202 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
207 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
203 return util.chunkbuffer(zgenerator(f))
208 return util.chunkbuffer(zgenerator(f))
204
209
205 def unbundle(self, cg, heads, source):
210 def unbundle(self, cg, heads, source):
206 # have to stream bundle to a temp file because we do not have
211 # have to stream bundle to a temp file because we do not have
207 # http 1.1 chunked transfer.
212 # http 1.1 chunked transfer.
208
213
209 type = ""
214 type = ""
210 types = self.capable('unbundle')
215 types = self.capable('unbundle')
211 # servers older than d1b16a746db6 will send 'unbundle' as a
216 # servers older than d1b16a746db6 will send 'unbundle' as a
212 # boolean capability
217 # boolean capability
213 try:
218 try:
214 types = types.split(',')
219 types = types.split(',')
215 except AttributeError:
220 except AttributeError:
216 types = [""]
221 types = [""]
217 if types:
222 if types:
218 for x in types:
223 for x in types:
219 if x in changegroup.bundletypes:
224 if x in changegroup.bundletypes:
220 type = x
225 type = x
221 break
226 break
222
227
223 tempname = changegroup.writebundle(cg, None, type)
228 tempname = changegroup.writebundle(cg, None, type)
224 fp = url.httpsendfile(tempname, "rb")
229 fp = url.httpsendfile(tempname, "rb")
225 try:
230 try:
226 try:
231 try:
227 resp = self.do_read(
232 resp = self.do_read(
228 'unbundle', data=fp,
233 'unbundle', data=fp,
229 headers={'Content-Type': 'application/octet-stream'},
234 headers={'Content-Type': 'application/octet-stream'},
230 heads=' '.join(map(hex, heads)))
235 heads=' '.join(map(hex, heads)))
231 resp_code, output = resp.split('\n', 1)
236 resp_code, output = resp.split('\n', 1)
232 try:
237 try:
233 ret = int(resp_code)
238 ret = int(resp_code)
234 except ValueError, err:
239 except ValueError, err:
235 raise error.ResponseError(
240 raise error.ResponseError(
236 _('push failed (unexpected response):'), resp)
241 _('push failed (unexpected response):'), resp)
237 self.ui.write(output)
242 self.ui.write(output)
238 return ret
243 return ret
239 except socket.error, err:
244 except socket.error, err:
240 if err[0] in (errno.ECONNRESET, errno.EPIPE):
245 if err[0] in (errno.ECONNRESET, errno.EPIPE):
241 raise util.Abort(_('push failed: %s') % err[1])
246 raise util.Abort(_('push failed: %s') % err[1])
242 raise util.Abort(err[1])
247 raise util.Abort(err[1])
243 finally:
248 finally:
244 fp.close()
249 fp.close()
245 os.unlink(tempname)
250 os.unlink(tempname)
246
251
247 def stream_out(self):
252 def stream_out(self):
248 return self.do_cmd('stream_out')
253 return self.do_cmd('stream_out')
249
254
250 class httpsrepository(httprepository):
255 class httpsrepository(httprepository):
251 def __init__(self, ui, path):
256 def __init__(self, ui, path):
252 if not url.has_https:
257 if not url.has_https:
253 raise util.Abort(_('Python support for SSL and HTTPS '
258 raise util.Abort(_('Python support for SSL and HTTPS '
254 'is not installed'))
259 'is not installed'))
255 httprepository.__init__(self, ui, path)
260 httprepository.__init__(self, ui, path)
256
261
257 def instance(ui, path, create):
262 def instance(ui, path, create):
258 if create:
263 if create:
259 raise util.Abort(_('cannot create new http repository'))
264 raise util.Abort(_('cannot create new http repository'))
260 try:
265 try:
261 if path.startswith('https:'):
266 if path.startswith('https:'):
262 inst = httpsrepository(ui, path)
267 inst = httpsrepository(ui, path)
263 else:
268 else:
264 inst = httprepository(ui, path)
269 inst = httprepository(ui, path)
265 inst.between([(nullid, nullid)])
270 inst.between([(nullid, nullid)])
266 return inst
271 return inst
267 except error.RepoError:
272 except error.RepoError:
268 ui.note('(falling back to static-http)\n')
273 ui.note('(falling back to static-http)\n')
269 return statichttprepo.instance(ui, "static-" + path, create)
274 return statichttprepo.instance(ui, "static-" + path, create)
General Comments 0
You need to be logged in to leave comments. Login now