##// END OF EJS Templates
sshpeer: document the handshake mechanism...
Gregory Szorc -
r35957:a622a927 default
parent child Browse files
Show More
@@ -1,423 +1,456
1 # sshpeer.py - ssh repository proxy class for mercurial
1 # sshpeer.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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 error,
14 error,
15 pycompat,
15 pycompat,
16 util,
16 util,
17 wireproto,
17 wireproto,
18 )
18 )
19
19
20 def _serverquote(s):
20 def _serverquote(s):
21 """quote a string for the remote shell ... which we assume is sh"""
21 """quote a string for the remote shell ... which we assume is sh"""
22 if not s:
22 if not s:
23 return s
23 return s
24 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
24 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
25 return s
25 return s
26 return "'%s'" % s.replace("'", "'\\''")
26 return "'%s'" % s.replace("'", "'\\''")
27
27
28 def _forwardoutput(ui, pipe):
28 def _forwardoutput(ui, pipe):
29 """display all data currently available on pipe as remote output.
29 """display all data currently available on pipe as remote output.
30
30
31 This is non blocking."""
31 This is non blocking."""
32 s = util.readpipe(pipe)
32 s = util.readpipe(pipe)
33 if s:
33 if s:
34 for l in s.splitlines():
34 for l in s.splitlines():
35 ui.status(_("remote: "), l, '\n')
35 ui.status(_("remote: "), l, '\n')
36
36
37 class doublepipe(object):
37 class doublepipe(object):
38 """Operate a side-channel pipe in addition of a main one
38 """Operate a side-channel pipe in addition of a main one
39
39
40 The side-channel pipe contains server output to be forwarded to the user
40 The side-channel pipe contains server output to be forwarded to the user
41 input. The double pipe will behave as the "main" pipe, but will ensure the
41 input. The double pipe will behave as the "main" pipe, but will ensure the
42 content of the "side" pipe is properly processed while we wait for blocking
42 content of the "side" pipe is properly processed while we wait for blocking
43 call on the "main" pipe.
43 call on the "main" pipe.
44
44
45 If large amounts of data are read from "main", the forward will cease after
45 If large amounts of data are read from "main", the forward will cease after
46 the first bytes start to appear. This simplifies the implementation
46 the first bytes start to appear. This simplifies the implementation
47 without affecting actual output of sshpeer too much as we rarely issue
47 without affecting actual output of sshpeer too much as we rarely issue
48 large read for data not yet emitted by the server.
48 large read for data not yet emitted by the server.
49
49
50 The main pipe is expected to be a 'bufferedinputpipe' from the util module
50 The main pipe is expected to be a 'bufferedinputpipe' from the util module
51 that handle all the os specific bits. This class lives in this module
51 that handle all the os specific bits. This class lives in this module
52 because it focus on behavior specific to the ssh protocol."""
52 because it focus on behavior specific to the ssh protocol."""
53
53
54 def __init__(self, ui, main, side):
54 def __init__(self, ui, main, side):
55 self._ui = ui
55 self._ui = ui
56 self._main = main
56 self._main = main
57 self._side = side
57 self._side = side
58
58
59 def _wait(self):
59 def _wait(self):
60 """wait until some data are available on main or side
60 """wait until some data are available on main or side
61
61
62 return a pair of boolean (ismainready, issideready)
62 return a pair of boolean (ismainready, issideready)
63
63
64 (This will only wait for data if the setup is supported by `util.poll`)
64 (This will only wait for data if the setup is supported by `util.poll`)
65 """
65 """
66 if getattr(self._main, 'hasbuffer', False): # getattr for classic pipe
66 if getattr(self._main, 'hasbuffer', False): # getattr for classic pipe
67 return (True, True) # main has data, assume side is worth poking at.
67 return (True, True) # main has data, assume side is worth poking at.
68 fds = [self._main.fileno(), self._side.fileno()]
68 fds = [self._main.fileno(), self._side.fileno()]
69 try:
69 try:
70 act = util.poll(fds)
70 act = util.poll(fds)
71 except NotImplementedError:
71 except NotImplementedError:
72 # non supported yet case, assume all have data.
72 # non supported yet case, assume all have data.
73 act = fds
73 act = fds
74 return (self._main.fileno() in act, self._side.fileno() in act)
74 return (self._main.fileno() in act, self._side.fileno() in act)
75
75
76 def write(self, data):
76 def write(self, data):
77 return self._call('write', data)
77 return self._call('write', data)
78
78
79 def read(self, size):
79 def read(self, size):
80 r = self._call('read', size)
80 r = self._call('read', size)
81 if size != 0 and not r:
81 if size != 0 and not r:
82 # We've observed a condition that indicates the
82 # We've observed a condition that indicates the
83 # stdout closed unexpectedly. Check stderr one
83 # stdout closed unexpectedly. Check stderr one
84 # more time and snag anything that's there before
84 # more time and snag anything that's there before
85 # letting anyone know the main part of the pipe
85 # letting anyone know the main part of the pipe
86 # closed prematurely.
86 # closed prematurely.
87 _forwardoutput(self._ui, self._side)
87 _forwardoutput(self._ui, self._side)
88 return r
88 return r
89
89
90 def readline(self):
90 def readline(self):
91 return self._call('readline')
91 return self._call('readline')
92
92
93 def _call(self, methname, data=None):
93 def _call(self, methname, data=None):
94 """call <methname> on "main", forward output of "side" while blocking
94 """call <methname> on "main", forward output of "side" while blocking
95 """
95 """
96 # data can be '' or 0
96 # data can be '' or 0
97 if (data is not None and not data) or self._main.closed:
97 if (data is not None and not data) or self._main.closed:
98 _forwardoutput(self._ui, self._side)
98 _forwardoutput(self._ui, self._side)
99 return ''
99 return ''
100 while True:
100 while True:
101 mainready, sideready = self._wait()
101 mainready, sideready = self._wait()
102 if sideready:
102 if sideready:
103 _forwardoutput(self._ui, self._side)
103 _forwardoutput(self._ui, self._side)
104 if mainready:
104 if mainready:
105 meth = getattr(self._main, methname)
105 meth = getattr(self._main, methname)
106 if data is None:
106 if data is None:
107 return meth()
107 return meth()
108 else:
108 else:
109 return meth(data)
109 return meth(data)
110
110
111 def close(self):
111 def close(self):
112 return self._main.close()
112 return self._main.close()
113
113
114 def flush(self):
114 def flush(self):
115 return self._main.flush()
115 return self._main.flush()
116
116
117 def _cleanuppipes(ui, pipei, pipeo, pipee):
117 def _cleanuppipes(ui, pipei, pipeo, pipee):
118 """Clean up pipes used by an SSH connection."""
118 """Clean up pipes used by an SSH connection."""
119 if pipeo:
119 if pipeo:
120 pipeo.close()
120 pipeo.close()
121 if pipei:
121 if pipei:
122 pipei.close()
122 pipei.close()
123
123
124 if pipee:
124 if pipee:
125 # Try to read from the err descriptor until EOF.
125 # Try to read from the err descriptor until EOF.
126 try:
126 try:
127 for l in pipee:
127 for l in pipee:
128 ui.status(_('remote: '), l)
128 ui.status(_('remote: '), l)
129 except (IOError, ValueError):
129 except (IOError, ValueError):
130 pass
130 pass
131
131
132 pipee.close()
132 pipee.close()
133
133
134 def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None):
134 def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None):
135 """Create an SSH connection to a server.
135 """Create an SSH connection to a server.
136
136
137 Returns a tuple of (process, stdin, stdout, stderr) for the
137 Returns a tuple of (process, stdin, stdout, stderr) for the
138 spawned process.
138 spawned process.
139 """
139 """
140 cmd = '%s %s %s' % (
140 cmd = '%s %s %s' % (
141 sshcmd,
141 sshcmd,
142 args,
142 args,
143 util.shellquote('%s -R %s serve --stdio' % (
143 util.shellquote('%s -R %s serve --stdio' % (
144 _serverquote(remotecmd), _serverquote(path))))
144 _serverquote(remotecmd), _serverquote(path))))
145
145
146 ui.debug('running %s\n' % cmd)
146 ui.debug('running %s\n' % cmd)
147 cmd = util.quotecommand(cmd)
147 cmd = util.quotecommand(cmd)
148
148
149 # no buffer allow the use of 'select'
149 # no buffer allow the use of 'select'
150 # feel free to remove buffering and select usage when we ultimately
150 # feel free to remove buffering and select usage when we ultimately
151 # move to threading.
151 # move to threading.
152 stdin, stdout, stderr, proc = util.popen4(cmd, bufsize=0, env=sshenv)
152 stdin, stdout, stderr, proc = util.popen4(cmd, bufsize=0, env=sshenv)
153
153
154 stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr)
154 stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr)
155 stdin = doublepipe(ui, stdin, stderr)
155 stdin = doublepipe(ui, stdin, stderr)
156
156
157 return proc, stdin, stdout, stderr
157 return proc, stdin, stdout, stderr
158
158
159 def _performhandshake(ui, stdin, stdout, stderr):
159 def _performhandshake(ui, stdin, stdout, stderr):
160 def badresponse():
160 def badresponse():
161 msg = _('no suitable response from remote hg')
161 msg = _('no suitable response from remote hg')
162 hint = ui.config('ui', 'ssherrorhint')
162 hint = ui.config('ui', 'ssherrorhint')
163 raise error.RepoError(msg, hint=hint)
163 raise error.RepoError(msg, hint=hint)
164
164
165 # The handshake consists of sending 2 wire protocol commands:
166 # ``hello`` and ``between``.
167 #
168 # The ``hello`` command (which was introduced in Mercurial 0.9.1)
169 # instructs the server to advertise its capabilities.
170 #
171 # The ``between`` command (which has existed in all Mercurial servers
172 # for as long as SSH support has existed), asks for the set of revisions
173 # between a pair of revisions.
174 #
175 # The ``between`` command is issued with a request for the null
176 # range. If the remote is a Mercurial server, this request will
177 # generate a specific response: ``1\n\n``. This represents the
178 # wire protocol encoded value for ``\n``. We look for ``1\n\n``
179 # in the output stream and know this is the response to ``between``
180 # and we're at the end of our handshake reply.
181 #
182 # The response to the ``hello`` command will be a line with the
183 # length of the value returned by that command followed by that
184 # value. If the server doesn't support ``hello`` (which should be
185 # rare), that line will be ``0\n``. Otherwise, the value will contain
186 # RFC 822 like lines. Of these, the ``capabilities:`` line contains
187 # the capabilities of the server.
188 #
189 # In addition to the responses to our command requests, the server
190 # may emit "banner" output on stdout. SSH servers are allowed to
191 # print messages to stdout on login. Issuing commands on connection
192 # allows us to flush this banner output from the server by scanning
193 # for output to our well-known ``between`` command. Of course, if
194 # the banner contains ``1\n\n``, this will throw off our detection.
195
165 requestlog = ui.configbool('devel', 'debug.peer-request')
196 requestlog = ui.configbool('devel', 'debug.peer-request')
166
197
167 try:
198 try:
168 pairsarg = '%s-%s' % ('0' * 40, '0' * 40)
199 pairsarg = '%s-%s' % ('0' * 40, '0' * 40)
169 handshake = [
200 handshake = [
170 'hello\n',
201 'hello\n',
171 'between\n',
202 'between\n',
172 'pairs %d\n' % len(pairsarg),
203 'pairs %d\n' % len(pairsarg),
173 pairsarg,
204 pairsarg,
174 ]
205 ]
175
206
176 if requestlog:
207 if requestlog:
177 ui.debug('devel-peer-request: hello\n')
208 ui.debug('devel-peer-request: hello\n')
178 ui.debug('sending hello command\n')
209 ui.debug('sending hello command\n')
179 if requestlog:
210 if requestlog:
180 ui.debug('devel-peer-request: between\n')
211 ui.debug('devel-peer-request: between\n')
181 ui.debug('devel-peer-request: pairs: %d bytes\n' % len(pairsarg))
212 ui.debug('devel-peer-request: pairs: %d bytes\n' % len(pairsarg))
182 ui.debug('sending between command\n')
213 ui.debug('sending between command\n')
183
214
184 stdin.write(''.join(handshake))
215 stdin.write(''.join(handshake))
185 stdin.flush()
216 stdin.flush()
186 except IOError:
217 except IOError:
187 badresponse()
218 badresponse()
188
219
189 lines = ['', 'dummy']
220 lines = ['', 'dummy']
190 max_noise = 500
221 max_noise = 500
191 while lines[-1] and max_noise:
222 while lines[-1] and max_noise:
192 try:
223 try:
193 l = stdout.readline()
224 l = stdout.readline()
194 _forwardoutput(ui, stderr)
225 _forwardoutput(ui, stderr)
195 if lines[-1] == '1\n' and l == '\n':
226 if lines[-1] == '1\n' and l == '\n':
196 break
227 break
197 if l:
228 if l:
198 ui.debug('remote: ', l)
229 ui.debug('remote: ', l)
199 lines.append(l)
230 lines.append(l)
200 max_noise -= 1
231 max_noise -= 1
201 except IOError:
232 except IOError:
202 badresponse()
233 badresponse()
203 else:
234 else:
204 badresponse()
235 badresponse()
205
236
206 caps = set()
237 caps = set()
207 for l in reversed(lines):
238 for l in reversed(lines):
239 # Look for response to ``hello`` command. Scan from the back so
240 # we don't misinterpret banner output as the command reply.
208 if l.startswith('capabilities:'):
241 if l.startswith('capabilities:'):
209 caps.update(l[:-1].split(':')[1].split())
242 caps.update(l[:-1].split(':')[1].split())
210 break
243 break
211
244
212 return caps
245 return caps
213
246
214 class sshpeer(wireproto.wirepeer):
247 class sshpeer(wireproto.wirepeer):
215 def __init__(self, ui, url, proc, stdin, stdout, stderr, caps):
248 def __init__(self, ui, url, proc, stdin, stdout, stderr, caps):
216 """Create a peer from an existing SSH connection.
249 """Create a peer from an existing SSH connection.
217
250
218 ``proc`` is a handle on the underlying SSH process.
251 ``proc`` is a handle on the underlying SSH process.
219 ``stdin``, ``stdout``, and ``stderr`` are handles on the stdio
252 ``stdin``, ``stdout``, and ``stderr`` are handles on the stdio
220 pipes for that process.
253 pipes for that process.
221 ``caps`` is a set of capabilities supported by the remote.
254 ``caps`` is a set of capabilities supported by the remote.
222 """
255 """
223 self._url = url
256 self._url = url
224 self._ui = ui
257 self._ui = ui
225 # self._subprocess is unused. Keeping a handle on the process
258 # self._subprocess is unused. Keeping a handle on the process
226 # holds a reference and prevents it from being garbage collected.
259 # holds a reference and prevents it from being garbage collected.
227 self._subprocess = proc
260 self._subprocess = proc
228 self._pipeo = stdin
261 self._pipeo = stdin
229 self._pipei = stdout
262 self._pipei = stdout
230 self._pipee = stderr
263 self._pipee = stderr
231 self._caps = caps
264 self._caps = caps
232
265
233 # Begin of _basepeer interface.
266 # Begin of _basepeer interface.
234
267
235 @util.propertycache
268 @util.propertycache
236 def ui(self):
269 def ui(self):
237 return self._ui
270 return self._ui
238
271
239 def url(self):
272 def url(self):
240 return self._url
273 return self._url
241
274
242 def local(self):
275 def local(self):
243 return None
276 return None
244
277
245 def peer(self):
278 def peer(self):
246 return self
279 return self
247
280
248 def canpush(self):
281 def canpush(self):
249 return True
282 return True
250
283
251 def close(self):
284 def close(self):
252 pass
285 pass
253
286
254 # End of _basepeer interface.
287 # End of _basepeer interface.
255
288
256 # Begin of _basewirecommands interface.
289 # Begin of _basewirecommands interface.
257
290
258 def capabilities(self):
291 def capabilities(self):
259 return self._caps
292 return self._caps
260
293
261 # End of _basewirecommands interface.
294 # End of _basewirecommands interface.
262
295
263 def _readerr(self):
296 def _readerr(self):
264 _forwardoutput(self.ui, self._pipee)
297 _forwardoutput(self.ui, self._pipee)
265
298
266 def _abort(self, exception):
299 def _abort(self, exception):
267 self._cleanup()
300 self._cleanup()
268 raise exception
301 raise exception
269
302
270 def _cleanup(self):
303 def _cleanup(self):
271 _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee)
304 _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee)
272
305
273 __del__ = _cleanup
306 __del__ = _cleanup
274
307
275 def _submitbatch(self, req):
308 def _submitbatch(self, req):
276 rsp = self._callstream("batch", cmds=wireproto.encodebatchcmds(req))
309 rsp = self._callstream("batch", cmds=wireproto.encodebatchcmds(req))
277 available = self._getamount()
310 available = self._getamount()
278 # TODO this response parsing is probably suboptimal for large
311 # TODO this response parsing is probably suboptimal for large
279 # batches with large responses.
312 # batches with large responses.
280 toread = min(available, 1024)
313 toread = min(available, 1024)
281 work = rsp.read(toread)
314 work = rsp.read(toread)
282 available -= toread
315 available -= toread
283 chunk = work
316 chunk = work
284 while chunk:
317 while chunk:
285 while ';' in work:
318 while ';' in work:
286 one, work = work.split(';', 1)
319 one, work = work.split(';', 1)
287 yield wireproto.unescapearg(one)
320 yield wireproto.unescapearg(one)
288 toread = min(available, 1024)
321 toread = min(available, 1024)
289 chunk = rsp.read(toread)
322 chunk = rsp.read(toread)
290 available -= toread
323 available -= toread
291 work += chunk
324 work += chunk
292 yield wireproto.unescapearg(work)
325 yield wireproto.unescapearg(work)
293
326
294 def _callstream(self, cmd, **args):
327 def _callstream(self, cmd, **args):
295 args = pycompat.byteskwargs(args)
328 args = pycompat.byteskwargs(args)
296 if (self.ui.debugflag
329 if (self.ui.debugflag
297 and self.ui.configbool('devel', 'debug.peer-request')):
330 and self.ui.configbool('devel', 'debug.peer-request')):
298 dbg = self.ui.debug
331 dbg = self.ui.debug
299 line = 'devel-peer-request: %s\n'
332 line = 'devel-peer-request: %s\n'
300 dbg(line % cmd)
333 dbg(line % cmd)
301 for key, value in sorted(args.items()):
334 for key, value in sorted(args.items()):
302 if not isinstance(value, dict):
335 if not isinstance(value, dict):
303 dbg(line % ' %s: %d bytes' % (key, len(value)))
336 dbg(line % ' %s: %d bytes' % (key, len(value)))
304 else:
337 else:
305 for dk, dv in sorted(value.items()):
338 for dk, dv in sorted(value.items()):
306 dbg(line % ' %s-%s: %d' % (key, dk, len(dv)))
339 dbg(line % ' %s-%s: %d' % (key, dk, len(dv)))
307 self.ui.debug("sending %s command\n" % cmd)
340 self.ui.debug("sending %s command\n" % cmd)
308 self._pipeo.write("%s\n" % cmd)
341 self._pipeo.write("%s\n" % cmd)
309 _func, names = wireproto.commands[cmd]
342 _func, names = wireproto.commands[cmd]
310 keys = names.split()
343 keys = names.split()
311 wireargs = {}
344 wireargs = {}
312 for k in keys:
345 for k in keys:
313 if k == '*':
346 if k == '*':
314 wireargs['*'] = args
347 wireargs['*'] = args
315 break
348 break
316 else:
349 else:
317 wireargs[k] = args[k]
350 wireargs[k] = args[k]
318 del args[k]
351 del args[k]
319 for k, v in sorted(wireargs.iteritems()):
352 for k, v in sorted(wireargs.iteritems()):
320 self._pipeo.write("%s %d\n" % (k, len(v)))
353 self._pipeo.write("%s %d\n" % (k, len(v)))
321 if isinstance(v, dict):
354 if isinstance(v, dict):
322 for dk, dv in v.iteritems():
355 for dk, dv in v.iteritems():
323 self._pipeo.write("%s %d\n" % (dk, len(dv)))
356 self._pipeo.write("%s %d\n" % (dk, len(dv)))
324 self._pipeo.write(dv)
357 self._pipeo.write(dv)
325 else:
358 else:
326 self._pipeo.write(v)
359 self._pipeo.write(v)
327 self._pipeo.flush()
360 self._pipeo.flush()
328
361
329 return self._pipei
362 return self._pipei
330
363
331 def _callcompressable(self, cmd, **args):
364 def _callcompressable(self, cmd, **args):
332 return self._callstream(cmd, **args)
365 return self._callstream(cmd, **args)
333
366
334 def _call(self, cmd, **args):
367 def _call(self, cmd, **args):
335 self._callstream(cmd, **args)
368 self._callstream(cmd, **args)
336 return self._recv()
369 return self._recv()
337
370
338 def _callpush(self, cmd, fp, **args):
371 def _callpush(self, cmd, fp, **args):
339 r = self._call(cmd, **args)
372 r = self._call(cmd, **args)
340 if r:
373 if r:
341 return '', r
374 return '', r
342 for d in iter(lambda: fp.read(4096), ''):
375 for d in iter(lambda: fp.read(4096), ''):
343 self._send(d)
376 self._send(d)
344 self._send("", flush=True)
377 self._send("", flush=True)
345 r = self._recv()
378 r = self._recv()
346 if r:
379 if r:
347 return '', r
380 return '', r
348 return self._recv(), ''
381 return self._recv(), ''
349
382
350 def _calltwowaystream(self, cmd, fp, **args):
383 def _calltwowaystream(self, cmd, fp, **args):
351 r = self._call(cmd, **args)
384 r = self._call(cmd, **args)
352 if r:
385 if r:
353 # XXX needs to be made better
386 # XXX needs to be made better
354 raise error.Abort(_('unexpected remote reply: %s') % r)
387 raise error.Abort(_('unexpected remote reply: %s') % r)
355 for d in iter(lambda: fp.read(4096), ''):
388 for d in iter(lambda: fp.read(4096), ''):
356 self._send(d)
389 self._send(d)
357 self._send("", flush=True)
390 self._send("", flush=True)
358 return self._pipei
391 return self._pipei
359
392
360 def _getamount(self):
393 def _getamount(self):
361 l = self._pipei.readline()
394 l = self._pipei.readline()
362 if l == '\n':
395 if l == '\n':
363 self._readerr()
396 self._readerr()
364 msg = _('check previous remote output')
397 msg = _('check previous remote output')
365 self._abort(error.OutOfBandError(hint=msg))
398 self._abort(error.OutOfBandError(hint=msg))
366 self._readerr()
399 self._readerr()
367 try:
400 try:
368 return int(l)
401 return int(l)
369 except ValueError:
402 except ValueError:
370 self._abort(error.ResponseError(_("unexpected response:"), l))
403 self._abort(error.ResponseError(_("unexpected response:"), l))
371
404
372 def _recv(self):
405 def _recv(self):
373 return self._pipei.read(self._getamount())
406 return self._pipei.read(self._getamount())
374
407
375 def _send(self, data, flush=False):
408 def _send(self, data, flush=False):
376 self._pipeo.write("%d\n" % len(data))
409 self._pipeo.write("%d\n" % len(data))
377 if data:
410 if data:
378 self._pipeo.write(data)
411 self._pipeo.write(data)
379 if flush:
412 if flush:
380 self._pipeo.flush()
413 self._pipeo.flush()
381 self._readerr()
414 self._readerr()
382
415
383 def instance(ui, path, create):
416 def instance(ui, path, create):
384 """Create an SSH peer.
417 """Create an SSH peer.
385
418
386 The returned object conforms to the ``wireproto.wirepeer`` interface.
419 The returned object conforms to the ``wireproto.wirepeer`` interface.
387 """
420 """
388 u = util.url(path, parsequery=False, parsefragment=False)
421 u = util.url(path, parsequery=False, parsefragment=False)
389 if u.scheme != 'ssh' or not u.host or u.path is None:
422 if u.scheme != 'ssh' or not u.host or u.path is None:
390 raise error.RepoError(_("couldn't parse location %s") % path)
423 raise error.RepoError(_("couldn't parse location %s") % path)
391
424
392 util.checksafessh(path)
425 util.checksafessh(path)
393
426
394 if u.passwd is not None:
427 if u.passwd is not None:
395 raise error.RepoError(_('password in URL not supported'))
428 raise error.RepoError(_('password in URL not supported'))
396
429
397 sshcmd = ui.config('ui', 'ssh')
430 sshcmd = ui.config('ui', 'ssh')
398 remotecmd = ui.config('ui', 'remotecmd')
431 remotecmd = ui.config('ui', 'remotecmd')
399 sshaddenv = dict(ui.configitems('sshenv'))
432 sshaddenv = dict(ui.configitems('sshenv'))
400 sshenv = util.shellenviron(sshaddenv)
433 sshenv = util.shellenviron(sshaddenv)
401 remotepath = u.path or '.'
434 remotepath = u.path or '.'
402
435
403 args = util.sshargs(sshcmd, u.host, u.user, u.port)
436 args = util.sshargs(sshcmd, u.host, u.user, u.port)
404
437
405 if create:
438 if create:
406 cmd = '%s %s %s' % (sshcmd, args,
439 cmd = '%s %s %s' % (sshcmd, args,
407 util.shellquote('%s init %s' %
440 util.shellquote('%s init %s' %
408 (_serverquote(remotecmd), _serverquote(remotepath))))
441 (_serverquote(remotecmd), _serverquote(remotepath))))
409 ui.debug('running %s\n' % cmd)
442 ui.debug('running %s\n' % cmd)
410 res = ui.system(cmd, blockedtag='sshpeer', environ=sshenv)
443 res = ui.system(cmd, blockedtag='sshpeer', environ=sshenv)
411 if res != 0:
444 if res != 0:
412 raise error.RepoError(_('could not create remote repo'))
445 raise error.RepoError(_('could not create remote repo'))
413
446
414 proc, stdin, stdout, stderr = _makeconnection(ui, sshcmd, args, remotecmd,
447 proc, stdin, stdout, stderr = _makeconnection(ui, sshcmd, args, remotecmd,
415 remotepath, sshenv)
448 remotepath, sshenv)
416
449
417 try:
450 try:
418 caps = _performhandshake(ui, stdin, stdout, stderr)
451 caps = _performhandshake(ui, stdin, stdout, stderr)
419 except Exception:
452 except Exception:
420 _cleanuppipes(ui, stdout, stdin, stderr)
453 _cleanuppipes(ui, stdout, stdin, stderr)
421 raise
454 raise
422
455
423 return sshpeer(ui, path, proc, stdin, stdout, stderr, caps)
456 return sshpeer(ui, path, proc, stdin, stdout, stderr, caps)
General Comments 0
You need to be logged in to leave comments. Login now