##// END OF EJS Templates
protocol: move basic ssh client commands to wirerepository
Matt Mackall -
r11586:ddaaaa23 default
parent child Browse files
Show More
@@ -1,293 +1,230 b''
1 1 # sshrepo.py - ssh repository proxy class for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import bin, hex
9 9 from i18n import _
10 import repo, util, error, encoding
10 import repo, util, error, encoding, wireproto
11 11 import re, urllib
12 12
13 13 class remotelock(object):
14 14 def __init__(self, repo):
15 15 self.repo = repo
16 16 def release(self):
17 17 self.repo.unlock()
18 18 self.repo = None
19 19 def __del__(self):
20 20 if self.repo:
21 21 self.release()
22 22
23 class sshrepository(repo.repository):
23 class sshrepository(wireproto.wirerepository):
24 24 def __init__(self, ui, path, create=0):
25 25 self._url = path
26 26 self.ui = ui
27 27
28 28 m = re.match(r'^ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?$', path)
29 29 if not m:
30 30 self.abort(error.RepoError(_("couldn't parse location %s") % path))
31 31
32 32 self.user = m.group(2)
33 33 self.host = m.group(3)
34 34 self.port = m.group(5)
35 35 self.path = m.group(7) or "."
36 36
37 37 sshcmd = self.ui.config("ui", "ssh", "ssh")
38 38 remotecmd = self.ui.config("ui", "remotecmd", "hg")
39 39
40 40 args = util.sshargs(sshcmd, self.host, self.user, self.port)
41 41
42 42 if create:
43 43 cmd = '%s %s "%s init %s"'
44 44 cmd = cmd % (sshcmd, args, remotecmd, self.path)
45 45
46 46 ui.note(_('running %s\n') % cmd)
47 47 res = util.system(cmd)
48 48 if res != 0:
49 49 self.abort(error.RepoError(_("could not create remote repo")))
50 50
51 51 self.validate_repo(ui, sshcmd, args, remotecmd)
52 52
53 53 def url(self):
54 54 return self._url
55 55
56 56 def validate_repo(self, ui, sshcmd, args, remotecmd):
57 57 # cleanup up previous run
58 58 self.cleanup()
59 59
60 60 cmd = '%s %s "%s -R %s serve --stdio"'
61 61 cmd = cmd % (sshcmd, args, remotecmd, self.path)
62 62
63 63 cmd = util.quotecommand(cmd)
64 64 ui.note(_('running %s\n') % cmd)
65 65 self.pipeo, self.pipei, self.pipee = util.popen3(cmd)
66 66
67 67 # skip any noise generated by remote shell
68 68 self.do_cmd("hello")
69 69 r = self.do_cmd("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
70 70 lines = ["", "dummy"]
71 71 max_noise = 500
72 72 while lines[-1] and max_noise:
73 73 l = r.readline()
74 74 self.readerr()
75 75 if lines[-1] == "1\n" and l == "\n":
76 76 break
77 77 if l:
78 78 ui.debug("remote: ", l)
79 79 lines.append(l)
80 80 max_noise -= 1
81 81 else:
82 82 self.abort(error.RepoError(_("no suitable response from remote hg")))
83 83
84 84 self.capabilities = set()
85 85 for l in reversed(lines):
86 86 if l.startswith("capabilities:"):
87 87 self.capabilities.update(l[:-1].split(":")[1].split())
88 88 break
89 89
90 90 def readerr(self):
91 91 while 1:
92 92 size = util.fstat(self.pipee).st_size
93 93 if size == 0:
94 94 break
95 95 l = self.pipee.readline()
96 96 if not l:
97 97 break
98 98 self.ui.status(_("remote: "), l)
99 99
100 100 def abort(self, exception):
101 101 self.cleanup()
102 102 raise exception
103 103
104 def _abort(self, exception):
105 self.cleanup()
106 raise exception
107
104 108 def cleanup(self):
105 109 try:
106 110 self.pipeo.close()
107 111 self.pipei.close()
108 112 # read the error descriptor until EOF
109 113 for l in self.pipee:
110 114 self.ui.status(_("remote: "), l)
111 115 self.pipee.close()
112 116 except:
113 117 pass
114 118
115 119 __del__ = cleanup
116 120
117 121 def do_cmd(self, cmd, **args):
118 122 self.ui.debug("sending %s command\n" % cmd)
119 123 self.pipeo.write("%s\n" % cmd)
120 124 for k, v in sorted(args.iteritems()):
121 125 self.pipeo.write("%s %d\n" % (k, len(v)))
122 126 self.pipeo.write(v)
123 127 self.pipeo.flush()
124 128
125 129 return self.pipei
126 130
127 131 def call(self, cmd, **args):
128 132 self.do_cmd(cmd, **args)
129 133 return self._recv()
130 134
135 def _call(self, cmd, **args):
136 self.do_cmd(cmd, **args)
137 return self._recv()
138
131 139 def _recv(self):
132 140 l = self.pipei.readline()
133 141 self.readerr()
134 142 try:
135 143 l = int(l)
136 144 except:
137 145 self.abort(error.ResponseError(_("unexpected response:"), l))
138 146 return self.pipei.read(l)
139 147
140 148 def _send(self, data, flush=False):
141 149 self.pipeo.write("%d\n" % len(data))
142 150 if data:
143 151 self.pipeo.write(data)
144 152 if flush:
145 153 self.pipeo.flush()
146 154 self.readerr()
147 155
148 156 def lock(self):
149 157 self.call("lock")
150 158 return remotelock(self)
151 159
152 160 def unlock(self):
153 161 self.call("unlock")
154 162
155 def lookup(self, key):
156 self.requirecap('lookup', _('look up remote revision'))
157 d = self.call("lookup", key=key)
158 success, data = d[:-1].split(" ", 1)
159 if int(success):
160 return bin(data)
161 else:
162 self.abort(error.RepoError(data))
163
164 def heads(self):
165 d = self.call("heads")
166 try:
167 return map(bin, d[:-1].split(" "))
168 except:
169 self.abort(error.ResponseError(_("unexpected response:"), d))
170
171 def branchmap(self):
172 d = self.call("branchmap")
173 try:
174 branchmap = {}
175 for branchpart in d.splitlines():
176 branchheads = branchpart.split(' ')
177 branchname = urllib.unquote(branchheads[0])
178 # Earlier servers (1.3.x) send branch names in (their) local
179 # charset. The best we can do is assume it's identical to our
180 # own local charset, in case it's not utf-8.
181 try:
182 branchname.decode('utf-8')
183 except UnicodeDecodeError:
184 branchname = encoding.fromlocal(branchname)
185 branchheads = [bin(x) for x in branchheads[1:]]
186 branchmap[branchname] = branchheads
187 return branchmap
188 except:
189 raise error.ResponseError(_("unexpected response:"), d)
190
191 def branches(self, nodes):
192 n = " ".join(map(hex, nodes))
193 d = self.call("branches", nodes=n)
194 try:
195 br = [tuple(map(bin, b.split(" "))) for b in d.splitlines()]
196 return br
197 except:
198 self.abort(error.ResponseError(_("unexpected response:"), d))
199
200 def between(self, pairs):
201 n = " ".join(["-".join(map(hex, p)) for p in pairs])
202 d = self.call("between", pairs=n)
203 try:
204 p = [l and map(bin, l.split(" ")) or [] for l in d.splitlines()]
205 return p
206 except:
207 self.abort(error.ResponseError(_("unexpected response:"), d))
208
209 163 def changegroup(self, nodes, kind):
210 164 n = " ".join(map(hex, nodes))
211 165 return self.do_cmd("changegroup", roots=n)
212 166
213 167 def changegroupsubset(self, bases, heads, kind):
214 168 self.requirecap('changegroupsubset', _('look up remote changes'))
215 169 bases = " ".join(map(hex, bases))
216 170 heads = " ".join(map(hex, heads))
217 171 return self.do_cmd("changegroupsubset", bases=bases, heads=heads)
218 172
219 173 def unbundle(self, cg, heads, source):
220 174 '''Send cg (a readable file-like object representing the
221 175 changegroup to push, typically a chunkbuffer object) to the
222 176 remote server as a bundle. Return an integer indicating the
223 177 result of the push (see localrepository.addchangegroup()).'''
224 178 d = self.call("unbundle", heads=' '.join(map(hex, heads)))
225 179 if d:
226 180 # remote may send "unsynced changes"
227 181 self.abort(error.RepoError(_("push refused: %s") % d))
228 182
229 183 while 1:
230 184 d = cg.read(4096)
231 185 if not d:
232 186 break
233 187 self._send(d)
234 188
235 189 self._send("", flush=True)
236 190
237 191 r = self._recv()
238 192 if r:
239 193 # remote may send "unsynced changes"
240 194 self.abort(error.RepoError(_("push failed: %s") % r))
241 195
242 196 r = self._recv()
243 197 try:
244 198 return int(r)
245 199 except:
246 200 self.abort(error.ResponseError(_("unexpected response:"), r))
247 201
248 202 def addchangegroup(self, cg, source, url):
249 203 '''Send a changegroup to the remote server. Return an integer
250 204 similar to unbundle(). DEPRECATED, since it requires locking the
251 205 remote.'''
252 206 d = self.call("addchangegroup")
253 207 if d:
254 208 self.abort(error.RepoError(_("push refused: %s") % d))
255 209 while 1:
256 210 d = cg.read(4096)
257 211 if not d:
258 212 break
259 213 self.pipeo.write(d)
260 214 self.readerr()
261 215
262 216 self.pipeo.flush()
263 217
264 218 self.readerr()
265 219 r = self._recv()
266 220 if not r:
267 221 return 1
268 222 try:
269 223 return int(r)
270 224 except:
271 225 self.abort(error.ResponseError(_("unexpected response:"), r))
272 226
273 227 def stream_out(self):
274 228 return self.do_cmd('stream_out')
275 229
276 def pushkey(self, namespace, key, old, new):
277 if not self.capable('pushkey'):
278 return False
279 d = self.call("pushkey",
280 namespace=namespace, key=key, old=old, new=new)
281 return bool(int(d))
282
283 def listkeys(self, namespace):
284 if not self.capable('pushkey'):
285 return {}
286 d = self.call("listkeys", namespace=namespace)
287 r = {}
288 for l in d.splitlines():
289 k, v = l.split('\t')
290 r[k.decode('string-escape')] = v.decode('string-escape')
291 return r
292
293 230 instance = sshrepository
@@ -1,97 +1,173 b''
1 1 # wireproto.py - generic wire protocol support functions
2 2 #
3 3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 from node import bin, hex
10 import urllib, streamclone
10 import urllib
11 import streamclone, repo, error, encoding
11 12 import pushkey as pushkey_
12 13
14 # client side
15
16 class wirerepository(repo.repository):
17 def lookup(self, key):
18 self.requirecap('lookup', _('look up remote revision'))
19 d = self._call("lookup", key=key)
20 success, data = d[:-1].split(" ", 1)
21 if int(success):
22 return bin(data)
23 self._abort(error.RepoError(data))
24
25 def heads(self):
26 d = self._call("heads")
27 try:
28 return map(bin, d[:-1].split(" "))
29 except:
30 self.abort(error.ResponseError(_("unexpected response:"), d))
31
32 def branchmap(self):
33 d = self._call("branchmap")
34 try:
35 branchmap = {}
36 for branchpart in d.splitlines():
37 branchheads = branchpart.split(' ')
38 branchname = urllib.unquote(branchheads[0])
39 # Earlier servers (1.3.x) send branch names in (their) local
40 # charset. The best we can do is assume it's identical to our
41 # own local charset, in case it's not utf-8.
42 try:
43 branchname.decode('utf-8')
44 except UnicodeDecodeError:
45 branchname = encoding.fromlocal(branchname)
46 branchheads = [bin(x) for x in branchheads[1:]]
47 branchmap[branchname] = branchheads
48 return branchmap
49 except TypeError:
50 self._abort(error.ResponseError(_("unexpected response:"), d))
51
52 def branches(self, nodes):
53 n = " ".join(map(hex, nodes))
54 d = self._call("branches", nodes=n)
55 try:
56 br = [tuple(map(bin, b.split(" "))) for b in d.splitlines()]
57 return br
58 except:
59 self._abort(error.ResponseError(_("unexpected response:"), d))
60
61 def between(self, pairs):
62 n = " ".join(["-".join(map(hex, p)) for p in pairs])
63 d = self._call("between", pairs=n)
64 try:
65 p = [l and map(bin, l.split(" ")) or [] for l in d.splitlines()]
66 return p
67 except:
68 self._abort(error.ResponseError(_("unexpected response:"), d))
69
70 def pushkey(self, namespace, key, old, new):
71 if not self.capable('pushkey'):
72 return False
73 d = self._call("pushkey",
74 namespace=namespace, key=key, old=old, new=new)
75 return bool(int(d))
76
77 def listkeys(self, namespace):
78 if not self.capable('pushkey'):
79 return {}
80 d = self._call("listkeys", namespace=namespace)
81 r = {}
82 for l in d.splitlines():
83 k, v = l.split('\t')
84 r[k.decode('string-escape')] = v.decode('string-escape')
85 return r
86
87 # server side
88
13 89 def dispatch(repo, proto, command):
14 90 if command not in commands:
15 91 return False
16 92 func, spec = commands[command]
17 93 args = proto.getargs(spec)
18 94 r = func(repo, proto, *args)
19 95 if r != None:
20 96 proto.respond(r)
21 97 return True
22 98
23 99 def between(repo, proto, pairs):
24 100 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
25 101 r = []
26 102 for b in repo.between(pairs):
27 103 r.append(" ".join(map(hex, b)) + "\n")
28 104 return "".join(r)
29 105
30 106 def branchmap(repo, proto):
31 107 branchmap = repo.branchmap()
32 108 heads = []
33 109 for branch, nodes in branchmap.iteritems():
34 110 branchname = urllib.quote(branch)
35 111 branchnodes = [hex(node) for node in nodes]
36 112 heads.append('%s %s' % (branchname, ' '.join(branchnodes)))
37 113 return '\n'.join(heads)
38 114
39 115 def branches(repo, proto, nodes):
40 116 nodes = map(bin, nodes.split(" "))
41 117 r = []
42 118 for b in repo.branches(nodes):
43 119 r.append(" ".join(map(hex, b)) + "\n")
44 120 return "".join(r)
45 121
46 122 def changegroup(repo, proto, roots):
47 123 nodes = map(bin, roots.split(" "))
48 124 cg = repo.changegroup(nodes, 'serve')
49 125 proto.sendchangegroup(cg)
50 126
51 127 def changegroupsubset(repo, proto, bases, heads):
52 128 bases = [bin(n) for n in bases.split(' ')]
53 129 heads = [bin(n) for n in heads.split(' ')]
54 130 cg = repo.changegroupsubset(bases, heads, 'serve')
55 131 proto.sendchangegroup(cg)
56 132
57 133 def heads(repo, proto):
58 134 h = repo.heads()
59 135 return " ".join(map(hex, h)) + "\n"
60 136
61 137 def listkeys(repo, proto, namespace):
62 138 d = pushkey_.list(repo, namespace).items()
63 139 t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
64 140 v.encode('string-escape')) for k, v in d])
65 141 return t
66 142
67 143 def lookup(repo, proto, key):
68 144 try:
69 145 r = hex(repo.lookup(key))
70 146 success = 1
71 147 except Exception, inst:
72 148 r = str(inst)
73 149 success = 0
74 150 return "%s %s\n" % (success, r)
75 151
76 152 def pushkey(repo, proto, namespace, key, old, new):
77 153 r = pushkey_.push(repo, namespace, key, old, new)
78 154 return '%s\n' % int(r)
79 155
80 156 def stream(repo, proto):
81 157 try:
82 158 proto.sendstream(streamclone.stream_out(repo))
83 159 except streamclone.StreamException, inst:
84 160 return str(inst)
85 161
86 162 commands = {
87 163 'between': (between, 'pairs'),
88 164 'branchmap': (branchmap, ''),
89 165 'branches': (branches, 'nodes'),
90 166 'changegroup': (changegroup, 'roots'),
91 167 'changegroupsubset': (changegroupsubset, 'bases heads'),
92 168 'heads': (heads, ''),
93 169 'listkeys': (listkeys, 'namespace'),
94 170 'lookup': (lookup, 'key'),
95 171 'pushkey': (pushkey, 'namespace key old new'),
96 172 'stream_out': (stream, ''),
97 173 }
General Comments 0
You need to be logged in to leave comments. Login now