Commit Graph

43 Commits

Author SHA1 Message Date
Zuzanna Mroczek
344962042e sshpeer: add a configurable hint for the ssh error message
Adding a possibility to configure error hint to be shown in the case of problems with SSH. Example of such hint can be "Please see http://company/internalwiki/ssh.html".

Test Plan:
- Ran hg pull with broken link and verified the output has no hint by default:

```
pulling from ssh://brokenrepository.com//repo
remote: ssh: Could not resolve hostname brokenrepository.com: Name or service not known
abort: no suitable response from remote hg!
```

- Run hg pull --config ui.ssherrorhint="Please see http://company/internalwiki/ssh.html":

```
pulling from ssh://brokenrepository.com//repo
remote: ssh: Could not resolve hostname brokenrepository.com: Name or service not known
abort: no suitable response from remote hg!
(Please see http://company/internalwiki/ssh.html)
```

Differential Revision: https://phab.mercurial-scm.org/D1431
2017-11-20 01:40:26 -08:00
Durham Goode
477a318d3a ssh: fix flakey ssh errors on BSD systems
There's been a persistent issue with flakiness on BSD systems (like OSX) where
the 'no suitable response from remote hg' message would sometimes not appear.
This was caused by one of the earlier calls failing with a "IOError: Broken
pipe". Catching those errors and printing the same message removes the
flakiness.

Differential Revision: https://phab.mercurial-scm.org/D687
2017-09-11 15:59:18 -07:00
Gregory Szorc
d5338b2208 wireproto: use new peer interface
The wirepeer class provides concrete implementations of peer interface
methods for calling wire protocol commands. It makes sense for this
class to inherit from the peer abstract base class. So we change
that.

Since httppeer and sshpeer have already been converted to the new
interface, peerrepository is no longer adding any value. So it has
been removed. httppeer and sshpeer have been updated to reflect the
loss of peerrepository and the inheritance of the abstract base
class in wirepeer.

The code changes in wirepeer are reordering of methods to group
by interface.

Some Python code in tests was updated to reflect changed APIs.

.. api::

   peer.peerrepository has been removed. Use repository.peer abstract
   base class to represent a peer repository.

Differential Revision: https://phab.mercurial-scm.org/D338
2017-08-10 20:58:28 -07:00
Gregory Szorc
a5f89f74c6 sshpeer: use peer interface
We need the same @property conversion of ui like we did for localpeer.
We renamed _capabilities() to capabilities() to satisfy the new
naming requirement.

However, since we're inheriting from wireproto.wirepeer which inherits
from peer.peerrepository and provides its own code accessing
_capabilities(), we need to keep the old alias around. This wonkiness
will disappear once wirepeer is cleaned up in subsequent commits.

We also implement methods for basepeer that are identical to the
defaults in peer.peerrepository in preparation for the removal of
peerrepository.

Differential Revision: https://phab.mercurial-scm.org/D336
2017-08-06 17:59:48 -07:00
Gregory Szorc
b724074785 sshpeer: make instance attributes and methods internal
Peer types are supposed to conform to a formal interface defined by
peer.peerrepository and wireproto.wirepeer. Every "public" attribute on
*peer types makes it harder to understand what attributes are part
of the interface and what are instance specific.

This commit converts a number of "public" instance attributes and
methods on sshpeer to internal so they can't be confused to be part of
the peer API.

The URL-related instance attributes were introduced in 904c418bea16
in 2005. AFAICT most of them aren't used and could potentially be
removed. But I kept them around anyway.

I also reorded some code to make things slightly easier to read.

.. api::

   Rename attributes on sshpeer to reflect peer API

Differential Revision: https://phab.mercurial-scm.org/D331
2017-08-10 20:55:28 -07:00
Augie Fackler
ef945af30b merge with stable 2017-08-10 18:55:33 -04:00
Jun Wu
a0e5a4defb ssh: quote parameters using shellquote (SEC)
This patch uses shellquote to quote ssh parameters more strictly to avoid
shell injection.
2017-08-04 23:54:12 -07:00
Sean Farley
e199b92002 sshpeer: check for safe ssh url (SEC)
Checking in the sshpeer for a rogue ssh:// urls seems like the right
place to do it (instead of whack-a-mole with pull, clone, push, etc).
2017-08-01 14:40:19 -07:00
Gregory Szorc
59e773f0f6 exchange: drop support for lock-based unbundling (BC)
Locking over the wire protocol and the "addchangegroup" wire
protocol command has been deprecated since f8e443eb02c9, which was
first part of Mercurial 0.9.1.

