Hello community,

here is the log from the commit of package python-Fabric for openSUSE:Factory 
checked in at 2019-04-19 18:38:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-Fabric (Old)
 and      /work/SRC/openSUSE:Factory/.python-Fabric.new.5536 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-Fabric"

Fri Apr 19 18:38:24 2019 rev:30 rq:693161 version:2.4.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-Fabric/python-Fabric.changes      
2018-08-24 17:03:46.150047551 +0200
+++ /work/SRC/openSUSE:Factory/.python-Fabric.new.5536/python-Fabric.changes    
2019-04-19 18:38:26.191188786 +0200
@@ -1,0 +2,33 @@
+Tue Apr  9 06:20:04 UTC 2019 - John Paul Adrian Glaubitz 
<[email protected]>
+
+- Version update to 2.4.0:
+  * [Feature] #1709: Add Group.close to allow closing an entire group’s
+    worth of connections at once. Patch via Johannes Löthberg.
+  * [Feature] #1780: Add context manager behavior to Group, to match
+    the same feature in Connection. Feature request by István Sárándi.
+  * [Feature] #1849: Add Connection.from_v1 (and Config.from_v1) for
+    easy creation of modern Connection/Config objects from the currently
+    configured Fabric 1.x environment. Should make upgrading piecemeal
+    much easier for many use cases.
+- additional changes from version 2.3.2:
+    * [Bug] #1852: Grant internal Connection objects created during
+      ProxyJump based gateways/proxies a copy of the outer Connection’s
+      configuration object. This was not previously done, which among
+      other things meant one could not fully disable SSH config file
+      loading (as the internal Connection objects would revert to the
+      default behavior). Thanks to Chris Adams for the report.
+    * [Bug]: Some debug logging was reusing Invoke’s logger object,
+      generating log messages “named” after invoke instead of fabric.
+      This has been fixed by using Fabric’s own logger everywhere instead.
+    * [Bug] #1850: Skip over ProxyJump configuration directives in SSH
+      config data when they would cause self-referential RecursionError
+      (e.g. due to wildcard-using Host stanzas which include the jump
+      server itself). Reported by Chris Adams.
+    * [Bug]: Fix a bug preventing tab completion (using the Invoke-level
+      --complete flag) from completing task names correctly (behavior was
+      to act as if there were never any tasks present, even if there was
+      a valid fabfile nearby).
+- Add sed expresion to spec file to remove all vendoring from imports
+- Run testsuite using the new %pytest macro
+
+-------------------------------------------------------------------

Old:
----
  fabric-2.3.1.tar.gz

New:
----
  fabric-2.4.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-Fabric.spec ++++++
--- /var/tmp/diff_new_pack.KRfiku/_old  2019-04-19 18:38:26.691189420 +0200
+++ /var/tmp/diff_new_pack.KRfiku/_new  2019-04-19 18:38:26.691189420 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-Fabric
 #
-# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -12,13 +12,13 @@
 # license that conforms to the Open Source Definition (Version 1.9)
 # published by the Open Source Initiative.
 
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-Fabric
-Version:        2.3.1
+Version:        2.4.0
 Release:        0
 Summary:        A Pythonic tool for remote execution and deployment
 License:        BSD-2-Clause
@@ -66,6 +66,8 @@
 
 %prep
 %setup -q -n fabric-%{version}
+# fix all imports:
+sed -i 's/from invoke.vendor\./from\ /' fabric/connection.py fabric/group.py 
integration/concurrency.py tests/config.py tests/transfer.py tests/_util.py 
tests/connection.py tests/runners.py
 
 %build
 %python_build
@@ -76,9 +78,7 @@
 %python_clone -a %{buildroot}%{_bindir}/fab
 
 %check
-%{python_expand export PYTHONPATH=%{buildroot}%{$python_sitelib}
-py.test-%{$python_bin_suffix} tests/
-}
+%pytest tests/
 
 %post
 %python_install_alternative fab

++++++ fabric-2.3.1.tar.gz -> fabric-2.4.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/PKG-INFO new/fabric-2.4.0/PKG-INFO
--- old/fabric-2.3.1/PKG-INFO   2018-08-09 04:12:30.000000000 +0200
+++ new/fabric-2.4.0/PKG-INFO   2018-09-14 00:29:24.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: fabric
-Version: 2.3.1
+Version: 2.4.0
 Summary: High level SSH command execution
 Home-page: http://fabfile.org
 Author: Jeff Forcier
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/fabric/_version.py 
new/fabric-2.4.0/fabric/_version.py
--- old/fabric-2.3.1/fabric/_version.py 2018-08-09 04:12:26.000000000 +0200
+++ new/fabric-2.4.0/fabric/_version.py 2018-09-14 00:29:20.000000000 +0200
@@ -1,2 +1,2 @@
-__version_info__ = (2, 3, 1)
+__version_info__ = (2, 4, 0)
 __version__ = ".".join(map(str, __version_info__))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/fabric/config.py 
new/fabric-2.4.0/fabric/config.py
--- old/fabric-2.3.1/fabric/config.py   2018-08-09 03:20:43.000000000 +0200
+++ new/fabric-2.4.0/fabric/config.py   2018-09-14 00:29:20.000000000 +0200
@@ -24,6 +24,8 @@
     - it extends the API to account for loading ``ssh_config`` files (which are
       stored as additional attributes and have no direct relation to the
       regular config data/hierarchy.)
+    - it adds a new optional constructor, `from_v1`, which :ref:`generates
+      configuration data from Fabric 1 <from-v1>`.
 
     Intended for use with `.Connection`, as using vanilla
     `invoke.config.Config` objects would require users to manually define
@@ -36,6 +38,69 @@
 
     prefix = "fabric"
 
