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