Support for handling these commands from sshserver was dropped in
93297d5f4df2 in 2015, effectively locking out pre 0.9.1 clients
from new servers.

However, client-side code for calling lock and addchangegroup is
still present in exchange.py and the various peer classes to
facilitate pushing to pre 0.9.1 servers.

The lock-based pushing mechanism is extremely brittle. 0.9.1 was
released in July 2006 and I highly doubt anyone is still running
such an ancient version of Mercurial on a server. I'm about to
refactor the peer API and I don't think it is worth keeping
support for this ancient protocol feature. So, this commit removes
client support for the lock-based pushing mechanism. This means
modern clients will no longer be able to push to pre 0.9.1 servers.

Differential Revision: https://phab.mercurial-scm.org/D264
2017-08-06 17:44:56 -07:00
Jun Wu
e47f7dc2fa codemod: register core configitems using a script
This is done by a script [2] using RedBaron [1], a tool designed for doing
code refactoring. All "default" values are decided by the script and are
strongly consistent with the existing code.

There are 2 changes done manually to fix tests:

  [warn] mercurial/exchange.py: experimental.bundle2-output-capture: default needs manual removal
  [warn] mercurial/localrepo.py: experimental.hook-track-tags: default needs manual removal

Since RedBaron is not confident about how to indent things [2].

[1]: https://github.com/PyCQA/redbaron
[2]: https://github.com/PyCQA/redbaron/issues/100
[3]:

#!/usr/bin/env python
# codemod_configitems.py - codemod tool to fill configitems
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function

import os
import sys

import redbaron

def readpath(path):
    with open(path) as f:
        return f.read()

def writepath(path, content):
    with open(path, 'w') as f:
        f.write(content)

_configmethods = {'config', 'configbool', 'configint', 'configbytes',
                  'configlist', 'configdate'}

def extractstring(rnode):
    """get the string from a RedBaron string or call_argument node"""
    while rnode.type != 'string':
        rnode = rnode.value
    return rnode.value[1:-1]  # unquote, "'str'" -> "str"

def uiconfigitems(red):
    """match *.ui.config* pattern, yield (node, method, args, section, name)"""
    for node in red.find_all('atomtrailers'):
        entry = None
        try:
            obj = node[-3].value
            method = node[-2].value
            args = node[-1]
            section = args[0].value
            name = args[1].value
            if (obj in ('ui', 'self') and method in _configmethods
                and section.type == 'string' and name.type == 'string'):
                entry = (node, method, args, extractstring(section),
                         extractstring(name))
        except Exception:
            pass
        else:
            if entry:
                yield entry

def coreconfigitems(red):
    """match coreconfigitem(...) pattern, yield (node, args, section, name)"""
    for node in red.find_all('atomtrailers'):
        entry = None
        try:
            args = node[1]
            section = args[0].value
            name = args[1].value
            if (node[0].value == 'coreconfigitem' and section.type == 'string'
                and name.type == 'string'):
                entry = (node, args, extractstring(section),
                         extractstring(name))
        except Exception:
            pass
        else:
            if entry:
                yield entry

def registercoreconfig(cfgred, section, name, defaultrepr):
    """insert coreconfigitem to cfgred AST

    section and name are plain string, defaultrepr is a string
    """
    # find a place to insert the "coreconfigitem" item
    entries = list(coreconfigitems(cfgred))
    for node, args, nodesection, nodename in reversed(entries):
        if (nodesection, nodename) < (section, name):
            # insert after this entry
            node.insert_after(
                'coreconfigitem(%r, %r,\n'
                '    default=%s,\n'
                ')' % (section, name, defaultrepr))
            return