+    @classmethod
+    def from_v1(cls, env, **kwargs):
+        """
+        Alternate constructor which uses Fabric 1's ``env`` dict for settings.
+
+        All keyword arguments besides ``env`` are passed unmolested into the
+        primary constructor, with the exception of ``overrides``, which is used
+        internally & will end up resembling the data from ``env`` with the
+        user-supplied overrides on top.
+
+        .. warning::
+            Because your own config overrides will win over data from ``env``,
+            make sure you only set values you *intend* to change from your v1
+            environment!
+
+        For details on exactly which ``env`` vars are imported and what they
+        become in the new API, please see :ref:`v1-env-var-imports`.
+
+        :param env:
+            An explicit Fabric 1 ``env`` dict (technically, any
+            ``fabric.utils._AttributeDict`` instance should work) to pull
+            configuration from.
+
+        .. versionadded:: 2.4
+        """
+        # TODO: automagic import, if we can find a way to test that
+        # Use overrides level (and preserve whatever the user may have given)
+        # TODO: we really do want arbitrary number of config levels, don't we?
+        # TODO: most of these need more care re: only filling in when they
+        # differ from the v1 default. As-is these won't overwrite runtime
+        # overrides (due to .setdefault) but they may still be filling in empty
+        # values to stomp on lower level config levels...
+        data = kwargs.pop("overrides", {})
+        # TODO: just use a dataproxy or defaultdict??
+        for subdict in ("connect_kwargs", "run", "sudo", "timeouts"):
+            data.setdefault(subdict, {})
+        # PTY use
+        data["run"].setdefault("pty", env.always_use_pty)
+        # Gateway
+        data.setdefault("gateway", env.gateway)
+        # Agent forwarding
+        data.setdefault("forward_agent", env.forward_agent)
+        # Key filename(s)
+        if env.key_filename is not None:
+            data["connect_kwargs"].setdefault("key_filename", env.key_filename)
+        # Load keys from agent?
+        data["connect_kwargs"].setdefault("allow_agent", not env.no_agent)
+        data.setdefault("ssh_config_path", env.ssh_config_path)
+        # Sudo password
+        data["sudo"].setdefault("password", env.sudo_password)
+        # Vanilla password (may be used for regular and/or sudo, depending)
+        passwd = env.password
+        data["connect_kwargs"].setdefault("password", passwd)
+        if not data["sudo"]["password"]:
+            data["sudo"]["password"] = passwd
+        data["sudo"].setdefault("prompt", env.sudo_prompt)
+        data["timeouts"].setdefault("connect", env.timeout)
+        data.setdefault("load_ssh_configs", env.use_ssh_config)
+        data["run"].setdefault("warn", env.warn_only)
+        # Put overrides back for real constructor and go
+        kwargs["overrides"] = data
+        return cls(**kwargs)
+
     def __init__(self, *args, **kwargs):
         """
         Creates a new Fabric-specific config object.
@@ -60,11 +125,12 @@
             ``~/.ssh/config``.
 
         :param bool lazy:
-            Has the same meaning as the parent class' ``lazy``, but additionall
-            controls whether SSH config file loading is deferred (requires
-            manually calling `load_ssh_config` sometime.) For example, one may
-            need to wait for user input before calling `set_runtime_ssh_path`,
-            which will inform exactly what `load_ssh_config` does.
+            Has the same meaning as the parent class' ``lazy``, but
+            additionally controls whether SSH config file loading is deferred
+            (requires manually calling `load_ssh_config` sometime.) For
+            example, one may need to wait for user input before calling
+            `set_runtime_ssh_path`, which will inform exactly what
+            `load_ssh_config` does.
         """
         # Tease out our own kwargs.
         # TODO: consider moving more stuff out of __init__ and into methods so
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/fabric/connection.py 
new/fabric-2.4.0/fabric/connection.py
--- old/fabric-2.3.1/fabric/connection.py       2018-08-09 04:09:28.000000000 
+0200
+++ new/fabric-2.4.0/fabric/connection.py       2018-09-14 00:29:20.000000000 
+0200
@@ -11,7 +11,6 @@
     from six import string_types
 import socket
 
-
 from invoke import Context
 from invoke.exceptions import ThreadException
 from paramiko.agent import AgentRequestHandler
@@ -20,6 +19,7 @@
 from paramiko.proxy import ProxyCommand
 
 from .config import Config
+from .exceptions import InvalidV1Env
 from .transfer import Transfer
 from .tunnels import TunnelManager, Tunnel
 
@@ -30,6 +30,29 @@
     return method(self, *args, **kwargs)
 
 
