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