def main(argv):
    if not argv:
        print('Usage: codemod_configitems.py FILES\n'
              'For example, FILES could be "{hgext,mercurial}/*/**.py"')
    dirname = os.path.dirname
    reporoot = dirname(dirname(dirname(os.path.abspath(__file__))))

    # register configitems to this destination
    cfgpath = os.path.join(reporoot, 'mercurial', 'configitems.py')
    cfgred = redbaron.RedBaron(readpath(cfgpath))

    # state about what to do
    registered = set((s, n) for n, a, s, n in coreconfigitems(cfgred))
    toregister = {} # {(section, name): defaultrepr}
    coreconfigs = set() # {(section, name)}, whether it's used in core

    # first loop: scan all files before taking any action
    for i, path in enumerate(argv):
        print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
        iscore = ('mercurial' in path) and ('hgext' not in path)
        red = redbaron.RedBaron(readpath(path))
        # find all repo.ui.config* and ui.config* calls, and collect their
        # section, name and default value information.
        for node, method, args, section, name in uiconfigitems(red):
            if section == 'web':
                # [web] section has some weirdness, ignore them for now
                continue
            defaultrepr = None
            key = (section, name)
            if len(args) == 2:
                if key in registered:
                    continue
                if method == 'configlist':
                    defaultrepr = 'list'
                elif method == 'configbool':
                    defaultrepr = 'False'
                else:
                    defaultrepr = 'None'
            elif len(args) >= 3 and (args[2].target is None or
                                     args[2].target.value == 'default'):
                # try to understand the "default" value
                dnode = args[2].value
                if dnode.type == 'name':
                    if dnode.value in {'None', 'True', 'False'}:
                        defaultrepr = dnode.value
                elif dnode.type == 'string':
                    defaultrepr = repr(dnode.value[1:-1])
                elif dnode.type in ('int', 'float'):
                    defaultrepr = dnode.value
            # inconsistent default
            if key in toregister and toregister[key] != defaultrepr:
                defaultrepr = None
            # interesting to rewrite
            if key not in registered:
                if defaultrepr is None:
                    print('[note] %s: %s.%s: unsupported default'
                          % (path, section, name))
                    registered.add(key) # skip checking it again
                else:
                    toregister[key] = defaultrepr
                    if iscore:
                        coreconfigs.add(key)

    # second loop: rewrite files given "toregister" result
    for path in argv:
        # reconstruct redbaron - trade CPU for memory
        red = redbaron.RedBaron(readpath(path))
        changed = False
        for node, method, args, section, name in uiconfigitems(red):
            key = (section, name)
            defaultrepr = toregister.get(key)
            if defaultrepr is None or key not in coreconfigs:
                continue
            if len(args) >= 3 and (args[2].target is None or
                                   args[2].target.value == 'default'):
                try:
                    del args[2]
                    changed = True
                except Exception:
                    # redbaron fails to do the rewrite due to indentation
                    # see https://github.com/PyCQA/redbaron/issues/100
                    print('[warn] %s: %s.%s: default needs manual removal'
                          % (path, section, name))
            if key not in registered:
                print('registering %s.%s' % (section, name))
                registercoreconfig(cfgred, section, name, defaultrepr)
                registered.add(key)
        if changed:
            print('updating %s' % path)
            writepath(path, red.dumps())

    if toregister:
        print('updating configitems.py')
        writepath(cfgpath, cfgred.dumps())

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
2017-07-14 14:22:40 -07:00
Pulkit Goyal
0993aa87aa py3: use pycompat.byteskwargs() to convert kwargs' keys to bytes
This is used where ever required like where kwargs are passed into
ui.formatter(), scmutil.match() or cmdutil.openrevlog() which expects bytes.
2017-06-27 00:20:55 +05:30
Augie Fackler
2f7c844e98 sshpeer: try harder to snag stderr when stdout closes unexpectedly
Resolves test failures on FreeBSD, but I'm not happy about the fix.

A previous version of this also wrapped readline by putting the hack
in the _call method on doublepipe. That was confusing for readers and
wasn't necessary - just doing this on read() is sufficient to fix the
bugs I'm observing. We can always come back and do readline later if
needed.
2017-04-13 16:09:40 -04:00
Augie Fackler
fe10e9b912 sshpeer: fix docstring typo 2017-04-13 14:48:18 -04:00
Simon Farnsworth
bdb0a7478b sshpeer: set a blockedtag when starting ssh
So that the data is readable.
2017-03-06 03:25:09 -08:00
Gregory Szorc
7315f10dde wireproto: consolidate code for obtaining "cmds" argument value
Both wireproto.py and sshpeer.py had code for producing the value to
the "cmds" argument used by the "batch" command. This patch extracts
that code to a standalone function and uses it.
2016-08-06 13:46:28 -07:00
Augie Fackler
15bb27e43f sshpeer: use iter(callable, sentinel) instead of while True
This is functionally equivalent, but is a little more concise.
2016-08-05 14:00:22 -04:00
liscju
c7ec9d159e i18n: translate abort messages
I found a few places where message given to abort is
not translated, I don't find any reason to not translate
them.
2016-06-14 11:53:55 +02:00
Augie Fackler
af1601947d wireproto: make iterbatcher behave streamily over http(s)
Unfortunately, the ssh and http implementations are slightly different
due to differences in their _callstream implementations, which
prevents ssh from behaving streamily. We should probably introduce a
new batch command that can stream results over ssh at some point in
the near future.