+def derive_shorthand(host_string):
+    user_hostport = host_string.rsplit("@", 1)
+    hostport = user_hostport.pop()
+    user = user_hostport[0] if user_hostport and user_hostport[0] else None
+
+    # IPv6: can't reliably tell where addr ends and port begins, so don't
+    # try (and don't bother adding special syntax either, user should avoid
+    # this situation by using port=).
+    if hostport.count(":") > 1:
+        host = hostport
+        port = None
+    # IPv4: can split on ':' reliably.
+    else:
+        host_port = hostport.rsplit(":", 1)
+        host = host_port.pop(0) or None
+        port = host_port[0] if host_port and host_port[0] else None
+
+    if port is not None:
+        port = int(port)
+
+    return {"user": user, "host": host, "port": port}
+
+
 class Connection(Context):
     """
     A connection to an SSH daemon, with methods for commands and file transfer.
@@ -47,12 +70,16 @@
     `.Connection` has a basic "`create <__init__>`, `connect/open <open>`, `do
     work <run>`, `disconnect/close <close>`" lifecycle:
 
-    * `Instantiation <__init__>` imprints the object with its connection
+    - `Instantiation <__init__>` imprints the object with its connection
       parameters (but does **not** actually initiate the network connection).
-    * Methods like `run`, `get` etc automatically trigger a call to
+
+        - An alternate constructor exists for users :ref:`upgrading piecemeal
+          from Fabric 1 <from-v1>`: `from_v1`
+
+    - Methods like `run`, `get` etc automatically trigger a call to
       `open` if the connection is not active; users may of course call `open`
       manually if desired.
-    * Connections do not always need to be explicitly closed; much of the
+    - Connections do not always need to be explicitly closed; much of the
       time, Paramiko's garbage collection hooks or Python's own shutdown
       sequence will take care of things. **However**, should you encounter edge
       cases (for example, sessions hanging on exit) it's helpful to explicitly
@@ -113,6 +140,64 @@
     _sftp = None
     _agent_handler = None
 
+    @classmethod
+    def from_v1(cls, env, **kwargs):
+        """
+        Alternate constructor which uses Fabric 1's ``env`` dict for settings.
+
+        All keyword arguments besides ``env`` are passed unmolested into the
+        primary constructor.
+
+        .. warning::
+            Because your own config overrides will win over data from ``env``,
+            make sure you only set values you *intend* to change from your v1
+            environment!
+
+        For details on exactly which ``env`` vars are imported and what they
+        become in the new API, please see :ref:`v1-env-var-imports`.
+
+        :param env:
+            An explicit Fabric 1 ``env`` dict (technically, any
+            ``fabric.utils._AttributeDict`` instance should work) to pull
+            configuration from.
+
+        .. versionadded:: 2.4
+        """
+        # TODO: import fabric.state.env (need good way to test it first...)
+        # TODO: how to handle somebody accidentally calling this in a process
+        # where 'fabric' is fabric 2, and there's no fabric 1? Probably just a
+        # re-raise of ImportError??
+        # Our only requirement is a non-empty host_string
+        if not env.host_string:
+            raise InvalidV1Env(
+                "Supplied v1 env has an empty `host_string` value! Please make 
sure you're calling Connection.from_v1 within a connected Fabric 1 session."  # 
noqa
+            )
+        # TODO: detect collisions with kwargs & except instead of overwriting?
+        # (More Zen of Python compliant, but also, effort, and also, makes it
+        # harder for users to intentionally overwrite!)
+        connect_kwargs = kwargs.setdefault("connect_kwargs", {})
+        kwargs.setdefault("host", env.host_string)
+        shorthand = derive_shorthand(env.host_string)
+        # TODO: don't we need to do the below skipping for user too?
+        kwargs.setdefault("user", env.user)
+        # Skip port if host string seemed to have it; otherwise we hit our own
+        # ambiguity clause in __init__. v1 would also have been doing this
+        # anyways (host string wins over other settings).
+        if not shorthand["port"]:
+            # Run port through int(); v1 inexplicably has a string default...
+            kwargs.setdefault("port", int(env.port))
+        # key_filename defaults to None in v1, but in v2, we expect it to be
+        # either unset, or set to a list. Thus, we only pull it over if it is
+        # not None.
+        if env.key_filename is not None:
+            connect_kwargs.setdefault("key_filename", env.key_filename)
+        # Obtain config values, if not given, from its own from_v1
+        # NOTE: not using setdefault as we truly only want to call
+        # Config.from_v1 when necessary.
+        if "config" not in kwargs:
+            kwargs["config"] = Config.from_v1(env)
+        return cls(**kwargs)
+
     # TODO: should "reopening" an existing Connection object that has been
     # closed, be allowed? (See e.g. how v1 detects closed/semi-closed
     # connections & nukes them before creating a new client to the same host.)
@@ -311,37 +396,12 @@
         #: The network port to connect on.
         self.port = port or int(self.ssh_config.get("port", self.config.port))
 
-        # Non-None values - string, Connection, even eg False - get set
-        # directly; None triggers seek in config/ssh_config
-        if gateway is None:
-            # SSH config wins over Invoke-style config
-            if "proxyjump" in self.ssh_config:
-                # Reverse hop1,hop2,hop3 style ProxyJump directive so we start
-                # with the final (itself non-gatewayed) hop and work up to
-                # the front (actual, supplied as our own gateway) hop
-                hops = reversed(self.ssh_config["proxyjump"].split(","))
-                prev_gw = None
-                for hop in hops:
-                    # Happily, ProxyJump uses identical format to our host
-                    # shorthand...
-                    if prev_gw is None:
-                        # TODO: this isn't persisting config! which among other
-                        # things can lead to not honoring skipping ssh config
-                        # file loads...
-                        cxn = Connection(hop)
-                    else:
-                        cxn = Connection(hop, gateway=prev_gw)
-                    prev_gw = cxn
-                gateway = prev_gw
-            elif "proxycommand" in self.ssh_config:
-                # Just a string, which we interpret as a proxy command..
-                gateway = self.ssh_config["proxycommand"]
-            else:
-                # Neither of those? Our config value please.
-                gateway = self.config.gateway
+        # Gateway/proxy/bastion/jump setting: non-None values - string,
+        # Connection, even eg False - get set directly; None triggers seek in
+        # config/ssh_config
         #: The gateway `.Connection` or ``ProxyCommand`` string to be used,
         #: if any.
-        self.gateway = gateway
+        self.gateway = gateway if gateway is not None else self.get_gateway()
         # NOTE: we use string above, vs ProxyCommand obj, to avoid spinning up
         # the ProxyCommand subprocess at init time, vs open() time.
         # TODO: make paramiko.proxy.ProxyCommand lazy instead?
@@ -414,6 +474,36 @@
 
         return connect_kwargs
 
+    def get_gateway(self):
+        # SSH config wins over Invoke-style config
+        if "proxyjump" in self.ssh_config:
+            # Reverse hop1,hop2,hop3 style ProxyJump directive so we start
+            # with the final (itself non-gatewayed) hop and work up to
+            # the front (actual, supplied as our own gateway) hop
+            hops = reversed(self.ssh_config["proxyjump"].split(","))
+            prev_gw = None
+            for hop in hops:
+                # Short-circuit if we appear to be our own proxy, which would
+                # be a RecursionError. Implies SSH config wildcards.
+                # TODO: in an ideal world we'd check user/port too in case they
+                # differ, but...seriously? They can file a PR with those extra
+                # half dozen test cases in play, E_NOTIME
+                if self.derive_shorthand(hop)["host"] == self.host:
+                    return None
+                # Happily, ProxyJump uses identical format to our host
+                # shorthand...
+                kwargs = dict(config=self.config.clone())
+                if prev_gw is not None:
+                    kwargs["gateway"] = prev_gw
+                cxn = Connection(hop, **kwargs)
+                prev_gw = cxn
+            return prev_gw
+        elif "proxycommand" in self.ssh_config:
+            # Just a string, which we interpret as a proxy command..
+            return self.ssh_config["proxycommand"]
+        # Fallback: config value (may be None).
+        return self.config.gateway
+
     def __repr__(self):
         # Host comes first as it's the most common differentiator by far
         bits = [("host", self.host)]
@@ -459,26 +549,10 @@
         return hash(self._identity())
 
     def derive_shorthand(self, host_string):
-        user_hostport = host_string.rsplit("@", 1)
-        hostport = user_hostport.pop()
-        user = user_hostport[0] if user_hostport and user_hostport[0] else None
-
-        # IPv6: can't reliably tell where addr ends and port begins, so don't
-        # try (and don't bother adding special syntax either, user should avoid
-        # this situation by using port=).
-        if hostport.count(":") > 1:
-            host = hostport
-            port = None
-        # IPv4: can split on ':' reliably.
-        else:
-            host_port = hostport.rsplit(":", 1)
-            host = host_port.pop(0) or None
-            port = host_port[0] if host_port and host_port[0] else None
-
-        if port is not None:
-            port = int(port)
-
-        return {"user": user, "host": host, "port": port}
+        # NOTE: used to be defined inline; preserving API call for both
+        # backwards compatibility and because it seems plausible we may want to
+        # modify behavior later, using eg config or other attributes.
+        return derive_shorthand(host_string)
 
     @property
     def is_connected(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/fabric/exceptions.py 
new/fabric-2.4.0/fabric/exceptions.py
--- old/fabric-2.3.1/fabric/exceptions.py       2018-07-25 20:34:43.000000000 
+0200
+++ new/fabric-2.4.0/fabric/exceptions.py       2018-09-14 00:29:20.000000000 
+0200
@@ -16,3 +16,11 @@
         #: been no errors. See its docstring (and that of `.Group`) for
         #: details.
         self.result = result
+
+
+class InvalidV1Env(Exception):
+    """
+    Raised when attempting to import a Fabric 1 ``env`` which is missing data.
+    """
+
+    pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/fabric/executor.py 
new/fabric-2.4.0/fabric/executor.py
--- old/fabric-2.3.1/fabric/executor.py 2018-08-08 23:56:55.000000000 +0200
+++ new/fabric-2.4.0/fabric/executor.py 2018-09-14 00:28:41.000000000 +0200
@@ -1,9 +1,9 @@
 import invoke
 from invoke import Call, Task
-from invoke.util import debug
 
 from .tasks import ConnectionCall
 from .exceptions import NothingToDo
+from .util import debug
 
 
 class Executor(invoke.Executor):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/fabric/group.py 
new/fabric-2.4.0/fabric/group.py
--- old/fabric-2.3.1/fabric/group.py    2018-08-08 23:56:55.000000000 +0200
+++ new/fabric-2.4.0/fabric/group.py    2018-09-14 00:29:20.000000000 +0200
@@ -55,7 +55,12 @@
             <Connection host='notahost'>: gaierror(...),
         }
 
+    As with `.Connection`, `.Group` objects may be used as context managers,
+    which will automatically `.close` the object on block exit.
+
     .. versionadded:: 2.0
+    .. versionchanged:: 2.4
+        Added context manager behavior.
     """
 
     def __init__(self, *hosts, **kwargs):
