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