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