##// END OF EJS Templates
xdiff: add a preprocessing step that trims files...
xdiff: add a preprocessing step that trims files xdiff has a `xdl_trim_ends` step that removes common lines, unmatchable lines. That is in theory good, but happens too late - after splitting, hashing, and adjusting the hash values so they are unique. Those splitting, hashing and adjusting hash values steps could have noticeable overhead. Diffing two large files with minor (one-line-ish) changes are not uncommon. In that case, the raw performance of those preparation steps seriously matter. Even allocating an O(N) array and storing line offsets to it is expensive. Therefore my previous attempts [1] [2] cannot be good enough since they do not remove the O(N) array assignment. This patch adds a preprocessing step - `xdl_trim_files` that runs before other preprocessing steps. It counts common prefix and suffix and lines in them (needed for displaying line number), without doing anything else. Testing with a crafted large (169MB) file, with minor change: ``` open('a','w').write(''.join('%s\n' % (i % 100000) for i in xrange(30000000) if i != 6000000)) open('b','w').write(''.join('%s\n' % (i % 100000) for i in xrange(30000000) if i != 6003000)) ``` Running xdiff by a simple binary [3], this patch improves the xdiff perf by more than 10x for the above case: ``` # xdiff before this patch 2.41s user 1.13s system 98% cpu 3.592 total # xdiff after this patch 0.14s user 0.16s system 98% cpu 0.309 total # gnu diffutils 0.12s user 0.15s system 98% cpu 0.272 total # (best of 20 runs) ``` It's still slightly slower than GNU diffutils. But it's pretty close now. Testing with real repo data: For the whole repo, this patch makes xdiff 25% faster: ``` # hg perfbdiff --count 100 --alldata -c d334afc585e2 --blocks [--xdiff] # xdiff, after ! wall 0.058861 comb 0.050000 user 0.050000 sys 0.000000 (best of 100) # xdiff, before ! wall 0.077816 comb 0.080000 user 0.080000 sys 0.000000 (best of 91) # bdiff ! wall 0.117473 comb 0.120000 user 0.120000 sys 0.000000 (best of 67) ``` For files that are long (ex. commands.py), the speedup is more than 3x, very significant: ``` # hg perfbdiff --count 3000 --blocks commands.py.i 1 [--xdiff] # xdiff, after ! wall 0.690583 comb 0.690000 user 0.690000 sys 0.000000 (best of 12) # xdiff, before ! wall 2.240361 comb 2.210000 user 2.210000 sys 0.000000 (best of 4) # bdiff ! wall 2.469852 comb 2.440000 user 2.440000 sys 0.000000 (best of 4) ``` [1]: https://phab.mercurial-scm.org/D2631 [2]: https://phab.mercurial-scm.org/D2634 [3]: ``` // Code to run xdiff from command line. No proper error handling. #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include "mercurial/thirdparty/xdiff/xdiff.h" #define ensure(x) if (!(x)) exit(255); mmfile_t readfile(const char *path) { struct stat st; int fd = open(path, O_RDONLY); fstat(fd, &st); mmfile_t file = { malloc(st.st_size), st.st_size }; ensure(read(fd, file.ptr, st.st_size) == st.st_size); close(fd); return file; } int main(int argc, char const *argv[]) { mmfile_t a = readfile(argv[1]), b = readfile(argv[2]); xpparam_t xpp = {0}; xdemitconf_t xecfg = {0}; xdemitcb_t ecb = {0}; xdl_diff(&a, &b, &xpp, &xecfg, &ecb); return 0; } ``` Differential Revision: https://phab.mercurial-scm.org/D2686

File last commit:

r36646:70415568 default
r36838:f33a87cf default
Show More
sshpeer.py
612 lines | 20.7 KiB | text/x-python | PythonLexer
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 # sshpeer.py - ssh repository proxy class for mercurial
#
# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
Gregory Szorc
sshpeer: use absolute_import
r25975 from __future__ import absolute_import
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 import re
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 import uuid
Gregory Szorc
sshpeer: use absolute_import
r25975
from .i18n import _
from . import (
error,
Pulkit Goyal
py3: use pycompat.byteskwargs() to convert kwargs' keys to bytes...
r33100 pycompat,
Gregory Szorc
sshpeer: use absolute_import
r25975 util,
wireproto,
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 wireprotoserver,
Gregory Szorc
wireprotoserver: move SSHV1 and SSHV2 constants to wireprototypes...
r36553 wireprototypes,
Gregory Szorc
sshpeer: use absolute_import
r25975 )
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
def _serverquote(s):
Yuya Nishihara
sshpeer: move docstring to top...
r35475 """quote a string for the remote shell ... which we assume is sh"""
Matt Mackall
sshpeer: more thorough shell quoting...
r23671 if not s:
return s
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
return s
return "'%s'" % s.replace("'", "'\\''")
Pierre-Yves David
sshpeer: extract the forward output logic...
r25244 def _forwardoutput(ui, pipe):
"""display all data currently available on pipe as remote output.
This is non blocking."""
s = util.readpipe(pipe)
if s:
for l in s.splitlines():
ui.status(_("remote: "), l, '\n')
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 class doublepipe(object):
"""Operate a side-channel pipe in addition of a main one
The side-channel pipe contains server output to be forwarded to the user
input. The double pipe will behave as the "main" pipe, but will ensure the
content of the "side" pipe is properly processed while we wait for blocking
call on the "main" pipe.
If large amounts of data are read from "main", the forward will cease after
the first bytes start to appear. This simplifies the implementation
without affecting actual output of sshpeer too much as we rarely issue
large read for data not yet emitted by the server.
The main pipe is expected to be a 'bufferedinputpipe' from the util module
Augie Fackler
sshpeer: fix docstring typo
r31953 that handle all the os specific bits. This class lives in this module
Mads Kiilerich
spelling: trivial spell checking
r26781 because it focus on behavior specific to the ssh protocol."""
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421
def __init__(self, ui, main, side):
self._ui = ui
self._main = main
self._side = side
def _wait(self):
"""wait until some data are available on main or side
return a pair of boolean (ismainready, issideready)
(This will only wait for data if the setup is supported by `util.poll`)
"""
Gregory Szorc
sshpeer: make pipe polling code more explicit...
r36387 if (isinstance(self._main, util.bufferedinputpipe) and
self._main.hasbuffer):
# Main has data. Assume side is worth poking at.
return True, True
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 fds = [self._main.fileno(), self._side.fileno()]
try:
act = util.poll(fds)
except NotImplementedError:
# non supported yet case, assume all have data.
act = fds
return (self._main.fileno() in act, self._side.fileno() in act)
Pierre-Yves David
sshpeer: allow write operations through double pipe...
r25456 def write(self, data):
return self._call('write', data)
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 def read(self, size):
Augie Fackler
sshpeer: try harder to snag stderr when stdout closes unexpectedly...
r32062 r = self._call('read', size)
if size != 0 and not r:
# We've observed a condition that indicates the
# stdout closed unexpectedly. Check stderr one
# more time and snag anything that's there before
# letting anyone know the main part of the pipe
# closed prematurely.
_forwardoutput(self._ui, self._side)
return r
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421
def readline(self):
return self._call('readline')
Pierre-Yves David
sshpeer: rename 'size' to 'data' in doublepipe...
r25455 def _call(self, methname, data=None):
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 """call <methname> on "main", forward output of "side" while blocking
"""
Pierre-Yves David
sshpeer: rename 'size' to 'data' in doublepipe...
r25455 # data can be '' or 0
if (data is not None and not data) or self._main.closed:
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 _forwardoutput(self._ui, self._side)
return ''
while True:
mainready, sideready = self._wait()
if sideready:
_forwardoutput(self._ui, self._side)
if mainready:
meth = getattr(self._main, methname)
Pierre-Yves David
sshpeer: rename 'size' to 'data' in doublepipe...
r25455 if data is None:
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 return meth()
else:
Pierre-Yves David
sshpeer: rename 'size' to 'data' in doublepipe...
r25455 return meth(data)
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421
def close(self):
return self._main.close()
Pierre-Yves David
sshpeer: allow write operations through double pipe...
r25456 def flush(self):
return self._main.flush()
Gregory Szorc
sshpeer: extract pipe cleanup logic to own function...
r35951 def _cleanuppipes(ui, pipei, pipeo, pipee):
"""Clean up pipes used by an SSH connection."""
if pipeo:
pipeo.close()
if pipei:
pipei.close()
if pipee:
# Try to read from the err descriptor until EOF.
try:
for l in pipee:
ui.status(_('remote: '), l)
except (IOError, ValueError):
pass
pipee.close()
Gregory Szorc
sshpeer: establish SSH connection before class instantiation...
r35953 def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None):
"""Create an SSH connection to a server.
Returns a tuple of (process, stdin, stdout, stderr) for the
spawned process.
"""
cmd = '%s %s %s' % (
sshcmd,
args,
util.shellquote('%s -R %s serve --stdio' % (
_serverquote(remotecmd), _serverquote(path))))
ui.debug('running %s\n' % cmd)
cmd = util.quotecommand(cmd)
# no buffer allow the use of 'select'
# feel free to remove buffering and select usage when we ultimately
# move to threading.
stdin, stdout, stderr, proc = util.popen4(cmd, bufsize=0, env=sshenv)
return proc, stdin, stdout, stderr
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 def _performhandshake(ui, stdin, stdout, stderr):
def badresponse():
Gregory Szorc
sshpeer: defer pipe buffering and stderr sidechannel binding...
r36388 # Flush any output on stderr.
_forwardoutput(ui, stderr)
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 msg = _('no suitable response from remote hg')
hint = ui.config('ui', 'ssherrorhint')
raise error.RepoError(msg, hint=hint)
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # The handshake consists of sending wire protocol commands in reverse
# order of protocol implementation and then sniffing for a response
# to one of them.
#
# Those commands (from oldest to newest) are:
Gregory Szorc
sshpeer: document the handshake mechanism...
r35957 #
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # ``between``
# Asks for the set of revisions between a pair of revisions. Command
# present in all Mercurial server implementations.
Gregory Szorc
sshpeer: document the handshake mechanism...
r35957 #
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # ``hello``
# Instructs the server to advertise its capabilities. Introduced in
# Mercurial 0.9.1.
#
# ``upgrade``
# Requests upgrade from default transport protocol version 1 to
# a newer version. Introduced in Mercurial 4.6 as an experimental
# feature.
Gregory Szorc
sshpeer: document the handshake mechanism...
r35957 #
# The ``between`` command is issued with a request for the null
# range. If the remote is a Mercurial server, this request will
# generate a specific response: ``1\n\n``. This represents the
# wire protocol encoded value for ``\n``. We look for ``1\n\n``
# in the output stream and know this is the response to ``between``
# and we're at the end of our handshake reply.
#
# The response to the ``hello`` command will be a line with the
# length of the value returned by that command followed by that
# value. If the server doesn't support ``hello`` (which should be
# rare), that line will be ``0\n``. Otherwise, the value will contain
# RFC 822 like lines. Of these, the ``capabilities:`` line contains
# the capabilities of the server.
#
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # The ``upgrade`` command isn't really a command in the traditional
# sense of version 1 of the transport because it isn't using the
# proper mechanism for formatting insteads: instead, it just encodes
# arguments on the line, delimited by spaces.
#
# The ``upgrade`` line looks like ``upgrade <token> <capabilities>``.
# If the server doesn't support protocol upgrades, it will reply to
# this line with ``0\n``. Otherwise, it emits an
# ``upgraded <token> <protocol>`` line to both stdout and stderr.
# Content immediately following this line describes additional
# protocol and server state.
#
Gregory Szorc
sshpeer: document the handshake mechanism...
r35957 # In addition to the responses to our command requests, the server
# may emit "banner" output on stdout. SSH servers are allowed to
# print messages to stdout on login. Issuing commands on connection
# allows us to flush this banner output from the server by scanning
# for output to our well-known ``between`` command. Of course, if
# the banner contains ``1\n\n``, this will throw off our detection.
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 requestlog = ui.configbool('devel', 'debug.peer-request')
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # Generate a random token to help identify responses to version 2
# upgrade request.
Gregory Szorc
py3: more robustly cast UUID to bytes...
r36060 token = pycompat.sysbytes(str(uuid.uuid4()))
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 upgradecaps = [
('proto', wireprotoserver.SSHV2),
]
upgradecaps = util.urlreq.urlencode(upgradecaps)
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 try:
pairsarg = '%s-%s' % ('0' * 40, '0' * 40)
handshake = [
'hello\n',
'between\n',
'pairs %d\n' % len(pairsarg),
pairsarg,
]
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # Request upgrade to version 2 if configured.
if ui.configbool('experimental', 'sshpeer.advertise-v2'):
ui.debug('sending upgrade request: %s %s\n' % (token, upgradecaps))
handshake.insert(0, 'upgrade %s %s\n' % (token, upgradecaps))
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 if requestlog:
ui.debug('devel-peer-request: hello\n')
ui.debug('sending hello command\n')
if requestlog:
ui.debug('devel-peer-request: between\n')
ui.debug('devel-peer-request: pairs: %d bytes\n' % len(pairsarg))
ui.debug('sending between command\n')
stdin.write(''.join(handshake))
stdin.flush()
except IOError:
badresponse()
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # Assume version 1 of wire protocol by default.
Gregory Szorc
wireprotoserver: move SSHV1 and SSHV2 constants to wireprototypes...
r36553 protoname = wireprototypes.SSHV1
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 reupgraded = re.compile(b'^upgraded %s (.*)$' % re.escape(token))
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 lines = ['', 'dummy']
max_noise = 500
while lines[-1] and max_noise:
try:
l = stdout.readline()
_forwardoutput(ui, stderr)
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994
# Look for reply to protocol upgrade request. It has a token
# in it, so there should be no false positives.
m = reupgraded.match(l)
if m:
protoname = m.group(1)
ui.debug('protocol upgraded to %s\n' % protoname)
# If an upgrade was handled, the ``hello`` and ``between``
# requests are ignored. The next output belongs to the
# protocol, so stop scanning lines.
break
# Otherwise it could be a banner, ``0\n`` response if server
# doesn't support upgrade.
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 if lines[-1] == '1\n' and l == '\n':
break
if l:
ui.debug('remote: ', l)
lines.append(l)
max_noise -= 1
except IOError:
badresponse()
else:
badresponse()
caps = set()
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # For version 1, we should see a ``capabilities`` line in response to the
# ``hello`` command.
Gregory Szorc
wireprotoserver: move SSHV1 and SSHV2 constants to wireprototypes...
r36553 if protoname == wireprototypes.SSHV1:
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 for l in reversed(lines):
# Look for response to ``hello`` command. Scan from the back so
# we don't misinterpret banner output as the command reply.
if l.startswith('capabilities:'):
caps.update(l[:-1].split(':')[1].split())
break
elif protoname == wireprotoserver.SSHV2:
# We see a line with number of bytes to follow and then a value
# looking like ``capabilities: *``.
line = stdout.readline()
try:
valuelen = int(line)
except ValueError:
badresponse()
capsline = stdout.read(valuelen)
if not capsline.startswith('capabilities: '):
badresponse()
Gregory Szorc
sshpeer: log remote capabilities after protocol upgrade...
r36234 ui.debug('remote: %s\n' % capsline)
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 caps.update(capsline.split(':')[1].split())
# Trailing newline.
stdout.read(1)
# Error if we couldn't find capabilities, this means:
Gregory Szorc
sshpeer: remove support for connecting to <0.9.1 servers (BC)...
r35958 #
# 1. Remote isn't a Mercurial server
# 2. Remote is a <0.9.1 Mercurial server
# 3. Remote is a future Mercurial server that dropped ``hello``
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # and other attempted handshake mechanisms.
Gregory Szorc
sshpeer: remove support for connecting to <0.9.1 servers (BC)...
r35958 if not caps:
badresponse()
Gregory Szorc
sshpeer: defer pipe buffering and stderr sidechannel binding...
r36388 # Flush any output on stderr before proceeding.
_forwardoutput(ui, stderr)
Gregory Szorc
sshpeer: implement peer for version 2 of wire protocol...
r35996 return protoname, caps
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956
Gregory Szorc
sshpeer: rename sshpeer class to sshv1peer (API)...
r35995 class sshv1peer(wireproto.wirepeer):
Gregory Szorc
sshpeer: support not reading and forwarding stderr...
r36550 def __init__(self, ui, url, proc, stdin, stdout, stderr, caps,
autoreadstderr=True):
Gregory Szorc
sshpeer: clean up API for sshpeer.__init__ (API)...
r35954 """Create a peer from an existing SSH connection.
``proc`` is a handle on the underlying SSH process.
``stdin``, ``stdout``, and ``stderr`` are handles on the stdio
pipes for that process.
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 ``caps`` is a set of capabilities supported by the remote.
Gregory Szorc
sshpeer: support not reading and forwarding stderr...
r36550 ``autoreadstderr`` denotes whether to automatically read from
stderr and to forward its output.
Gregory Szorc
sshpeer: clean up API for sshpeer.__init__ (API)...
r35954 """
self._url = url
Gregory Szorc
sshpeer: use peer interface...
r33803 self._ui = ui
Gregory Szorc
sshpeer: establish SSH connection before class instantiation...
r35953 # self._subprocess is unused. Keeping a handle on the process
# holds a reference and prevents it from being garbage collected.
Gregory Szorc
sshpeer: clean up API for sshpeer.__init__ (API)...
r35954 self._subprocess = proc
Gregory Szorc
sshpeer: defer pipe buffering and stderr sidechannel binding...
r36388
# And we hook up our "doublepipe" wrapper to allow querying
# stderr any time we perform I/O.
Gregory Szorc
sshpeer: support not reading and forwarding stderr...
r36550 if autoreadstderr:
stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr)
stdin = doublepipe(ui, stdin, stderr)
Gregory Szorc
sshpeer: defer pipe buffering and stderr sidechannel binding...
r36388
Gregory Szorc
sshpeer: clean up API for sshpeer.__init__ (API)...
r35954 self._pipeo = stdin
self._pipei = stdout
self._pipee = stderr
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 self._caps = caps
Gregory Szorc
sshpeer: don't read from stderr when that behavior is disabled...
r36626 self._autoreadstderr = autoreadstderr
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
sshpeer: return framed file object when needed...
r36385 # Commands that have a "framed" response where the first line of the
# response contains the length of that response.
_FRAMED_COMMANDS = {
'batch',
}
Gregory Szorc
sshpeer: use peer interface...
r33803 # Begin of _basepeer interface.
@util.propertycache
def ui(self):
return self._ui
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 def url(self):
return self._url
Gregory Szorc
sshpeer: use peer interface...
r33803 def local(self):
return None
def peer(self):
return self
def canpush(self):
return True
def close(self):
pass
# End of _basepeer interface.
# Begin of _basewirecommands interface.
def capabilities(self):
return self._caps
# End of _basewirecommands interface.
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 def _readerr(self):
_forwardoutput(self.ui, self._pipee)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
def _abort(self, exception):
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._cleanup()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 raise exception
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 def _cleanup(self):
Gregory Szorc
sshpeer: extract pipe cleanup logic to own function...
r35951 _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 __del__ = _cleanup
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
sshpeer: return framed file object when needed...
r36385 def _sendrequest(self, cmd, args, framed=False):
Boris Feld
sshpeer: add support for request tracing...
r35717 if (self.ui.debugflag
and self.ui.configbool('devel', 'debug.peer-request')):
dbg = self.ui.debug
line = 'devel-peer-request: %s\n'
dbg(line % cmd)
for key, value in sorted(args.items()):
if not isinstance(value, dict):
dbg(line % ' %s: %d bytes' % (key, len(value)))
else:
for dk, dv in sorted(value.items()):
dbg(line % ' %s-%s: %d' % (key, dk, len(dv)))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 self.ui.debug("sending %s command\n" % cmd)
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.write("%s\n" % cmd)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 _func, names = wireproto.commands[cmd]
keys = names.split()
wireargs = {}
for k in keys:
if k == '*':
wireargs['*'] = args
break
else:
wireargs[k] = args[k]
del args[k]
for k, v in sorted(wireargs.iteritems()):
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.write("%s %d\n" % (k, len(v)))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if isinstance(v, dict):
for dk, dv in v.iteritems():
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.write("%s %d\n" % (dk, len(dv)))
self._pipeo.write(dv)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 else:
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.write(v)
self._pipeo.flush()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
sshpeer: return framed file object when needed...
r36385 # We know exactly how many bytes are in the response. So return a proxy
# around the raw output stream that allows reading exactly this many
# bytes. Callers then can read() without fear of overrunning the
# response.
if framed:
amount = self._getamount()
return util.cappedreader(self._pipei, amount)
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 return self._pipei
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
sshpeer: move logic for sending a request into a new function...
r36384 def _callstream(self, cmd, **args):
args = pycompat.byteskwargs(args)
Gregory Szorc
sshpeer: return framed file object when needed...
r36385 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
Gregory Szorc
sshpeer: move logic for sending a request into a new function...
r36384
Pierre-Yves David
wireproto: drop the _decompress method in favor a new call type...
r20905 def _callcompressable(self, cmd, **args):
Gregory Szorc
sshpeer: move logic for sending a request into a new function...
r36384 args = pycompat.byteskwargs(args)
Gregory Szorc
sshpeer: return framed file object when needed...
r36385 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
Pierre-Yves David
wireproto: drop the _decompress method in favor a new call type...
r20905
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 def _call(self, cmd, **args):
Gregory Szorc
sshpeer: move logic for sending a request into a new function...
r36384 args = pycompat.byteskwargs(args)
Gregory Szorc
sshpeer: return framed file object when needed...
r36385 return self._sendrequest(cmd, args, framed=True).read()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
def _callpush(self, cmd, fp, **args):
Gregory Szorc
wireproto: document the wonky push protocol for SSH...
r36390 # The server responds with an empty frame if the client should
# continue submitting the payload.
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 r = self._call(cmd, **args)
if r:
return '', r
Gregory Szorc
wireproto: document the wonky push protocol for SSH...
r36390
# The payload consists of frames with content followed by an empty
# frame.
Augie Fackler
sshpeer: use `iter(callable, sentinel)` instead of while True...
r29727 for d in iter(lambda: fp.read(4096), ''):
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 self._writeframed(d)
self._writeframed("", flush=True)
Gregory Szorc
wireproto: document the wonky push protocol for SSH...
r36390
# In case of success, there is an empty frame and a frame containing
# the integer result (as a string).
# In case of error, there is a non-empty frame containing the error.
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 r = self._readframed()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if r:
return '', r
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 return self._readframed(), ''
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Pierre-Yves David
sshpeer: add implementation of _calltwowaystream...
r21073 def _calltwowaystream(self, cmd, fp, **args):
Gregory Szorc
wireproto: document the wonky push protocol for SSH...
r36390 # The server responds with an empty frame if the client should
# continue submitting the payload.
Pierre-Yves David
sshpeer: add implementation of _calltwowaystream...
r21073 r = self._call(cmd, **args)
if r:
# XXX needs to be made better
liscju
i18n: translate abort messages...
r29389 raise error.Abort(_('unexpected remote reply: %s') % r)
Gregory Szorc
wireproto: document the wonky push protocol for SSH...
r36390
# The payload consists of frames with content followed by an empty
# frame.
Augie Fackler
sshpeer: use `iter(callable, sentinel)` instead of while True...
r29727 for d in iter(lambda: fp.read(4096), ''):
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 self._writeframed(d)
self._writeframed("", flush=True)
Gregory Szorc
wireproto: document the wonky push protocol for SSH...
r36390
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 return self._pipei
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Augie Fackler
wireproto: make iterbatcher behave streamily over http(s)...
r28438 def _getamount(self):
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 l = self._pipei.readline()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if l == '\n':
Gregory Szorc
sshpeer: don't read from stderr when that behavior is disabled...
r36626 if self._autoreadstderr:
self._readerr()
Pierre-Yves David
sshpeer: break "OutOfBandError" feature for ssh (BC)...
r25243 msg = _('check previous remote output')
self._abort(error.OutOfBandError(hint=msg))
Gregory Szorc
sshpeer: don't read from stderr when that behavior is disabled...
r36626 if self._autoreadstderr:
self._readerr()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 try:
Augie Fackler
wireproto: make iterbatcher behave streamily over http(s)...
r28438 return int(l)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 except ValueError:
self._abort(error.ResponseError(_("unexpected response:"), l))
Augie Fackler
wireproto: make iterbatcher behave streamily over http(s)...
r28438
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 def _readframed(self):
Gregory Szorc
sshpeer: don't read(0)...
r36646 size = self._getamount()
if not size:
return b''
return self._pipei.read(size)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 def _writeframed(self, data, flush=False):
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.write("%d\n" % len(data))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if data:
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.write(data)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if flush:
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.flush()
Gregory Szorc
sshpeer: don't read from stderr when that behavior is disabled...
r36626 if self._autoreadstderr:
self._readerr()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
sshpeer: implement peer for version 2 of wire protocol...
r35996 class sshv2peer(sshv1peer):
"""A peer that speakers version 2 of the transport protocol."""
# Currently version 2 is identical to version 1 post handshake.
# And handshake is performed before the peer is instantiated. So
# we need no custom code.
Gregory Szorc
sshpeer: support not reading and forwarding stderr...
r36550 def makepeer(ui, path, proc, stdin, stdout, stderr, autoreadstderr=True):
Gregory Szorc
sshpeer: factor out code for creating peers from pipes...
r36505 """Make a peer instance from existing pipes.
``path`` and ``proc`` are stored on the eventual peer instance and may
not be used for anything meaningful.
``stdin``, ``stdout``, and ``stderr`` are the pipes connected to the
SSH server's stdio handles.
This function is factored out to allow creating peers that don't
actually spawn a new process. It is useful for starting SSH protocol
servers and clients via non-standard means, which can be useful for
testing.
"""
try:
protoname, caps = _performhandshake(ui, stdin, stdout, stderr)
except Exception:
_cleanuppipes(ui, stdout, stdin, stderr)
raise
Gregory Szorc
wireprotoserver: move SSHV1 and SSHV2 constants to wireprototypes...
r36553 if protoname == wireprototypes.SSHV1:
Gregory Szorc
sshpeer: support not reading and forwarding stderr...
r36550 return sshv1peer(ui, path, proc, stdin, stdout, stderr, caps,
autoreadstderr=autoreadstderr)
Gregory Szorc
wireprotoserver: move SSHV1 and SSHV2 constants to wireprototypes...
r36553 elif protoname == wireprototypes.SSHV2:
Gregory Szorc
sshpeer: support not reading and forwarding stderr...
r36550 return sshv2peer(ui, path, proc, stdin, stdout, stderr, caps,
autoreadstderr=autoreadstderr)
Gregory Szorc
sshpeer: factor out code for creating peers from pipes...
r36505 else:
_cleanuppipes(ui, stdout, stdin, stderr)
raise error.RepoError(_('unknown version of SSH protocol: %s') %
protoname)
Gregory Szorc
sshpeer: make "instance" a function...
r35946 def instance(ui, path, create):
Gregory Szorc
sshpeer: move URL validation out of sshpeer.__init__...
r35949 """Create an SSH peer.
The returned object conforms to the ``wireproto.wirepeer`` interface.
"""
u = util.url(path, parsequery=False, parsefragment=False)
if u.scheme != 'ssh' or not u.host or u.path is None:
raise error.RepoError(_("couldn't parse location %s") % path)
util.checksafessh(path)
if u.passwd is not None:
raise error.RepoError(_('password in URL not supported'))
Gregory Szorc
sshpeer: move ssh command and repo creation logic out of __init__...
r35950 sshcmd = ui.config('ui', 'ssh')
remotecmd = ui.config('ui', 'remotecmd')
sshaddenv = dict(ui.configitems('sshenv'))
sshenv = util.shellenviron(sshaddenv)
remotepath = u.path or '.'
args = util.sshargs(sshcmd, u.host, u.user, u.port)
if create:
cmd = '%s %s %s' % (sshcmd, args,
util.shellquote('%s init %s' %
(_serverquote(remotecmd), _serverquote(remotepath))))
ui.debug('running %s\n' % cmd)
res = ui.system(cmd, blockedtag='sshpeer', environ=sshenv)
if res != 0:
raise error.RepoError(_('could not create remote repo'))
Gregory Szorc
sshpeer: establish SSH connection before class instantiation...
r35953 proc, stdin, stdout, stderr = _makeconnection(ui, sshcmd, args, remotecmd,
remotepath, sshenv)
Gregory Szorc
sshpeer: factor out code for creating peers from pipes...
r36505 return makepeer(ui, path, proc, stdin, stdout, stderr)