The streamy behavior of batch over http(s) is an enormous win for
remotefilelog over http: in my testing, it's saving about 40% on file
fetches with a cold cache against a server on localhost.
2016-03-01 18:41:43 -05:00
Bryan O'Sullivan
5ea0fa6c09 sshpeer: make remotelock a context manager 2016-01-15 13:14:50 -08:00
Mads Kiilerich
09567db49a spelling: trivial spell checking 2015-10-17 00:58:46 +02:00
Pierre-Yves David
30913031d4 error: get Abort from 'error' instead of 'util'
The home of 'Abort' is 'error' not 'util' however, a lot of code seems to be
confused about that and gives all the credit to 'util' instead of the
hardworking 'error'. In a spirit of equity, we break the cycle of injustice and
give back to 'error' the respect it deserves. And screw that 'util' poser.

For great justice.
2015-10-08 12:55:45 -07:00
Gregory Szorc
1251142350 sshpeer: use absolute_import 2015-08-08 19:55:01 -07:00
Pierre-Yves David
9e33e7774c sshpeer: also use doublepipe for client to server communication
This will allow even more real time output when the server issue output in the
middle a stream push.
2015-05-20 11:55:59 -05:00
Pierre-Yves David
69155bf9f2 sshpeer: allow doublepipe on unbuffered main pipe
The output pipe does not have manually managed read buffer (actually, no read
anything). To also use the doublepipe for outgoing write (useful to consume
remote output when pushing large set of data) we need the doublepipe to work on
standard pipe too.
2015-06-05 04:54:23 -07:00
Pierre-Yves David
6a2621d0a1 sshpeer: allow write operations through double pipe
We have a shiny toy, lets make it wider.
2015-05-20 10:58:29 -05:00
Pierre-Yves David
25d5d30d03 sshpeer: rename 'size' to 'data' in doublepipe
We are about to add 'write' support, the argument will be either an int or a
string.
2015-05-20 17:40:47 -05:00
Pierre-Yves David
b4290f7a14 sshpeer: use the doublepipe object for the server to client channel
This restores real-time output from ssh server while waiting for protocol data
sent by the server.
2015-05-20 11:41:48 -05:00
Pierre-Yves David
fdc193e836 sshpeer: introduce a "doublepipe" class
This class is responsible for ensuring we still process the server output
streamed through the ssh's 'stderr' pipe during the initial wait for other
protocol streams.

It currently only works on posix system because of its use of 'select.select'.
2015-05-22 10:48:11 -05:00
Pierre-Yves David
e6518023e3 sshpeer: run the ssh command unbuffered
This is necessary to use non-blocking IO base on polling. Such polling is
needed to restore real time output with ssh peer.