@@ -130,8 +135,6 @@
     # would be distinct from Group. (May want to switch Group to use that,
     # though, whatever it ends up being?)
 
-    # TODO: mirror Connection's close()?
-
     def get(self, *args, **kwargs):
         """
         Executes `.Connection.get` on all member `Connections <.Connection>`.
@@ -144,6 +147,21 @@
         # TODO: actually implement on subclasses
         raise NotImplementedError
 
+    def close(self):
+        """
+        Executes `.Connection.close` on all member `Connections <.Connection>`.
+
+        .. versionadded:: 2.4
+        """
+        for cxn in self:
+            cxn.close()
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *exc):
+        self.close()
+
 
 class SerialGroup(Group):
     """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/fabric/main.py 
new/fabric-2.4.0/fabric/main.py
--- old/fabric-2.3.1/fabric/main.py     2018-08-08 23:56:55.000000000 +0200
+++ new/fabric-2.4.0/fabric/main.py     2018-09-14 00:28:41.000000000 +0200
@@ -54,7 +54,16 @@
 
     @property
     def _remainder_only(self):
-        return not self.core.unparsed and self.core.remainder
+        # No 'unparsed' (i.e. tokens intended for task contexts), and remainder
+        # (text after a double-dash) implies a contextless/taskless remainder
+        # execution of the style 'fab -H host -- command'.
+        # NOTE: must ALSO check to ensure the double dash isn't being used for
+        # tab completion machinery...
+        return (
+            not self.core.unparsed
+            and self.core.remainder
+            and not self.args.complete.value
+        )
 
     def load_collection(self):
         # Stick in a dummy Collection if it looks like we were invoked w/o any
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/fabric/transfer.py 
new/fabric-2.4.0/fabric/transfer.py
--- old/fabric-2.3.1/fabric/transfer.py 2018-07-25 20:34:43.000000000 +0200
+++ new/fabric-2.4.0/fabric/transfer.py 2018-09-06 21:28:02.000000000 +0200
@@ -6,7 +6,7 @@
 import posixpath
 import stat
 
-from invoke.util import debug  # TODO: actual logging! LOL
+from .util import debug  # TODO: actual logging! LOL
 
 # TODO: figure out best way to direct folks seeking rsync, to patchwork's rsync
 # call (which needs updating to use invoke.run() & fab 2 connection methods,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/fabric.egg-info/PKG-INFO 
new/fabric-2.4.0/fabric.egg-info/PKG-INFO
--- old/fabric-2.3.1/fabric.egg-info/PKG-INFO   2018-08-09 04:12:29.000000000 
+0200
+++ new/fabric-2.4.0/fabric.egg-info/PKG-INFO   2018-09-14 00:29:24.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: fabric
-Version: 2.3.1
+Version: 2.4.0
 Summary: High level SSH command execution
 Home-page: http://fabfile.org
 Author: Jeff Forcier
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/fabric.egg-info/SOURCES.txt 
new/fabric-2.4.0/fabric.egg-info/SOURCES.txt
--- old/fabric-2.3.1/fabric.egg-info/SOURCES.txt        2018-08-09 
04:12:29.000000000 +0200
+++ new/fabric-2.4.0/fabric.egg-info/SOURCES.txt        2018-09-14 
00:29:24.000000000 +0200
@@ -91,6 +91,8 @@
 tests/_support/ssh_config/overridden_hostname.conf
 tests/_support/ssh_config/proxyjump.conf
 tests/_support/ssh_config/proxyjump_multi.conf
+tests/_support/ssh_config/proxyjump_multi_recursive.conf
+tests/_support/ssh_config/proxyjump_recursive.conf
 tests/_support/ssh_config/runtime.conf
 tests/_support/ssh_config/runtime_identity.conf
 tests/_support/ssh_config/system.conf
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/sites/docs/concepts/configuration.rst 
new/fabric-2.4.0/sites/docs/concepts/configuration.rst
--- old/fabric-2.3.1/sites/docs/concepts/configuration.rst      2018-08-09 
03:20:43.000000000 +0200
+++ new/fabric-2.4.0/sites/docs/concepts/configuration.rst      2018-09-14 
00:29:00.000000000 +0200
@@ -87,7 +87,7 @@
   OpenSSH.)
 - ``gateway``: Used as the default value of the ``gateway`` kwarg for
   `.Connection`. May be any value accepted by that argument. Default: ``None``.
-- ``load_openssh_configs``: Whether to automatically seek out :ref:`SSH config
+- ``load_ssh_configs``: Whether to automatically seek out :ref:`SSH config
   files <ssh-config>`. When ``False``, no automatic loading occurs. Default:
   ``True``.
 - ``port``: TCP port number used by `.Connection` objects when not otherwise
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/sites/docs/index.rst 
new/fabric-2.4.0/sites/docs/index.rst
--- old/fabric-2.3.1/sites/docs/index.rst       2018-07-02 23:24:48.000000000 
+0200
+++ new/fabric-2.4.0/sites/docs/index.rst       2018-09-06 21:28:02.000000000 
+0200
@@ -20,7 +20,7 @@
 Upgrading from 1.x
 ------------------
 
-Looking to upgrade from Fabric 1.x? See our :doc:`detailed upgrade guide
+Looking to upgrade from Fabric 1.x? See our :ref:`detailed upgrade guide
 <upgrading>` on the nonversioned main project site.
 
 .. _concepts-docs:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/sites/www/changelog.rst 
new/fabric-2.4.0/sites/www/changelog.rst
--- old/fabric-2.3.1/sites/www/changelog.rst    2018-08-09 04:12:24.000000000 
+0200
+++ new/fabric-2.4.0/sites/www/changelog.rst    2018-09-14 00:29:20.000000000 
+0200
@@ -5,10 +5,42 @@
 .. note::
     Looking for the Fabric 1.x changelog? See :doc:`/changelog-v1`.
 
+- :release:`2.4.0 <2018-09-13>`
+- :release:`2.3.2 <2018-09-13>`
+- :release:`2.2.3 <2018-09-13>`
+- :release:`2.1.6 <2018-09-13>`
+- :release:`2.0.5 <2018-09-13>`
+- :feature:`1849` Add `Connection.from_v1
+  <fabric.connection.Connection.from_v1>` for easy creation of a modern
+  ``Connection`` object from the currently configured Fabric 1.x
+  environment. Should make upgrading piecemeal much easier for many use
+  cases.
+- :feature:`1780` Add context manager behavior to `~fabric.group.Group`, to
+  match the same feature in `~fabric.connection.Connection`. Feature request by
+  István Sárándi.
+- :feature:`1709` Add `Group.close <fabric.group.Group.close>` to allow closing
+  an entire group's worth of connections at once. Patch via Johannes Löthberg.
+- :bug:`-` Fix a bug preventing tab completion (using the Invoke-level
+  ``--complete`` flag) from completing task names correctly (behavior was to
+  act as if there were never any tasks present, even if there was a valid
+  fabfile nearby).
+- :bug:`1850` Skip over ``ProxyJump`` configuration directives in SSH config
+  data when they would cause self-referential ``RecursionError`` (e.g. due to
+  wildcard-using ``Host`` stanzas which include the jump server itself).
+  Reported by Chris Adams.
+- :bug:`-` Some debug logging was reusing Invoke's logger object, generating
+  log messages "named" after ``invoke`` instead of ``fabric``. This has been
+  fixed by using Fabric's own logger everywhere instead.
+- :bug:`1852` Grant internal `~fabric.connection.Connection` objects created
+  during ``ProxyJump`` based gateways/proxies a copy of the outer
+  ``Connection``'s configuration object. This was not previously done, which
+  among other things meant one could not fully disable SSH config file loading
+  (as the internal ``Connection`` objects would revert to the default
+  behavior). Thanks to Chris Adams for the report.
 - :release:`2.3.1 <2018-08-08>`
