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