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