-- :bug:`-` Update the new functionality added for :issue:`1826` so it uses
-  ``export``; without this, nontrivial shell invocations like ``command1 &&
-  command2`` end up only applying the env vars to the first command.
+- :bug:`- (2.3+)` Update the new functionality added for :issue:`1826` so it
+  uses ``export``; without this, nontrivial shell invocations like ``command1
+  && command2`` end up only applying the env vars to the first command.
 - :release:`2.3.0 <2018-08-08>`
 - :feature:`1826` Add a new Boolean configuration and
   `~fabric.connection.Connection` parameter, ``inline_ssh_env``, which (when
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/sites/www/faq.rst 
new/fabric-2.4.0/sites/www/faq.rst
--- old/fabric-2.3.1/sites/www/faq.rst  2018-08-09 03:20:43.000000000 +0200
+++ new/fabric-2.4.0/sites/www/faq.rst  2018-09-14 00:29:20.000000000 +0200
@@ -19,8 +19,8 @@
 
 .. _remote-env-vars-dont-work:
 
-Environment variables are not being set correctly on the remote end!
-====================================================================
+Explicitly set env variables are not being set correctly on the remote end!
+===========================================================================
 
 If your attempts to set environment variables for things like `Connection.run
 <fabric.connection.Connection.run>` appear to silently fail, you're almost
@@ -34,6 +34,51 @@
 instead.
 
 
+The remote shell environment doesn't match interactive shells!
+==============================================================
+
+You may find environment variables (or the behavior they trigger) differ
+interactively vs scripted via Fabric. For example, a program that's on your
+``$PATH`` when you manually ``ssh`` in might not be visible when using
+`Connection.run <fabric.connection.Connection.run>`; or special per-program env
+vars such as those for Python, pip, Java etc are not taking effect; etc.
+
+The root cause of this is typically because the SSH server runs non-interactive
+commands via a very limited shell call: ``/path/to/shell -c "command"`` (for
+example, `OpenSSH
+<https://github.com/fabric/fabric/issues/1519#issuecomment-411247228>`_). Most
+shells, when run this way, are not considered to be either **interactive** or
+**login** shells; and this then impacts which startup files get loaded.
+
+Users typically only modify shell files related to interactive operation (such
+as ``~/.bash_profile`` or ``/etc/zshrc``); such changes do not take effect when
+the SSH server is running one-off commands.
+
+To work around this, consult your shell's documentation to see if it offers any
+non-login, non-interactive config files; for example, ``zsh`` lets you
+configure ``/etc/zshrc`` or ``~/.zshenv`` for this purpose.
+
+.. note::
+    ``bash`` does not appear to offer standard non-login/non-interactive
+    startup files, even in version 4. However, it may attempt to determine if
+    it's being run by a remote-execution daemon and will apparently source
+    ``~/.bashrc`` if so; check to see if this is the case on your target
+    systems.
+
+.. note::
+    Another workaround for ``bash`` users is to reply on its ``$BASH_ENV``
+    functionality, which names a file path as the startup file to load:
+
+    - configure your SSH server to ``AcceptEnv BASH_ENV``, so that you can
+      actually set that env var for the remote session at the top level (most
+      SSH servers disallow this method by default).
+    - decide which file this should be, though if you're already modifying
+      files like ``~/.bash_profile`` or ``~/.bashrc``, you may want to just
+      point at that exact path.
+    - set the Fabric configuration value ``run.env`` to aim at the above path,
+      e.g. ``{"BASH_ENV": "~/.bash_profile"}``.
+
+
 .. _one-shell-per-command:
 
 My (``cd``/``workon``/``export``/etc) calls don't seem to work!
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/sites/www/upgrading.rst 
new/fabric-2.4.0/sites/www/upgrading.rst
--- old/fabric-2.3.1/sites/www/upgrading.rst    2018-08-08 23:56:55.000000000 
+0200
+++ new/fabric-2.4.0/sites/www/upgrading.rst    2018-09-14 00:29:20.000000000 
+0200
@@ -119,6 +119,122 @@
 For details on how to obtain the ``fabric2`` version of the package, see
 :ref:`installing-as-fabric2`.
 
+.. _from-v1:
+
+Creating ``Connection`` and/or ``Config`` objects from v1 settings
+------------------------------------------------------------------
+
+A common tactic when upgrading piecemeal is to generate modern Fabric objects
+whose contents match the current Fabric 1 environment. Whereas Fabric 1 stores
+*all* configuration (including the "current host") in a single place -- the
+``env`` object -- modern Fabric breaks things up into multiple (albeit
+composed) objects: `~fabric.connection.Connection` for per-connection
+parameters, and `~fabric.config.Config` for general settings and defaults.
+
+In most cases, you'll only need to generate a `~fabric.connection.Connection`
+object using the alternate class constructor `Connection.from_v1
+<fabric.connection.Connection.from_v1>`, which should be fed your appropriate
+local ``fabric.api.env`` object; see its API docs for details.
+
+A contrived example::
+
+    from fabric.api import env, run
+    from fabric2 import Connection
+
+    env.host_string = "admin@myserver"
+    run("whoami") # v1
+    cxn = Connection.from_v1(env)
+    cxn.run("whoami") # v2+
+
+By default, this constructor calls another API member -- `Config.from_v1
+<fabric.config.Config.from_v1>` -- internally on your behalf. Users who need
+tighter control over modern-style config options may opt to call that
+classmethod explicitly and hand their modified result into `Connection.from_v1
+<fabric.connection.Connection.from_v1>`, which will cause the latter to skip
+any implicit config creation.
+
+.. _v1-env-var-imports:
+
+Mapping of v1 ``env`` vars to modern API members
+------------------------------------------------
+
+The ``env`` vars and how they map to `~fabric.connection.Connection` arguments
+or `~fabric.config.Config` values (when fed into the ``.from_v1`` constructors
+described above) are listed below.
+
+.. list-table::
+    :header-rows: 1
+
+    * - v1 ``env`` var
+      - v2+ usage (prefixed with the class it ends up in)
+
+    * - ``always_use_pty``
+      - Config: ``run.pty``.
+    * - ``forward_agent``
+      - Config: ``connect_kwargs.forward_agent``.
+    * - ``gateway``
+      - Config: ``gateway``.
+    * - ``host_string``
+      - Connection: ``host`` kwarg (which can handle host-string like values,
+        including user/port).
+    * - ``key``
+      - **Not supported**: Fabric 1 performed extra processing on this
+        (trying a bunch of key classes to instantiate) before
+        handing it into Paramiko; modern Fabric prefers to just let you handle
+        Paramiko-level parameters directly.
+
+        If you're filling your Fabric 1 ``key`` data from a file, we recommend
+        switching to ``key_filename`` instead, which is supported.
+
+        If you're loading key data from some other source as a string, you
+        should know what type of key your data is and manually instantiate it
+        instead, then supply it to the ``connect_kwargs`` parameter. For
+        example::
+
+            from io import StringIO  # or 'from StringIO' on Python 2
+            from fabric.state import env
+            from fabric2 import Connection
+            from paramiko import RSAKey
+            from somewhere import load_my_key_string
+
+            pkey = RSAKey.from_private_key(StringIO(load_my_key_string()))
+            cxn = Connection.from_v1(env, connect_kwargs={"pkey": pkey})
+
+    * - ``key_filename``
+      - Config: ``connect_kwargs.key_filename``.
+    * - ``no_agent``
+      - Config: ``connect_kwargs.allow_agent`` (inverted).
+    * - ``password``
+      - Config: ``connect_kwargs.password``, as well as ``sudo.password``
+        **if and only if** the env's ``sudo_password`` (see below) is unset.
+        (This mimics how v1 uses this particular setting - in earlier versions
+        there was no ``sudo_password`` at all.)
+    * - ``port``
+      - Connection: ``port`` kwarg. Is casted to an integer due to Fabric 1's
+        default being a string value (which is not valid in v2).
+
+        .. note::
+            Since v1's ``port`` is used both for a default *and* to store the
+            current connection state, v2 uses it to fill in the Connection
+            only, and not the Config, on assumption that it will typically be
+            the current connection state.
+
+    * - ``ssh_config_path``
+      - Config: ``ssh_config_path``.
+    * - ``sudo_password``
+      - Config: ``sudo.password``.
+    * - ``sudo_prompt``
+      - Config: ``sudo.prompt``.
+    * - ``timeout``
+      - Config: ``timeouts.connection`` (because v1's ambiguously named
+        ``timeout`` setting was, in fact, for connection timeouts).
+    * - ``use_ssh_config``
+      - Config: ``load_ssh_configs``.
+    * - ``user``
+      - Connection: ``user`` kwarg.
+    * - ``warn_only``
+      - Config: ``run.warn``
+
 
 .. _upgrade-specifics:
 
@@ -209,6 +325,10 @@
         `fabric.connection.Connection` objects and call their methods. These
         objects encapsulate all connection state (user, host, gateway, etc) and
         have their own SSH client instances.
+
+        .. seealso::
+            `Connection.from_v1 <fabric.connection.Connection.from_v1>`
+
     * - Emphasis on serialized "host strings" as method of setting user, host,
         port, etc
       - Ported/Removed
@@ -995,6 +1115,11 @@
       - This behavior is ultimately unnecessary (one can simply leave the
         tilde off for the same result) and had a few pernicious bugs of its
         own, so it's gone.
+    * - Naming downloaded files after some aspect of the remote destination, to
+        avoid overwriting during multi-server actions
+      - `Pending <https://github.com/fabric/fabric/issues/1868>`__
+      - This falls under the `~fabric.group.Group` family, which still needs
+        some work in this regard.
 
 
 .. _upgrading-configuration:
@@ -1179,7 +1304,7 @@
         `fabric.util.get_local_user`.
     * - ``env.output_prefix`` determining whether or not line-by-line
         host-string prefixes are displayed
-      - `Pending <https://github.com/pyinvoke/invoke/issues/15>`_
+      - `Pending <https://github.com/pyinvoke/invoke/issues/15>`__
       - Differentiating parallel stdout/err is still a work in progress; we may
         end up reusing line-by-line logging and prefixing (ideally via actual
         logging) or we may try for something cleaner such as streaming to
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/tasks.py new/fabric-2.4.0/tasks.py
--- old/fabric-2.3.1/tasks.py   2018-07-09 20:09:01.000000000 +0200
+++ new/fabric-2.4.0/tasks.py   2018-09-14 00:29:20.000000000 +0200
@@ -1,5 +1,6 @@
 from functools import partial
-from os import environ
+from os import environ, getcwd
+import sys
 
 from invocations import travis
 from invocations.checks import blacken
@@ -57,6 +58,45 @@
         release.upload(c, directory, index, sign, dry_run)
 
 
+@task
+def sanity_test_from_v1(c):
+    """
+    Run some very quick in-process sanity tests on a dual fabric1-v-2 env.
+
+    Assumes Fabric 2+ is already installed as 'fabric2'.
+    """
+    # This cannot, by definition, work under Python 3 as Fabric 1 is not Python
+    # 3 compatible.
+    PYTHON = environ.get("TRAVIS_PYTHON_VERSION", "")
+    if PYTHON.startswith("3") or PYTHON == "pypy3":
+        return
+    c.run("pip install 'fabric<2'")
+    # Make darn sure the two copies of fabric are coming from install root, not
+    # local directory - which would result in 'fabric' always being v2!
+    for serious in (getcwd(), ""):
+        if serious in sys.path:  # because why would .remove be idempotent?!
+            sys.path.remove(serious)
+
+    from fabric.api import env
+    from fabric2 import Connection
+
+    env.gateway = "some-gateway"
+    env.no_agent = True
+    env.password = "sikrit"
+    env.user = "admin"
+    env.host_string = "localghost"
+    env.port = "2222"
+    cxn = Connection.from_v1(env)
+    config = cxn.config
+    assert config.run.pty is True
+    assert config.gateway == "some-gateway"
+    assert config.connect_kwargs.password == "sikrit"
+    assert config.sudo.password == "sikrit"
+    assert cxn.host == "localghost"
+    assert cxn.user == "admin"
+    assert cxn.port == 2222
+
+
 # Better than nothing, since we haven't solved "pretend I have some other
 # task's signature" yet...
 publish.__doc__ = release.publish.__doc__
@@ -75,6 +115,7 @@
     travis,
     watch_docs,
     www,
+    sanity_test_from_v1,
 )
 ns.configure(
     {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/fabric-2.3.1/tests/_support/ssh_config/proxyjump_multi_recursive.conf 
new/fabric-2.4.0/tests/_support/ssh_config/proxyjump_multi_recursive.conf
--- old/fabric-2.3.1/tests/_support/ssh_config/proxyjump_multi_recursive.conf   
1970-01-01 01:00:00.000000000 +0100
+++ new/fabric-2.4.0/tests/_support/ssh_config/proxyjump_multi_recursive.conf   
2018-09-06 21:28:02.000000000 +0200
@@ -0,0 +1,2 @@
+Host *.tld 
+    ProxyJump bastion1.tld,bastion2.tld
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/fabric-2.3.1/tests/_support/ssh_config/proxyjump_recursive.conf 
new/fabric-2.4.0/tests/_support/ssh_config/proxyjump_recursive.conf
--- old/fabric-2.3.1/tests/_support/ssh_config/proxyjump_recursive.conf 
1970-01-01 01:00:00.000000000 +0100
+++ new/fabric-2.4.0/tests/_support/ssh_config/proxyjump_recursive.conf 
2018-09-06 21:28:02.000000000 +0200
@@ -0,0 +1,2 @@
+Host *.tld
+    ProxyJump bastion.tld
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/tests/_util.py 
new/fabric-2.4.0/tests/_util.py
--- old/fabric-2.3.1/tests/_util.py     2018-08-08 23:56:55.000000000 +0200
+++ new/fabric-2.4.0/tests/_util.py     2018-09-14 00:29:20.000000000 +0200
@@ -3,6 +3,7 @@
 import re
 import sys
 
+from invoke.vendor.lexicon import Lexicon
 from pytest_relaxed import trap
 
 from fabric import Connection as Connection_, Config as Config_
@@ -69,3 +70,27 @@
         # Make sure we're using our tweaked Config if none was given.
         kwargs.setdefault("config", Config())
         super(Connection, self).__init__(*args, **kwargs)
+
+
+def faux_v1_env():
+    # Close enough to v1 _AttributeDict...
+    # Contains a copy of enough of v1's defaults to prevent us having to do a
+    # lot of extra .get()s...meh
+    return Lexicon(
+        always_use_pty=True,
+        forward_agent=False,
+        gateway=None,
+        host_string="localghost",
+        key_filename=None,
+        no_agent=False,
+        password=None,
+        port=22,
+        ssh_config_path=None,
+        # Used in a handful of sanity tests, so it gets a 'real' value. eh.
+        sudo_password="nope",
+        sudo_prompt=None,
+        timeout=None,
+        use_ssh_config=False,
+        user="localuser",
+        warn_only=False,
+    )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/tests/config.py 
new/fabric-2.4.0/tests/config.py
--- old/fabric-2.3.1/tests/config.py    2018-08-09 03:20:43.000000000 +0200
+++ new/fabric-2.4.0/tests/config.py    2018-09-14 00:29:20.000000000 +0200
@@ -2,13 +2,14 @@
 from os.path import join, expanduser
 
 from paramiko.config import SSHConfig
+from invoke.vendor.lexicon import Lexicon
 
 from fabric import Config
 from fabric.util import get_local_user
 
 from mock import patch, call
 
-from _util import support
+from _util import support, faux_v1_env
 
 
 class Config_:
@@ -58,6 +59,124 @@
         # just tests the underlying data/attribute driving the behavior.
         assert Config().prefix == "fabric"
 
+    class from_v1:
+        def setup(self):
+            self.env = faux_v1_env()
+
+        def _conf(self, **kwargs):
+            self.env.update(kwargs)
+            return Config.from_v1(self.env)
+
+        def must_be_given_explicit_env_arg(self):
+            config = Config.from_v1(
+                env=Lexicon(self.env, sudo_password="sikrit")
+            )
+            assert config.sudo.password == "sikrit"
+
+        class additional_kwargs:
+            def forwards_arbitrary_kwargs_to_init(self):
+                config = Config.from_v1(
+                    self.env,
+                    # Vanilla Invoke
+                    overrides={"some": "value"},
+                    # Fabric
+                    system_ssh_path="/what/ever",
+                )
+                assert config.some == "value"
+                assert config._system_ssh_path == "/what/ever"
+
+            def subservient_to_runtime_overrides(self):
+                env = self.env
+                env.sudo_password = "from-v1"
+                config = Config.from_v1(
+                    env, overrides={"sudo": {"password": "runtime"}}
+                )
+                assert config.sudo.password == "runtime"
+
+            def connect_kwargs_also_merged_with_imported_values(self):
+                self.env["key_filename"] = "whatever"
+                conf = Config.from_v1(
+                    self.env, overrides={"connect_kwargs": {"meh": "effort"}}
+                )
+                assert conf.connect_kwargs["key_filename"] == "whatever"
+                assert conf.connect_kwargs["meh"] == "effort"
+
+        class var_mappings:
+            def always_use_pty(self):
+                # Testing both due to v1-didn't-use-None-default issues
+                config = self._conf(always_use_pty=True)
+                assert config.run.pty is True
+                config = self._conf(always_use_pty=False)
+                assert config.run.pty is False
+
+            def forward_agent(self):
+                config = self._conf(forward_agent=True)
+                assert config.forward_agent is True
+
+            def gateway(self):
+                config = self._conf(gateway="bastion.host")
+                assert config.gateway == "bastion.host"
+
+            class key_filename:
+                def base(self):
+                    config = self._conf(key_filename="/some/path")
+                    assert (
+                        config.connect_kwargs["key_filename"] == "/some/path"
+                    )
+
+                def is_not_set_if_None(self):
+                    config = self._conf(key_filename=None)
+                    assert "key_filename" not in config.connect_kwargs
+
+            def no_agent(self):
+                config = self._conf()
+                assert config.connect_kwargs.allow_agent is True
+                config = self._conf(no_agent=True)
+                assert config.connect_kwargs.allow_agent is False
+
+            class password:
+                def set_just_to_connect_kwargs_if_sudo_password_set(self):
+                    # NOTE: default faux env has sudo_password set already...
+                    config = self._conf(password="screaming-firehawks")
+                    passwd = config.connect_kwargs.password
+                    assert passwd == "screaming-firehawks"
+
+                def set_to_both_password_fields_if_necessary(self):
+                    config = self._conf(password="sikrit", sudo_password=None)
+                    assert config.connect_kwargs.password == "sikrit"
+                    assert config.sudo.password == "sikrit"
+
+            def ssh_config_path(self):
+                self.env.ssh_config_path = "/where/ever"
+                config = Config.from_v1(self.env, lazy=True)
+                assert config.ssh_config_path == "/where/ever"
+
+            def sudo_password(self):
+                config = self._conf(sudo_password="sikrit")
+                assert config.sudo.password == "sikrit"
+
+            def sudo_prompt(self):
+                config = self._conf(sudo_prompt="password???")
+                assert config.sudo.prompt == "password???"
+
+            def timeout(self):
+                config = self._conf(timeout=15)
+                assert config.timeouts.connect == 15
+
+            def use_ssh_config(self):
+                # Testing both due to v1-didn't-use-None-default issues
+                config = self._conf(use_ssh_config=True)
+                assert config.load_ssh_configs is True
+                config = self._conf(use_ssh_config=False)
+                assert config.load_ssh_configs is False
+
+            def warn_only(self):
+                # Testing both due to v1-didn't-use-None-default issues
+                config = self._conf(warn_only=True)
+                assert config.run.warn is True
+                config = self._conf(warn_only=False)
+                assert config.run.warn is False
+
 
 class ssh_config_loading:
     "ssh_config loading"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/tests/connection.py 
new/fabric-2.4.0/tests/connection.py
--- old/fabric-2.3.1/tests/connection.py        2018-08-09 03:20:43.000000000 
+0200
+++ new/fabric-2.4.0/tests/connection.py        2018-09-14 00:29:20.000000000 
+0200
@@ -15,14 +15,16 @@
 import pytest  # for mark
 from pytest import skip, param
 from pytest_relaxed import raises
+from invoke.vendor.lexicon import Lexicon
 
 from invoke.config import Config as InvokeConfig
 from invoke.exceptions import ThreadException
 
 from fabric import Config as Config_
+from fabric.exceptions import InvalidV1Env
 from fabric.util import get_local_user
 
-from _util import support, Connection, Config
+from _util import support, Connection, Config, faux_v1_env
 
 
 # Remote is woven in as a config default, so must be patched there
@@ -419,6 +421,39 @@
                     assert middle == Connection("jumpuser2@jumphost2:872")
                     assert outermost == Connection("jumpuser@jumphost:373")
 
+                def wildcards_do_not_trigger_recursion(self):
+                    # When #1850 is present, this will RecursionError.
+                    conf = self._runtime_config(basename="proxyjump_recursive")
+                    cxn = Connection("runtime.tld", config=conf)
+                    assert cxn.gateway == Connection("bastion.tld")
+                    assert cxn.gateway.gateway is None
+
+                def multihop_plus_wildcards_still_no_recursion(self):
+                    conf = self._runtime_config(
+                        basename="proxyjump_multi_recursive"
+                    )
+                    cxn = Connection("runtime.tld", config=conf)
+                    outer = cxn.gateway
+                    inner = cxn.gateway.gateway
+                    assert outer == Connection("bastion1.tld")
+                    assert inner == Connection("bastion2.tld")
+                    assert inner.gateway is None
+
+                def gateway_Connections_get_parent_connection_configs(self):
+                    conf = self._runtime_config(
+                        basename="proxyjump",
+                        overrides={"some_random_option": "a-value"},
+                    )
+                    cxn = Connection("runtime", config=conf)
+                    # Sanity
+                    assert cxn.config is conf
+                    assert cxn.gateway == self._expected_gw
+                    # Real check
+                    assert cxn.gateway.config.some_random_option == "a-value"
+                    # Prove copy not reference
+                    # TODO: would we ever WANT a reference? can't imagine...
+                    assert cxn.gateway.config is not conf
+
             class connect_timeout:
                 def wins_over_default(self):
                     assert self._runtime_cxn().connect_timeout == 15
@@ -478,6 +513,79 @@
                 cxn = Connection("host", inline_ssh_env=True)
                 assert cxn.inline_ssh_env is True
 
+    class from_v1:
+        def setup(self):
+            self.env = faux_v1_env()
+
+        def _cxn(self, **kwargs):
+            self.env.update(kwargs)
+            return Connection.from_v1(self.env)
+
+        def must_be_given_explicit_env_arg(self):
+            cxn = Connection.from_v1(self.env)
+            assert cxn.host == "localghost"
+
+        class obtaining_config:
+            @patch("fabric.connection.Config.from_v1")
+            def defaults_to_calling_Config_from_v1(self, Config_from_v1):
+                Connection.from_v1(self.env)
+                Config_from_v1.assert_called_once_with(self.env)
+
+            @patch("fabric.connection.Config.from_v1")
+            def may_be_given_config_explicitly(self, Config_from_v1):
+                # Arguably a dupe of regular Connection constructor behavior,
+                # but whatever.
+                Connection.from_v1(env=self.env, config=Config())
+                assert not Config_from_v1.called
+
+        class additional_kwargs:
+            # I.e. as opposed to what happens to the 'env' kwarg...
+            def forwards_arbitrary_kwargs_to_init(self):
+                cxn = Connection.from_v1(
+                    self.env,
+                    connect_kwargs={"foo": "bar"},
+                    inline_ssh_env=True,
+                    connect_timeout=15,
+                )
+                assert cxn.connect_kwargs["foo"] == "bar"
+                assert cxn.inline_ssh_env is True
+                assert cxn.connect_timeout == 15
+
+            def conflicting_kwargs_win_over_v1_env_values(self):
+                env = Lexicon(self.env)
+                cxn = Connection.from_v1(
+                    env, host="not-localghost", port=2222, user="remoteuser"
+                )
+                assert cxn.host == "not-localghost"
+                assert cxn.user == "remoteuser"
+                assert cxn.port == 2222
+
+        class var_mappings:
+            def host_string(self):
+                cxn = self._cxn()  # default is 'localghost'
+                assert cxn.host == "localghost"
+
+            @raises(InvalidV1Env)
+            def None_host_string_errors_usefully(self):
+                self._cxn(host_string=None)
+
+            def user(self):
+                cxn = self._cxn(user="space")
+                assert cxn.user == "space"
+
+            class port:
+                def basic(self):
+                    cxn = self._cxn(port=2222)
+                    assert cxn.port == 2222
+
+                def casted_to_int(self):
+                    cxn = self._cxn(port="2222")
+                    assert cxn.port == 2222
+
+                def not_supplied_if_given_in_host_string(self):
+                    cxn = self._cxn(host_string="localghost:3737", port=2222)
+                    assert cxn.port == 3737
+
     class string_representation:
         "string representations"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/tests/group.py 
new/fabric-2.4.0/tests/group.py
--- old/fabric-2.3.1/tests/group.py     2018-08-08 23:56:55.000000000 +0200
+++ new/fabric-2.4.0/tests/group.py     2018-09-14 00:29:20.000000000 +0200
@@ -46,6 +46,22 @@
         def not_implemented_in_base_class(self):
             Group().run()
 
+    class close_and_contextmanager_behavior:
+        def close_closes_all_member_connections(self):
+            cxns = [Mock(name=x) for x in ("foo", "bar", "biz")]
+            g = Group.from_connections(cxns)
+            g.close()
+            for c in cxns:
+                c.close.assert_called_once_with()
+
+        def contextmanager_behavior_works_like_Connection(self):
+            cxns = [Mock(name=x) for x in ("foo", "bar", "biz")]
+            g = Group.from_connections(cxns)
+            with g as my_g:
+                assert my_g is g
+            for c in cxns:
+                c.close.assert_called_once_with()
+
 
 def _make_serial_tester(cxns, index, args, kwargs):
     args = args[:]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.3.1/tests/main.py 
new/fabric-2.4.0/tests/main.py
--- old/fabric-2.3.1/tests/main.py      2018-08-08 23:56:55.000000000 +0200
+++ new/fabric-2.4.0/tests/main.py      2018-09-14 00:28:41.000000000 +0200
@@ -313,6 +313,22 @@
             with cd(os.path.join(support, "yml_conf")):
                 program.run("fab -i cli.key expect-cli-key-filename")
 
+    class completion:
+        # NOTE: most completion tests are in Invoke too; this is just an
+        # irritating corner case driven by Fabric's 'remainder' functionality.
+        @trap
+        def complete_flag_does_not_trigger_remainder_only_behavior(self):
+            # When bug present, 'fab --complete -- fab' fails to load any
+            # collections because it thinks it's in remainder-only,
+            # work-without-a-collection mode.
+            with cd(support):
+                program.run("fab --complete -- fab", exit=False)
+            # Cherry-picked sanity checks looking for tasks from fixture
+            # fabfile
+            output = sys.stdout.getvalue()
+            for name in ("build", "deploy", "expect-from-env"):
+                assert name in output
+
 
 class main:
     "__main__"


Reply via email to