##// END OF EJS Templates
httprepo: always store the response url (issue1968)...
Steve Borho -
r10208:37c4ce51 default
parent child Browse files
Show More
@@ -1,267 +1,267 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, incorporated herein by reference.
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 77 try:
78 78 if data:
79 79 self.ui.debug("sending %s bytes\n" % len(data))
80 80 resp = self.urlopener.open(urllib2.Request(cu, data, headers))
81 81 except urllib2.HTTPError, inst:
82 82 if inst.code == 401:
83 83 raise util.Abort(_('authorization failed'))
84 84 raise
85 85 except httplib.HTTPException, inst:
86 86 self.ui.debug('http error while sending %s command\n' % cmd)
87 87 self.ui.traceback()
88 88 raise IOError(None, inst)
89 89 except IndexError:
90 90 # this only happens with Python 2.3, later versions raise URLError
91 91 raise util.Abort(_('http error, possibly caused by proxy setting'))
92 92 # record the url we got redirected to
93 93 resp_url = resp.geturl()
94 94 if resp_url.endswith(qs):
95 95 resp_url = resp_url[:-len(qs)]
96 96 if self._url.rstrip('/') != resp_url.rstrip('/'):
97 97 self.ui.status(_('real URL is %s\n') % resp_url)
98 self._url = resp_url
98 self._url = resp_url
99 99 try:
100 100 proto = resp.getheader('content-type')
101 101 except AttributeError:
102 102 proto = resp.headers['content-type']
103 103
104 104 safeurl = url.hidepassword(self._url)
105 105 # accept old "text/plain" and "application/hg-changegroup" for now
106 106 if not (proto.startswith('application/mercurial-') or
107 107 proto.startswith('text/plain') or
108 108 proto.startswith('application/hg-changegroup')):
109 109 self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu))
110 110 raise error.RepoError(_("'%s' does not appear to be an hg repository:\n"
111 111 "---%%<--- (%s)\n%s\n---%%<---\n")
112 112 % (safeurl, proto, resp.read()))
113 113
114 114 if proto.startswith('application/mercurial-'):
115 115 try:
116 116 version = proto.split('-', 1)[1]
117 117 version_info = tuple([int(n) for n in version.split('.')])
118 118 except ValueError:
119 119 raise error.RepoError(_("'%s' sent a broken Content-Type "
120 120 "header (%s)") % (safeurl, proto))
121 121 if version_info > (0, 1):
122 122 raise error.RepoError(_("'%s' uses newer protocol %s") %
123 123 (safeurl, version))
124 124
125 125 return resp
126 126
127 127 def do_read(self, cmd, **args):
128 128 fp = self.do_cmd(cmd, **args)
129 129 try:
130 130 return fp.read()
131 131 finally:
132 132 # if using keepalive, allow connection to be reused
133 133 fp.close()
134 134
135 135 def lookup(self, key):
136 136 self.requirecap('lookup', _('look up remote revision'))
137 137 d = self.do_cmd("lookup", key = key).read()
138 138 success, data = d[:-1].split(' ', 1)
139 139 if int(success):
140 140 return bin(data)
141 141 raise error.RepoError(data)
142 142
143 143 def heads(self):
144 144 d = self.do_read("heads")
145 145 try:
146 146 return map(bin, d[:-1].split(" "))
147 147 except:
148 148 raise error.ResponseError(_("unexpected response:"), d)
149 149
150 150 def branchmap(self):
151 151 d = self.do_read("branchmap")
152 152 try:
153 153 branchmap = {}
154 154 for branchpart in d.splitlines():
155 155 branchheads = branchpart.split(' ')
156 156 branchname = urllib.unquote(branchheads[0])
157 157 # Earlier servers (1.3.x) send branch names in (their) local
158 158 # charset. The best we can do is assume it's identical to our
159 159 # own local charset, in case it's not utf-8.
160 160 try:
161 161 branchname.decode('utf-8')
162 162 except UnicodeDecodeError:
163 163 branchname = encoding.fromlocal(branchname)
164 164 branchheads = [bin(x) for x in branchheads[1:]]
165 165 branchmap[branchname] = branchheads
166 166 return branchmap
167 167 except:
168 168 raise error.ResponseError(_("unexpected response:"), d)
169 169
170 170 def branches(self, nodes):
171 171 n = " ".join(map(hex, nodes))
172 172 d = self.do_read("branches", nodes=n)
173 173 try:
174 174 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
175 175 return br
176 176 except:
177 177 raise error.ResponseError(_("unexpected response:"), d)
178 178
179 179 def between(self, pairs):
180 180 batch = 8 # avoid giant requests
181 181 r = []
182 182 for i in xrange(0, len(pairs), batch):
183 183 n = " ".join(["-".join(map(hex, p)) for p in pairs[i:i + batch]])
184 184 d = self.do_read("between", pairs=n)
185 185 try:
186 186 r += [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
187 187 except:
188 188 raise error.ResponseError(_("unexpected response:"), d)
189 189 return r
190 190
191 191 def changegroup(self, nodes, kind):
192 192 n = " ".join(map(hex, nodes))
193 193 f = self.do_cmd("changegroup", roots=n)
194 194 return util.chunkbuffer(zgenerator(f))
195 195
196 196 def changegroupsubset(self, bases, heads, source):
197 197 self.requirecap('changegroupsubset', _('look up remote changes'))
198 198 baselst = " ".join([hex(n) for n in bases])
199 199 headlst = " ".join([hex(n) for n in heads])
200 200 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
201 201 return util.chunkbuffer(zgenerator(f))
202 202
203 203 def unbundle(self, cg, heads, source):
204 204 # have to stream bundle to a temp file because we do not have
205 205 # http 1.1 chunked transfer.
206 206
207 207 type = ""
208 208 types = self.capable('unbundle')
209 209 # servers older than d1b16a746db6 will send 'unbundle' as a
210 210 # boolean capability
211 211 try:
212 212 types = types.split(',')
213 213 except AttributeError:
214 214 types = [""]
215 215 if types:
216 216 for x in types:
217 217 if x in changegroup.bundletypes:
218 218 type = x
219 219 break
220 220
221 221 tempname = changegroup.writebundle(cg, None, type)
222 222 fp = url.httpsendfile(tempname, "rb")
223 223 try:
224 224 try:
225 225 resp = self.do_read(
226 226 'unbundle', data=fp,
227 227 headers={'Content-Type': 'application/octet-stream'},
228 228 heads=' '.join(map(hex, heads)))
229 229 resp_code, output = resp.split('\n', 1)
230 230 try:
231 231 ret = int(resp_code)
232 232 except ValueError, err:
233 233 raise error.ResponseError(
234 234 _('push failed (unexpected response):'), resp)
235 235 self.ui.write(output)
236 236 return ret
237 237 except socket.error, err:
238 238 if err[0] in (errno.ECONNRESET, errno.EPIPE):
239 239 raise util.Abort(_('push failed: %s') % err[1])
240 240 raise util.Abort(err[1])
241 241 finally:
242 242 fp.close()
243 243 os.unlink(tempname)
244 244
245 245 def stream_out(self):
246 246 return self.do_cmd('stream_out')
247 247
248 248 class httpsrepository(httprepository):
249 249 def __init__(self, ui, path):
250 250 if not url.has_https:
251 251 raise util.Abort(_('Python support for SSL and HTTPS '
252 252 'is not installed'))
253 253 httprepository.__init__(self, ui, path)
254 254
255 255 def instance(ui, path, create):
256 256 if create:
257 257 raise util.Abort(_('cannot create new http repository'))
258 258 try:
259 259 if path.startswith('https:'):
260 260 inst = httpsrepository(ui, path)
261 261 else:
262 262 inst = httprepository(ui, path)
263 263 inst.between([(nullid, nullid)])
264 264 return inst
265 265 except error.RepoError:
266 266 ui.note('(falling back to static-http)\n')
267 267 return statichttprepo.instance(ui, "static-" + path, create)
General Comments 0
You need to be logged in to leave comments. Login now