##// END OF EJS Templates
peer: get the `path` object down to the sshpeer...
marmoute -
r50656:73ed1d13 default
parent child Browse files
Show More
@@ -1,676 +1,674
1 1 # sshpeer.py - ssh repository proxy class for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Olivia Mackall <olivia@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
9 9 import re
10 10 import uuid
11 11
12 12 from .i18n import _
13 13 from .pycompat import getattr
14 14 from . import (
15 15 error,
16 16 pycompat,
17 17 util,
18 18 wireprototypes,
19 19 wireprotov1peer,
20 20 wireprotov1server,
21 21 )
22 22 from .utils import (
23 23 procutil,
24 24 stringutil,
25 25 urlutil,
26 26 )
27 27
28 28
29 29 def _serverquote(s):
30 30 """quote a string for the remote shell ... which we assume is sh"""
31 31 if not s:
32 32 return s
33 33 if re.match(b'[a-zA-Z0-9@%_+=:,./-]*$', s):
34 34 return s
35 35 return b"'%s'" % s.replace(b"'", b"'\\''")
36 36
37 37
38 38 def _forwardoutput(ui, pipe, warn=False):
39 39 """display all data currently available on pipe as remote output.
40 40
41 41 This is non blocking."""
42 42 if pipe and not pipe.closed:
43 43 s = procutil.readpipe(pipe)
44 44 if s:
45 45 display = ui.warn if warn else ui.status
46 46 for l in s.splitlines():
47 47 display(_(b"remote: "), l, b'\n')
48 48
49 49
50 50 class doublepipe:
51 51 """Operate a side-channel pipe in addition of a main one
52 52
53 53 The side-channel pipe contains server output to be forwarded to the user
54 54 input. The double pipe will behave as the "main" pipe, but will ensure the
55 55 content of the "side" pipe is properly processed while we wait for blocking
56 56 call on the "main" pipe.
57 57
58 58 If large amounts of data are read from "main", the forward will cease after
59 59 the first bytes start to appear. This simplifies the implementation
60 60 without affecting actual output of sshpeer too much as we rarely issue
61 61 large read for data not yet emitted by the server.
62 62
63 63 The main pipe is expected to be a 'bufferedinputpipe' from the util module
64 64 that handle all the os specific bits. This class lives in this module
65 65 because it focus on behavior specific to the ssh protocol."""
66 66
67 67 def __init__(self, ui, main, side):
68 68 self._ui = ui
69 69 self._main = main
70 70 self._side = side
71 71
72 72 def _wait(self):
73 73 """wait until some data are available on main or side
74 74
75 75 return a pair of boolean (ismainready, issideready)
76 76
77 77 (This will only wait for data if the setup is supported by `util.poll`)
78 78 """
79 79 if (
80 80 isinstance(self._main, util.bufferedinputpipe)
81 81 and self._main.hasbuffer
82 82 ):
83 83 # Main has data. Assume side is worth poking at.
84 84 return True, True
85 85
86 86 fds = [self._main.fileno(), self._side.fileno()]
87 87 try:
88 88 act = util.poll(fds)
89 89 except NotImplementedError:
90 90 # non supported yet case, assume all have data.
91 91 act = fds
92 92 return (self._main.fileno() in act, self._side.fileno() in act)
93 93
94 94 def write(self, data):
95 95 return self._call(b'write', data)
96 96
97 97 def read(self, size):
98 98 r = self._call(b'read', size)
99 99 if size != 0 and not r:
100 100 # We've observed a condition that indicates the
101 101 # stdout closed unexpectedly. Check stderr one
102 102 # more time and snag anything that's there before
103 103 # letting anyone know the main part of the pipe
104 104 # closed prematurely.
105 105 _forwardoutput(self._ui, self._side)
106 106 return r
107 107
108 108 def unbufferedread(self, size):
109 109 r = self._call(b'unbufferedread', size)
110 110 if size != 0 and not r:
111 111 # We've observed a condition that indicates the
112 112 # stdout closed unexpectedly. Check stderr one
113 113 # more time and snag anything that's there before
114 114 # letting anyone know the main part of the pipe
115 115 # closed prematurely.
116 116 _forwardoutput(self._ui, self._side)
117 117 return r
118 118
119 119 def readline(self):
120 120 return self._call(b'readline')
121 121
122 122 def _call(self, methname, data=None):
123 123 """call <methname> on "main", forward output of "side" while blocking"""
124 124 # data can be '' or 0
125 125 if (data is not None and not data) or self._main.closed:
126 126 _forwardoutput(self._ui, self._side)
127 127 return b''
128 128 while True:
129 129 mainready, sideready = self._wait()
130 130 if sideready:
131 131 _forwardoutput(self._ui, self._side)
132 132 if mainready:
133 133 meth = getattr(self._main, methname)
134 134 if data is None:
135 135 return meth()
136 136 else:
137 137 return meth(data)
138 138
139 139 def close(self):
140 140 return self._main.close()
141 141
142 142 @property
143 143 def closed(self):
144 144 return self._main.closed
145 145
146 146 def flush(self):
147 147 return self._main.flush()
148 148
149 149
150 150 def _cleanuppipes(ui, pipei, pipeo, pipee, warn):
151 151 """Clean up pipes used by an SSH connection."""
152 152 didsomething = False
153 153 if pipeo and not pipeo.closed:
154 154 didsomething = True
155 155 pipeo.close()
156 156 if pipei and not pipei.closed:
157 157 didsomething = True
158 158 pipei.close()
159 159
160 160 if pipee and not pipee.closed:
161 161 didsomething = True
162 162 # Try to read from the err descriptor until EOF.
163 163 try:
164 164 for l in pipee:
165 165 ui.status(_(b'remote: '), l)
166 166 except (IOError, ValueError):
167 167 pass
168 168
169 169 pipee.close()
170 170
171 171 if didsomething and warn is not None:
172 172 # Encourage explicit close of sshpeers. Closing via __del__ is
173 173 # not very predictable when exceptions are thrown, which has led
174 174 # to deadlocks due to a peer get gc'ed in a fork
175 175 # We add our own stack trace, because the stacktrace when called
176 176 # from __del__ is useless.
177 177 ui.develwarn(b'missing close on SSH connection created at:\n%s' % warn)
178 178
179 179
180 180 def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None):
181 181 """Create an SSH connection to a server.
182 182
183 183 Returns a tuple of (process, stdin, stdout, stderr) for the
184 184 spawned process.
185 185 """
186 186 cmd = b'%s %s %s' % (
187 187 sshcmd,
188 188 args,
189 189 procutil.shellquote(
190 190 b'%s -R %s serve --stdio'
191 191 % (_serverquote(remotecmd), _serverquote(path))
192 192 ),
193 193 )
194 194
195 195 ui.debug(b'running %s\n' % cmd)
196 196
197 197 # no buffer allow the use of 'select'
198 198 # feel free to remove buffering and select usage when we ultimately
199 199 # move to threading.
200 200 stdin, stdout, stderr, proc = procutil.popen4(cmd, bufsize=0, env=sshenv)
201 201
202 202 return proc, stdin, stdout, stderr
203 203
204 204
205 205 def _clientcapabilities():
206 206 """Return list of capabilities of this client.
207 207
208 208 Returns a list of capabilities that are supported by this client.
209 209 """
210 210 protoparams = {b'partial-pull'}
211 211 comps = [
212 212 e.wireprotosupport().name
213 213 for e in util.compengines.supportedwireengines(util.CLIENTROLE)
214 214 ]
215 215 protoparams.add(b'comp=%s' % b','.join(comps))
216 216 return protoparams
217 217
218 218
219 219 def _performhandshake(ui, stdin, stdout, stderr):
220 220 def badresponse():
221 221 # Flush any output on stderr. In general, the stderr contains errors
222 222 # from the remote (ssh errors, some hg errors), and status indications
223 223 # (like "adding changes"), with no current way to tell them apart.
224 224 # Here we failed so early that it's almost certainly only errors, so
225 225 # use warn=True so -q doesn't hide them.
226 226 _forwardoutput(ui, stderr, warn=True)
227 227
228 228 msg = _(b'no suitable response from remote hg')
229 229 hint = ui.config(b'ui', b'ssherrorhint')
230 230 raise error.RepoError(msg, hint=hint)
231 231
232 232 # The handshake consists of sending wire protocol commands in reverse
233 233 # order of protocol implementation and then sniffing for a response
234 234 # to one of them.
235 235 #
236 236 # Those commands (from oldest to newest) are:
237 237 #
238 238 # ``between``
239 239 # Asks for the set of revisions between a pair of revisions. Command
240 240 # present in all Mercurial server implementations.
241 241 #
242 242 # ``hello``
243 243 # Instructs the server to advertise its capabilities. Introduced in
244 244 # Mercurial 0.9.1.
245 245 #
246 246 # ``upgrade``
247 247 # Requests upgrade from default transport protocol version 1 to
248 248 # a newer version. Introduced in Mercurial 4.6 as an experimental
249 249 # feature.
250 250 #
251 251 # The ``between`` command is issued with a request for the null
252 252 # range. If the remote is a Mercurial server, this request will
253 253 # generate a specific response: ``1\n\n``. This represents the
254 254 # wire protocol encoded value for ``\n``. We look for ``1\n\n``
255 255 # in the output stream and know this is the response to ``between``
256 256 # and we're at the end of our handshake reply.
257 257 #
258 258 # The response to the ``hello`` command will be a line with the
259 259 # length of the value returned by that command followed by that
260 260 # value. If the server doesn't support ``hello`` (which should be
261 261 # rare), that line will be ``0\n``. Otherwise, the value will contain
262 262 # RFC 822 like lines. Of these, the ``capabilities:`` line contains
263 263 # the capabilities of the server.
264 264 #
265 265 # The ``upgrade`` command isn't really a command in the traditional
266 266 # sense of version 1 of the transport because it isn't using the
267 267 # proper mechanism for formatting insteads: instead, it just encodes
268 268 # arguments on the line, delimited by spaces.
269 269 #
270 270 # The ``upgrade`` line looks like ``upgrade <token> <capabilities>``.
271 271 # If the server doesn't support protocol upgrades, it will reply to
272 272 # this line with ``0\n``. Otherwise, it emits an
273 273 # ``upgraded <token> <protocol>`` line to both stdout and stderr.
274 274 # Content immediately following this line describes additional
275 275 # protocol and server state.
276 276 #
277 277 # In addition to the responses to our command requests, the server
278 278 # may emit "banner" output on stdout. SSH servers are allowed to
279 279 # print messages to stdout on login. Issuing commands on connection
280 280 # allows us to flush this banner output from the server by scanning
281 281 # for output to our well-known ``between`` command. Of course, if
282 282 # the banner contains ``1\n\n``, this will throw off our detection.
283 283
284 284 requestlog = ui.configbool(b'devel', b'debug.peer-request')
285 285
286 286 # Generate a random token to help identify responses to version 2
287 287 # upgrade request.
288 288 token = pycompat.sysbytes(str(uuid.uuid4()))
289 289
290 290 try:
291 291 pairsarg = b'%s-%s' % (b'0' * 40, b'0' * 40)
292 292 handshake = [
293 293 b'hello\n',
294 294 b'between\n',
295 295 b'pairs %d\n' % len(pairsarg),
296 296 pairsarg,
297 297 ]
298 298
299 299 if requestlog:
300 300 ui.debug(b'devel-peer-request: hello+between\n')
301 301 ui.debug(b'devel-peer-request: pairs: %d bytes\n' % len(pairsarg))
302 302 ui.debug(b'sending hello command\n')
303 303 ui.debug(b'sending between command\n')
304 304
305 305 stdin.write(b''.join(handshake))
306 306 stdin.flush()
307 307 except IOError:
308 308 badresponse()
309 309
310 310 # Assume version 1 of wire protocol by default.
311 311 protoname = wireprototypes.SSHV1
312 312 reupgraded = re.compile(b'^upgraded %s (.*)$' % stringutil.reescape(token))
313 313
314 314 lines = [b'', b'dummy']
315 315 max_noise = 500
316 316 while lines[-1] and max_noise:
317 317 try:
318 318 l = stdout.readline()
319 319 _forwardoutput(ui, stderr, warn=True)
320 320
321 321 # Look for reply to protocol upgrade request. It has a token
322 322 # in it, so there should be no false positives.
323 323 m = reupgraded.match(l)
324 324 if m:
325 325 protoname = m.group(1)
326 326 ui.debug(b'protocol upgraded to %s\n' % protoname)
327 327 # If an upgrade was handled, the ``hello`` and ``between``
328 328 # requests are ignored. The next output belongs to the
329 329 # protocol, so stop scanning lines.
330 330 break
331 331
332 332 # Otherwise it could be a banner, ``0\n`` response if server
333 333 # doesn't support upgrade.
334 334
335 335 if lines[-1] == b'1\n' and l == b'\n':
336 336 break
337 337 if l:
338 338 ui.debug(b'remote: ', l)
339 339 lines.append(l)
340 340 max_noise -= 1
341 341 except IOError:
342 342 badresponse()
343 343 else:
344 344 badresponse()
345 345
346 346 caps = set()
347 347
348 348 # For version 1, we should see a ``capabilities`` line in response to the
349 349 # ``hello`` command.
350 350 if protoname == wireprototypes.SSHV1:
351 351 for l in reversed(lines):
352 352 # Look for response to ``hello`` command. Scan from the back so
353 353 # we don't misinterpret banner output as the command reply.
354 354 if l.startswith(b'capabilities:'):
355 355 caps.update(l[:-1].split(b':')[1].split())
356 356 break
357 357
358 358 # Error if we couldn't find capabilities, this means:
359 359 #
360 360 # 1. Remote isn't a Mercurial server
361 361 # 2. Remote is a <0.9.1 Mercurial server
362 362 # 3. Remote is a future Mercurial server that dropped ``hello``
363 363 # and other attempted handshake mechanisms.
364 364 if not caps:
365 365 badresponse()
366 366
367 367 # Flush any output on stderr before proceeding.
368 368 _forwardoutput(ui, stderr, warn=True)
369 369
370 370 return protoname, caps
371 371
372 372
373 373 class sshv1peer(wireprotov1peer.wirepeer):
374 374 def __init__(
375 self, ui, url, proc, stdin, stdout, stderr, caps, autoreadstderr=True
375 self, ui, path, proc, stdin, stdout, stderr, caps, autoreadstderr=True
376 376 ):
377 377 """Create a peer from an existing SSH connection.
378 378
379 379 ``proc`` is a handle on the underlying SSH process.
380 380 ``stdin``, ``stdout``, and ``stderr`` are handles on the stdio
381 381 pipes for that process.
382 382 ``caps`` is a set of capabilities supported by the remote.
383 383 ``autoreadstderr`` denotes whether to automatically read from
384 384 stderr and to forward its output.
385 385 """
386 super().__init__(ui)
387 self._url = url
386 super().__init__(ui, path=path)
388 387 # self._subprocess is unused. Keeping a handle on the process
389 388 # holds a reference and prevents it from being garbage collected.
390 389 self._subprocess = proc
391 390
392 391 # And we hook up our "doublepipe" wrapper to allow querying
393 392 # stderr any time we perform I/O.
394 393 if autoreadstderr:
395 394 stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr)
396 395 stdin = doublepipe(ui, stdin, stderr)
397 396
398 397 self._pipeo = stdin
399 398 self._pipei = stdout
400 399 self._pipee = stderr
401 400 self._caps = caps
402 401 self._autoreadstderr = autoreadstderr
403 402 self._initstack = b''.join(util.getstackframes(1))
404 403
405 404 # Commands that have a "framed" response where the first line of the
406 405 # response contains the length of that response.
407 406 _FRAMED_COMMANDS = {
408 407 b'batch',
409 408 }
410 409
411 410 # Begin of ipeerconnection interface.
412 411
413 412 def url(self):
414 return self._url
413 return self.path.loc
415 414
416 415 def local(self):
417 416 return None
418 417
419 418 def canpush(self):
420 419 return True
421 420
422 421 def close(self):
423 422 self._cleanup()
424 423
425 424 # End of ipeerconnection interface.
426 425
427 426 # Begin of ipeercommands interface.
428 427
429 428 def capabilities(self):
430 429 return self._caps
431 430
432 431 # End of ipeercommands interface.
433 432
434 433 def _readerr(self):
435 434 _forwardoutput(self.ui, self._pipee)
436 435
437 436 def _abort(self, exception):
438 437 self._cleanup()
439 438 raise exception
440 439
441 440 def _cleanup(self, warn=None):
442 441 _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee, warn=warn)
443 442
444 443 def __del__(self):
445 444 self._cleanup(warn=self._initstack)
446 445
447 446 def _sendrequest(self, cmd, args, framed=False):
448 447 if self.ui.debugflag and self.ui.configbool(
449 448 b'devel', b'debug.peer-request'
450 449 ):
451 450 dbg = self.ui.debug
452 451 line = b'devel-peer-request: %s\n'
453 452 dbg(line % cmd)
454 453 for key, value in sorted(args.items()):
455 454 if not isinstance(value, dict):
456 455 dbg(line % b' %s: %d bytes' % (key, len(value)))
457 456 else:
458 457 for dk, dv in sorted(value.items()):
459 458 dbg(line % b' %s-%s: %d' % (key, dk, len(dv)))
460 459 self.ui.debug(b"sending %s command\n" % cmd)
461 460 self._pipeo.write(b"%s\n" % cmd)
462 461 _func, names = wireprotov1server.commands[cmd]
463 462 keys = names.split()
464 463 wireargs = {}
465 464 for k in keys:
466 465 if k == b'*':
467 466 wireargs[b'*'] = args
468 467 break
469 468 else:
470 469 wireargs[k] = args[k]
471 470 del args[k]
472 471 for k, v in sorted(wireargs.items()):
473 472 self._pipeo.write(b"%s %d\n" % (k, len(v)))
474 473 if isinstance(v, dict):
475 474 for dk, dv in v.items():
476 475 self._pipeo.write(b"%s %d\n" % (dk, len(dv)))
477 476 self._pipeo.write(dv)
478 477 else:
479 478 self._pipeo.write(v)
480 479 self._pipeo.flush()
481 480
482 481 # We know exactly how many bytes are in the response. So return a proxy
483 482 # around the raw output stream that allows reading exactly this many
484 483 # bytes. Callers then can read() without fear of overrunning the
485 484 # response.
486 485 if framed:
487 486 amount = self._getamount()
488 487 return util.cappedreader(self._pipei, amount)
489 488
490 489 return self._pipei
491 490
492 491 def _callstream(self, cmd, **args):
493 492 args = pycompat.byteskwargs(args)
494 493 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
495 494
496 495 def _callcompressable(self, cmd, **args):
497 496 args = pycompat.byteskwargs(args)
498 497 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
499 498
500 499 def _call(self, cmd, **args):
501 500 args = pycompat.byteskwargs(args)
502 501 return self._sendrequest(cmd, args, framed=True).read()
503 502
504 503 def _callpush(self, cmd, fp, **args):
505 504 # The server responds with an empty frame if the client should
506 505 # continue submitting the payload.
507 506 r = self._call(cmd, **args)
508 507 if r:
509 508 return b'', r
510 509
511 510 # The payload consists of frames with content followed by an empty
512 511 # frame.
513 512 for d in iter(lambda: fp.read(4096), b''):
514 513 self._writeframed(d)
515 514 self._writeframed(b"", flush=True)
516 515
517 516 # In case of success, there is an empty frame and a frame containing
518 517 # the integer result (as a string).
519 518 # In case of error, there is a non-empty frame containing the error.
520 519 r = self._readframed()
521 520 if r:
522 521 return b'', r
523 522 return self._readframed(), b''
524 523
525 524 def _calltwowaystream(self, cmd, fp, **args):
526 525 # The server responds with an empty frame if the client should
527 526 # continue submitting the payload.
528 527 r = self._call(cmd, **args)
529 528 if r:
530 529 # XXX needs to be made better
531 530 raise error.Abort(_(b'unexpected remote reply: %s') % r)
532 531
533 532 # The payload consists of frames with content followed by an empty
534 533 # frame.
535 534 for d in iter(lambda: fp.read(4096), b''):
536 535 self._writeframed(d)
537 536 self._writeframed(b"", flush=True)
538 537
539 538 return self._pipei
540 539
541 540 def _getamount(self):
542 541 l = self._pipei.readline()
543 542 if l == b'\n':
544 543 if self._autoreadstderr:
545 544 self._readerr()
546 545 msg = _(b'check previous remote output')
547 546 self._abort(error.OutOfBandError(hint=msg))
548 547 if self._autoreadstderr:
549 548 self._readerr()
550 549 try:
551 550 return int(l)
552 551 except ValueError:
553 552 self._abort(error.ResponseError(_(b"unexpected response:"), l))
554 553
555 554 def _readframed(self):
556 555 size = self._getamount()
557 556 if not size:
558 557 return b''
559 558
560 559 return self._pipei.read(size)
561 560
562 561 def _writeframed(self, data, flush=False):
563 562 self._pipeo.write(b"%d\n" % len(data))
564 563 if data:
565 564 self._pipeo.write(data)
566 565 if flush:
567 566 self._pipeo.flush()
568 567 if self._autoreadstderr:
569 568 self._readerr()
570 569
571 570
572 571 def makepeer(ui, path, proc, stdin, stdout, stderr, autoreadstderr=True):
573 572 """Make a peer instance from existing pipes.
574 573
575 574 ``path`` and ``proc`` are stored on the eventual peer instance and may
576 575 not be used for anything meaningful.
577 576
578 577 ``stdin``, ``stdout``, and ``stderr`` are the pipes connected to the
579 578 SSH server's stdio handles.
580 579
581 580 This function is factored out to allow creating peers that don't
582 581 actually spawn a new process. It is useful for starting SSH protocol
583 582 servers and clients via non-standard means, which can be useful for
584 583 testing.
585 584 """
586 585 try:
587 586 protoname, caps = _performhandshake(ui, stdin, stdout, stderr)
588 587 except Exception:
589 588 _cleanuppipes(ui, stdout, stdin, stderr, warn=None)
590 589 raise
591 590
592 591 if protoname == wireprototypes.SSHV1:
593 592 return sshv1peer(
594 593 ui,
595 594 path,
596 595 proc,
597 596 stdin,
598 597 stdout,
599 598 stderr,
600 599 caps,
601 600 autoreadstderr=autoreadstderr,
602 601 )
603 602 else:
604 603 _cleanuppipes(ui, stdout, stdin, stderr, warn=None)
605 604 raise error.RepoError(
606 605 _(b'unknown version of SSH protocol: %s') % protoname
607 606 )
608 607
609 608
610 609 def make_peer(ui, path, create, intents=None, createopts=None):
611 610 """Create an SSH peer.
612 611
613 612 The returned object conforms to the ``wireprotov1peer.wirepeer`` interface.
614 613 """
615 path = path.loc
616 u = urlutil.url(path, parsequery=False, parsefragment=False)
614 u = urlutil.url(path.loc, parsequery=False, parsefragment=False)
617 615 if u.scheme != b'ssh' or not u.host or u.path is None:
618 616 raise error.RepoError(_(b"couldn't parse location %s") % path)
619 617
620 urlutil.checksafessh(path)
618 urlutil.checksafessh(path.loc)
621 619
622 620 if u.passwd is not None:
623 621 raise error.RepoError(_(b'password in URL not supported'))
624 622
625 623 sshcmd = ui.config(b'ui', b'ssh')
626 624 remotecmd = ui.config(b'ui', b'remotecmd')
627 625 sshaddenv = dict(ui.configitems(b'sshenv'))
628 626 sshenv = procutil.shellenviron(sshaddenv)
629 627 remotepath = u.path or b'.'
630 628
631 629 args = procutil.sshargs(sshcmd, u.host, u.user, u.port)
632 630
633 631 if create:
634 632 # We /could/ do this, but only if the remote init command knows how to
635 633 # handle them. We don't yet make any assumptions about that. And without
636 634 # querying the remote, there's no way of knowing if the remote even
637 635 # supports said requested feature.
638 636 if createopts:
639 637 raise error.RepoError(
640 638 _(
641 639 b'cannot create remote SSH repositories '
642 640 b'with extra options'
643 641 )
644 642 )
645 643
646 644 cmd = b'%s %s %s' % (
647 645 sshcmd,
648 646 args,
649 647 procutil.shellquote(
650 648 b'%s init %s'
651 649 % (_serverquote(remotecmd), _serverquote(remotepath))
652 650 ),
653 651 )
654 652 ui.debug(b'running %s\n' % cmd)
655 653 res = ui.system(cmd, blockedtag=b'sshpeer', environ=sshenv)
656 654 if res != 0:
657 655 raise error.RepoError(_(b'could not create remote repo'))
658 656
659 657 proc, stdin, stdout, stderr = _makeconnection(
660 658 ui, sshcmd, args, remotecmd, remotepath, sshenv
661 659 )
662 660
663 661 peer = makepeer(ui, path, proc, stdin, stdout, stderr)
664 662
665 663 # Finally, if supported by the server, notify it about our own
666 664 # capabilities.
667 665 if b'protocaps' in peer.capabilities():
668 666 try:
669 667 peer._call(
670 668 b"protocaps", caps=b' '.join(sorted(_clientcapabilities()))
671 669 )
672 670 except IOError:
673 671 peer._cleanup()
674 672 raise error.RepoError(_(b'capability exchange failed'))
675 673
676 674 return peer
General Comments 0
You need to be logged in to leave comments. Login now