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