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