Changeset 6a565336bae3 is talking about 5x regression on Mac OS X when playing
with this value. So we introduced our own buffering layer in previous
changesets. This seems to keep the regression away (we are even issuing much
less read).
2015-05-20 11:31:38 -05:00
Pierre-Yves David
de72eff352 sshpeer: use a 'bufferedinputpipe' for standard output of the ssh process
We need this pipe to still be buffered when will switch to unbuffered pipe.
(switch motivated by the need of using polling to restore real time output from
ssh server). This is the only pipe that needs to be wrapped because this is the
one who do extensive usage of 'readline'. The stderr pipe of the process is
alway read in non blocking raw chunk, so it won't benefit from the
buffering.
2015-05-31 00:00:36 -07:00
Pierre-Yves David
c899dea3d5 sshpeer: extract the forward output logic
We are about to make a more aggressive use of this when reading and writing on
the other pipes. We it needs to be reusable.
2015-05-18 23:19:11 -05:00
Pierre-Yves David
b51b0deda6 sshpeer: break "OutOfBandError" feature for ssh (BC)
When we'll be using the ssh's 'stderr' for realtime output successfully, it will
no longer be possible to use 'stderr' to carry the error message (because it
is already consumed by the real time output logic.

This feature is very rarely used (test says largefile only) and I think
breaking its output pattern is worth the benefit of having real time output with
ssh.
2015-05-20 12:33:12 -05:00
Matt Mackall
3406ce4956 merge with stable 2014-12-29 16:39:20 -06:00
Matt Mackall
bf17f56a67 sshpeer: more thorough shell quoting
This fixes an issue spotted by Jesse Hertz.
2014-12-29 14:27:02 -06:00
Yuya Nishihara
ceda6fbba9 util.system: use ui.system() in place of optional ui.fout parameter 2014-11-08 13:06:22 +09:00
Yuya Nishihara
187868d5fe sshpeer: forward stdout of remote "hg init" to appropriate output channel
Otherwise, commandserver channel could be corrupted.
2014-10-14 21:59:39 +09:00
Gregory Szorc
db57d5e9d6 platform: implement readpipe()
Reading all available data from a pipe has a platform-dependent
implementation.

This patch establishes platform.readpipe() by copying the
inline implementation in sshpeer.readerr(). The implementations
for POSIX and Windows are currently identical. The POSIX
implementation will be changed in a subsequent patch.
2014-08-15 20:02:18 -07:00
Pierre-Yves David
1383ee8462 sshpeer: add implementation of _calltwowaystream
This implements the call needed by bundle2. The error handling is a bit flaky
right now, but bundle2 error handling in general is still flaky anyway. Bundle2
is still disabled by default, I do not expect this to be a problem.
2014-04-15 17:18:35 -04:00
Pierre-Yves David
e3dee4d3e4 wireproto: drop the _decompress method in favor a new call type
We already have multiple call function for multiple return type. The
`_decompress` function is only used for http and seems like a layer violation.
We drop it in favor of a new call type dedicated to "stream that may be useful to
compress".
2014-03-28 14:24:13 -07:00
Siddharth Agarwal
1c5bc58f8f sshpeer: only print out 'running ssh' messages in debug mode (BC)
Previously, if another command was run with --verbose, and for whatever reason
that invoked sshpeer, we'd get a 'running ssh' message from sshpeer. This extra
line would interfere with that command's output and cause dumb parsers to
break.

For example, hg annotate can be run with --verbose to get full usernames. This,
combined with the third-party remotefilelog extension which can cause ssh
connections to be created, leads to an extra 'running ssh' line that breaks
most parsers.

This patch is (BC) because hg pull --verbose will no longer print out exactly
what ssh command it is running.

No tests are affected by this change.
2014-03-18 13:40:03 -07:00
Matt Mackall
796c64f268 sshpeer: mark _validrepo internal 2013-07-16 11:18:16 -05:00
Durham Goode
97bdc357fb sshpeer: store subprocess so it cleans up correctly
When running 'hg pull --rebase', I was seeing this exception 100% of the
time as the python process was closing down:

Exception TypeError: TypeError("'NoneType' object is not callable",) in
<bound method Popen.__del__ of <subprocess.Popen object at 0x937c10>> ignored

By storing the subprocess on the sshpeer, the subprocess seems to clean up
correctly, and I no longer see the exception. I have no idea why this actually
works, but I get a 0% repro if I store the subprocess in self.subprocess,
and a 100% repro if I store None in self.subprocess.

Possibly related to issue 2240.
2013-03-08 16:59:36 -08:00
Peter Arrenbrecht
ef7b77046e peer: introduce real peer classes
This change separates peer implementations from the repository implementation.
localpeer currently is a simple pass-through to localrepository, except for
legacy calls, which have already been removed from localpeer. This ensures that
the local client code only uses the most modern peer API when talking to local
repos.

Peers have a .local() method which returns either None or the underlying
localrepository (or descendant thereof). Repos have a .peer() method to return
a freshly constructed localpeer. The latter is used by hg.peer(), and also to
allow folks to pass either a peer or a repo to some generic helper methods.
We might want to get rid of .peer() eventually.

The only user of locallegacypeer is debugdiscovery, which uses it to pose as a
pre-setdiscovery client. But we decided to leave the old API defined in
locallegacypeer for clarity and maybe for other uses in the future.

It might be nice to actually define the peer API directly in peer.py as stub
methods. One problem there is, however, that localpeer implements
lock/addchangegroup, whereas the true remote peers implement unbundle.
It might be desireable to get rid of this distinction eventually.
2012-07-13 21:47:06 +02:00