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