Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-Fabric for openSUSE:Factory 
checked in at 2022-05-17 17:24:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-Fabric (Old)
 and      /work/SRC/openSUSE:Factory/.python-Fabric.new.1538 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-Fabric"

Tue May 17 17:24:32 2022 rev:38 rq:977640 version:2.7.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-Fabric/python-Fabric.changes      
2022-03-13 22:01:56.404471598 +0100
+++ /work/SRC/openSUSE:Factory/.python-Fabric.new.1538/python-Fabric.changes    
2022-05-17 17:24:50.739193396 +0200
@@ -1,0 +2,15 @@
+Tue May 17 05:29:37 UTC 2022 - Steve Kowalik <steven.kowa...@suse.com>
+
+- Update to 2.7.0:
+  * Add ~fabric.connection.Connection.shell, a belated port of the v1
+    open_shell() feature.
+  * Forward local terminal resizes to the remote end, when applicable.
+    (For the technical: this means we now turn SIGWINCH into SSH
+    window-change messages.)
+  * Update ~fabric.connection.Connection temporarily so that it doesn't
+    incidentally apply replace_env=True to local shell commands, only
+    remote ones.
+- Add patch remove-mock.patch:
+  * Use unittest.mock, instead of mock
+
+-------------------------------------------------------------------

Old:
----
  fabric-2.6.0.tar.gz

New:
----
  fabric-2.7.0.tar.gz
  remove-mock.patch

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

Other differences:
------------------
++++++ python-Fabric.spec ++++++
--- /var/tmp/diff_new_pack.DHvTkk/_old  2022-05-17 17:24:52.219194737 +0200
+++ /var/tmp/diff_new_pack.DHvTkk/_new  2022-05-17 17:24:52.227194744 +0200
@@ -18,19 +18,19 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-Fabric
-Version:        2.6.0
+Version:        2.7.0
 Release:        0
 Summary:        A Pythonic tool for remote execution and deployment
 License:        BSD-2-Clause
-Group:          Development/Languages/Python
 URL:            http://fabfile.org
 Source:         
https://files.pythonhosted.org/packages/source/f/fabric/fabric-%{version}.tar.gz
-# fix executable in tests
+# PATCH-FIX-UPSTREAM gh#fabric/fabric#2209
 Patch0:         fix-executable.patch
+# PATCH-FIX-UPSTREAM gh#fabric/fabric#2210
+Patch1:         remove-mock.patch
 BuildRequires:  %{python_module cryptography >= 1.1}
 BuildRequires:  %{python_module decorator}
 BuildRequires:  %{python_module invoke >= 1.3}
-BuildRequires:  %{python_module mock >= 2.0.0}
 BuildRequires:  %{python_module paramiko >= 2.4}
 BuildRequires:  %{python_module pytest-relaxed}
 BuildRequires:  %{python_module setuptools}
@@ -72,10 +72,9 @@
 Fabric itself leverages).
 
 %prep
-%setup -q -n fabric-%{version}
+%autosetup -p1 -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
-%patch0 -p1
 
 %build
 %python_build

++++++ fabric-2.6.0.tar.gz -> fabric-2.7.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/PKG-INFO new/fabric-2.7.0/PKG-INFO
--- old/fabric-2.6.0/PKG-INFO   2021-01-19 02:09:55.000000000 +0100
+++ new/fabric-2.7.0/PKG-INFO   2022-03-26 02:43:59.000000000 +0100
@@ -1,28 +1,52 @@
 Metadata-Version: 2.1
 Name: fabric
-Version: 2.6.0
+Version: 2.7.0
 Summary: High level SSH command execution
-Home-page: http://fabfile.org
+Home-page: https://fabfile.org
 Author: Jeff Forcier
 Author-email: j...@bitprophet.org
 License: BSD
+Project-URL: Docs, https://docs.fabfile.org
+Project-URL: Source, https://github.com/fabric/fabric
+Project-URL: Issues, https://github.com/fabric/fabric/issues
+Project-URL: Changelog, https://www.fabfile.org/changelog.html
+Project-URL: CI, https://app.circleci.com/pipelines/github/fabric/fabric
+Project-URL: Twitter, https://twitter.com/pyfabric
 Description: 
         To find out what's new in this version of Fabric, please see `the 
changelog
-        <http://fabfile.org/changelog.html>`_.
+        <https://fabfile.org/changelog.html>`_.
+        
+        |version| |python| |license| |ci| |coverage|
+        
+        .. |version| image:: https://img.shields.io/pypi/v/fabric
+            :target: https://pypi.org/project/fabric/
+            :alt: PyPI - Package Version
+        .. |python| image:: https://img.shields.io/pypi/pyversions/fabric
+            :target: https://pypi.org/project/fabric/
+            :alt: PyPI - Python Version
+        .. |license| image:: https://img.shields.io/pypi/l/fabric
+            :target: https://github.com/fabric/fabric/blob/main/LICENSE
+            :alt: PyPI - License
+        .. |ci| image:: 
https://img.shields.io/circleci/build/github/fabric/fabric/main
+            :target: https://app.circleci.com/pipelines/github/fabric/fabric
+            :alt: CircleCI
+        .. |coverage| image:: https://img.shields.io/codecov/c/gh/fabric/fabric
+            :target: https://app.codecov.io/gh/fabric/fabric
+            :alt: Codecov
         
         Welcome to Fabric!
         ==================
         
         Fabric is a high level Python (2.7, 3.4+) library designed to execute 
shell
         commands remotely over SSH, yielding useful Python objects in return. 
It builds
-        on top of `Invoke <http://pyinvoke.org>`_ (subprocess command 
execution and
-        command-line features) and `Paramiko <http://paramiko.org>`_ (SSH 
protocol
+        on top of `Invoke <https://pyinvoke.org>`_ (subprocess command 
execution and
+        command-line features) and `Paramiko <https://paramiko.org>`_ (SSH 
protocol
         implementation), extending their APIs to complement one another and 
provide
         additional functionality.
         
         For a high level introduction, including example code, please see
-        `our main project website <http://fabfile.org>`_; or for detailed API 
docs, see
-        `the versioned API website <http://docs.fabfile.org>`_.
+        `our main project website <https://fabfile.org>`_; or for detailed API 
docs, see
+        `the versioned API website <https://docs.fabfile.org>`_.
         
         
 Platform: UNKNOWN
@@ -50,5 +74,5 @@
 Classifier: Topic :: System :: Clustering
 Classifier: Topic :: System :: Software Distribution
 Classifier: Topic :: System :: Systems Administration
-Provides-Extra: testing
 Provides-Extra: pytest
+Provides-Extra: testing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/README.rst new/fabric-2.7.0/README.rst
--- old/fabric-2.6.0/README.rst 2019-06-08 02:41:25.000000000 +0200
+++ new/fabric-2.7.0/README.rst 2022-03-26 02:43:27.000000000 +0100
@@ -1,13 +1,31 @@
+|version| |python| |license| |ci| |coverage|
+
+.. |version| image:: https://img.shields.io/pypi/v/fabric
+    :target: https://pypi.org/project/fabric/
+    :alt: PyPI - Package Version
+.. |python| image:: https://img.shields.io/pypi/pyversions/fabric
+    :target: https://pypi.org/project/fabric/
+    :alt: PyPI - Python Version
+.. |license| image:: https://img.shields.io/pypi/l/fabric
+    :target: https://github.com/fabric/fabric/blob/main/LICENSE
+    :alt: PyPI - License
+.. |ci| image:: https://img.shields.io/circleci/build/github/fabric/fabric/main
+    :target: https://app.circleci.com/pipelines/github/fabric/fabric
+    :alt: CircleCI
+.. |coverage| image:: https://img.shields.io/codecov/c/gh/fabric/fabric
+    :target: https://app.codecov.io/gh/fabric/fabric
+    :alt: Codecov
+
 Welcome to Fabric!
 ==================
 
 Fabric is a high level Python (2.7, 3.4+) library designed to execute shell
 commands remotely over SSH, yielding useful Python objects in return. It builds
-on top of `Invoke <http://pyinvoke.org>`_ (subprocess command execution and
-command-line features) and `Paramiko <http://paramiko.org>`_ (SSH protocol
+on top of `Invoke <https://pyinvoke.org>`_ (subprocess command execution and
+command-line features) and `Paramiko <https://paramiko.org>`_ (SSH protocol
 implementation), extending their APIs to complement one another and provide
 additional functionality.
 
 For a high level introduction, including example code, please see
-`our main project website <http://fabfile.org>`_; or for detailed API docs, see
-`the versioned API website <http://docs.fabfile.org>`_.
+`our main project website <https://fabfile.org>`_; or for detailed API docs, 
see
+`the versioned API website <https://docs.fabfile.org>`_.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/dev-requirements.txt 
new/fabric-2.7.0/dev-requirements.txt
--- old/fabric-2.6.0/dev-requirements.txt       2020-12-31 17:19:09.000000000 
+0100
+++ new/fabric-2.7.0/dev-requirements.txt       2022-03-26 02:43:27.000000000 
+0100
@@ -1,8 +1,7 @@
-# Invoke implicitly required by self/pip install -e .
+# Us, of course
+-e .
 # Invocations for common project tasks
-invocations>=1.0,<2.0
-# Invoke from git, temporarily?
--e git+https://github.com/pyinvoke/invoke#egg=invoke
+invocations==2.5.0
 # pytest-relaxed for test organization, display etc tweaks
 pytest-relaxed>=1.0.1,<1.1
 pytest==3.2.5
@@ -11,8 +10,9 @@
 six==1.10.0
 # Mock for test mocking
 mock==2.0.0
-# Linting!
+# Formatting!
 flake8==3.6.0
+black==18.6b4
 # Coverage!
 coverage==5.3.1
 codecov==2.1.11
@@ -22,5 +22,3 @@
 releases>=1.5,<2.0
 # Release tools
 semantic_version>=2.4,<2.5
-wheel==0.24
-twine==1.11.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/fabric/__init__.py 
new/fabric-2.7.0/fabric/__init__.py
--- old/fabric-2.6.0/fabric/__init__.py 2020-01-10 22:20:59.000000000 +0100
+++ new/fabric-2.7.0/fabric/__init__.py 2022-03-26 02:43:27.000000000 +0100
@@ -1,7 +1,7 @@
 # flake8: noqa
 from ._version import __version_info__, __version__
 from .connection import Config, Connection
-from .runners import Remote, Result
+from .runners import Remote, RemoteShell, Result
 from .group import Group, SerialGroup, ThreadingGroup, GroupResult
 from .tasks import task, Task
 from .executor import Executor
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/fabric/_version.py 
new/fabric-2.7.0/fabric/_version.py
--- old/fabric-2.6.0/fabric/_version.py 2021-01-19 01:49:26.000000000 +0100
+++ new/fabric-2.7.0/fabric/_version.py 2022-03-26 02:43:27.000000000 +0100
@@ -1,2 +1,2 @@
-__version_info__ = (2, 6, 0)
+__version_info__ = (2, 7, 0)
 __version__ = ".".join(map(str, __version_info__))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/fabric/config.py 
new/fabric-2.7.0/fabric/config.py
--- old/fabric-2.6.0/fabric/config.py   2020-01-10 22:21:00.000000000 +0100
+++ new/fabric-2.7.0/fabric/config.py   2022-03-26 02:43:27.000000000 +0100
@@ -5,7 +5,7 @@
 from invoke.config import Config as InvokeConfig, merge_dicts
 from paramiko.config import SSHConfig
 
-from .runners import Remote
+from .runners import Remote, RemoteShell
 from .util import get_local_user, debug
 
 
@@ -308,8 +308,7 @@
             "inline_ssh_env": False,
             "load_ssh_configs": True,
             "port": 22,
-            "run": {"replace_env": True},
-            "runners": {"remote": Remote},
+            "runners": {"remote": Remote, "remote_shell": RemoteShell},
             "ssh_config_path": None,
             "tasks": {"collection_name": "fabfile"},
             # TODO: this becomes an override/extend once Invoke grows execution
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/fabric/connection.py 
new/fabric-2.7.0/fabric/connection.py
--- old/fabric-2.6.0/fabric/connection.py       2021-01-19 01:49:26.000000000 
+0100
+++ new/fabric-2.7.0/fabric/connection.py       2022-03-26 02:43:27.000000000 
+0100
@@ -87,18 +87,18 @@
     - 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
-      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
-      close connections when you're done with them.
-
-      This can be accomplished by manually calling `close`, or by using the
-      object as a contextmanager::
-
-        with Connection('host') as c:
-            c.run('command')
-            c.put('file')
+    - It's best to explicitly close your connections when done using them. This
+      can be accomplished by manually calling `close`, or by using the object
+      as a contextmanager::
+
+          with Connection('host') as c:
+             c.run('command')
+             c.put('file')
+
+      .. warning::
+          While Fabric (and Paramiko) attempt to register connections for
+          automatic garbage collection, it's not currently safe to rely on that
+          feature, as it can lead to end-of-process hangs and similar behavior.
 
     .. note::
         This class rebinds `invoke.context.Context.run` to `.local` so both
@@ -703,7 +703,9 @@
         return channel
 
     def _remote_runner(self):
-        return self.config.runners.remote(self, inline_env=self.inline_ssh_env)
+        return self.config.runners.remote(
+            context=self, inline_env=self.inline_ssh_env
+        )
 
     @opens
     def run(self, command, **kwargs):
@@ -736,6 +738,86 @@
         """
         return self._sudo(self._remote_runner(), command, **kwargs)
 
+    @opens
+    def shell(self, **kwargs):
+        """
+        Run an interactive login shell on the remote end, as with ``ssh``.
+
+        This method is intended strictly for use cases where you can't know
+        what remote shell to invoke, or are connecting to a non-POSIX-server
+        environment such as a network appliance or other custom SSH server.
+        Nearly every other use case, including interactively-focused ones, will
+        be better served by using `run` plus an explicit remote shell command
+        (eg ``bash``).
+
+        `shell` has the following differences in behavior from `run`:
+
+        - It still returns a `~invoke.runners.Result` instance, but the object
+          will have a less useful set of attributes than with `run` or `local`:
+
+            - ``command`` will be ``None``, as there is no such input argument.
+            - ``stdout`` will contain a full record of the session, including
+              all interactive input, as that is echoed back to the user. This
+              can be useful for logging but is much less so for doing
+              programmatic things after the method returns.
+            - ``stderr`` will always be empty (same as `run` when
+              ``pty==True``).
+            - ``pty`` will always be True (because one was automatically used).
+            - ``exited`` and similar attributes will only reflect the overall
+              session, which may vary by shell or appliance but often has no
+              useful relationship with the internally executed commands' exit
+              codes.
+
+        - This method behaves as if ``warn`` is set to ``True``: even if the
+          remote shell exits uncleanly, no exception will be raised.
+        - A pty is always allocated remotely, as with ``pty=True`` under `run`.
+        - The ``inline_env`` setting is ignored, as there is no default shell
+          command to add the parameters to (and no guarantee the remote end
+          even is a shell!)
+
+        It supports **only** the following kwargs, which behave identically to
+        their counterparts in `run` unless otherwise stated:
+
+        - ``encoding``
+        - ``env``
+        - ``in_stream`` (useful in niche cases, but make sure regular `run`
+          with this argument isn't more suitable!)
+        - ``replace_env``
+        - ``watchers`` (note that due to pty echoing your stdin back to stdout,
+          a watcher will see your input as well as program stdout!)
+
+        Those keyword arguments also honor the ``run.*`` configuration tree, as
+        in `run`/`sudo`.
+
+        :returns: `~invoke.runners.Result`
+
+        :raises:
+            `~invoke.exceptions.ThreadException` (if the background I/O threads
+            encountered exceptions other than
+            `~invoke.exceptions.WatcherError`).
+
+        .. versionadded:: 2.7
+        """
+        runner = self.config.runners.remote_shell(context=self)
+        # Reinstate most defaults as explicit kwargs to ensure user's config
+        # doesn't make this mode break horribly. Then override a few that need
+        # to change, like pty.
+        allowed = ("encoding", "env", "in_stream", "replace_env", "watchers")
+        new_kwargs = {}
+        for key, value in self.config.global_defaults()["run"].items():
+            if key in allowed:
+                # Use allowed kwargs if given, otherwise also fill them from
+                # defaults
+                new_kwargs[key] = kwargs.pop(key, self.config.run[key])
+            else:
+                new_kwargs[key] = value
+        new_kwargs.update(pty=True)
+        # At this point, any leftover kwargs would be ignored, so yell instead
+        if kwargs:
+            err = "shell() got unexpected keyword arguments: {!r}"
+            raise TypeError(err.format(list(kwargs.keys())))
+        return runner.run(command=None, **new_kwargs)
+
     def local(self, *args, **kwargs):
         """
         Execute a shell command on the local system.
@@ -778,7 +860,7 @@
 
     def put(self, *args, **kwargs):
         """
-        Put a remote file (or file-like object) to the remote filesystem.
+        Put a local file (or file-like object) to the remote filesystem.
 
         Simply a wrapper for `.Transfer.put`. Please see its documentation for
         all details.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/fabric/runners.py 
new/fabric-2.7.0/fabric/runners.py
--- old/fabric-2.6.0/fabric/runners.py  2020-01-10 22:21:00.000000000 +0100
+++ new/fabric-2.7.0/fabric/runners.py  2022-03-26 02:43:27.000000000 +0100
@@ -1,3 +1,5 @@
+import signal
+
 from invoke import Runner, pty_size, Result as InvokeResult
 
 
@@ -35,8 +37,13 @@
     def start(self, command, shell, env, timeout=None):
         self.channel = self.context.create_session()
         if self.using_pty:
-            rows, cols = pty_size()
-            self.channel.get_pty(width=rows, height=cols)
+            # Set initial size to match local size
+            cols, rows = pty_size()
+            self.channel.get_pty(width=cols, height=rows)
+            # If platform supports, also respond to SIGWINCH (window change) by
+            # sending the sshd a window-change message to update
+            if hasattr(signal, "SIGWINCH"):
+                signal.signal(signal.SIGWINCH, self.handle_window_change)
         if env:
             # TODO: honor SendEnv from ssh_config (but if we do, _should_ we
             # honor it even when prefixing? That would depart from OpenSSH
@@ -55,8 +62,15 @@
                 command = "export {} && {}".format(parameters, command)
             else:
                 self.channel.update_environment(env)
+        self.send_start_message(command)
+
+    def send_start_message(self, command):
         self.channel.exec_command(command)
 
+    def run(self, command, **kwargs):
+        kwargs.setdefault("replace_env", True)
+        return super(Remote, self).run(command, **kwargs)
+
     def read_proc_stdout(self, num_bytes):
         return self.channel.recv(num_bytes)
 
@@ -108,6 +122,14 @@
         # belong in invoke.Runner anyways?
         self.channel.close()
 
+    def handle_window_change(self, signum, frame):
+        """
+        Respond to a `signal.SIGWINCH` (as a standard signal handler).
+
+        Sends a window resize command via Paramiko channel method.
+        """
+        self.channel.resize_pty(*pty_size())
+
     # TODO: shit that is in fab 1 run() but could apply to invoke.Local too:
     # * see rest of stuff in _run_command/_execute in operations.py...there is
     # a bunch that applies generally like optional exit codes, etc
@@ -128,6 +150,11 @@
     # * agent-forward close()
 
 
+class RemoteShell(Remote):
+    def send_start_message(self, command):
+        self.channel.invoke_shell()
+
+
 class Result(InvokeResult):
     """
     An `invoke.runners.Result` exposing which `.Connection` was run against.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/fabric/testing/base.py 
new/fabric-2.7.0/fabric/testing/base.py
--- old/fabric-2.6.0/fabric/testing/base.py     2021-01-19 01:49:26.000000000 
+0100
+++ new/fabric-2.7.0/fabric/testing/base.py     2022-03-26 02:43:27.000000000 
+0100
@@ -67,6 +67,25 @@
         # grow more dependencies? Ehhh
         return "<{} cmd={!r}>".format(self.__class__.__name__, self.cmd)
 
+    def expect_execution(self, channel):
+        """
+        Assert that the ``channel`` was used to run this command.
+
+        .. versionadded:: 2.7
+        """
+        channel.exec_command.assert_called_with(self.cmd or ANY)
+
+
+class ShellCommand(Command):
+    """
+    A pseudo-command that expects an interactive shell to be executed.
+
+    .. versionadded:: 2.7
+    """
+
+    def expect_execution(self, channel):
+        channel.invoke_shell.assert_called_once_with()
+
 
 class MockChannel(Mock):
     """
@@ -254,8 +273,8 @@
         for channel, command in zip(self.channels, self.commands):
             # Expect an open_session for each command exec
             session_opens.append(call())
-            # Expect that the channel gets an exec_command
-            channel.exec_command.assert_called_with(command.cmd or ANY)
+            # Expect that the channel gets an exec_command or etc
+            command.expect_execution(channel=channel)
             # Expect written stdin, if given
             if command.in_:
                 assert channel._stdin.getvalue() == command.in_
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/fabric.egg-info/PKG-INFO 
new/fabric-2.7.0/fabric.egg-info/PKG-INFO
--- old/fabric-2.6.0/fabric.egg-info/PKG-INFO   2021-01-19 02:09:55.000000000 
+0100
+++ new/fabric-2.7.0/fabric.egg-info/PKG-INFO   2022-03-26 02:43:58.000000000 
+0100
@@ -1,28 +1,52 @@
 Metadata-Version: 2.1
 Name: fabric
-Version: 2.6.0
+Version: 2.7.0
 Summary: High level SSH command execution
-Home-page: http://fabfile.org
+Home-page: https://fabfile.org
 Author: Jeff Forcier
 Author-email: j...@bitprophet.org
 License: BSD
+Project-URL: Docs, https://docs.fabfile.org
+Project-URL: Source, https://github.com/fabric/fabric
+Project-URL: Issues, https://github.com/fabric/fabric/issues
+Project-URL: Changelog, https://www.fabfile.org/changelog.html
+Project-URL: CI, https://app.circleci.com/pipelines/github/fabric/fabric
+Project-URL: Twitter, https://twitter.com/pyfabric
 Description: 
         To find out what's new in this version of Fabric, please see `the 
changelog
-        <http://fabfile.org/changelog.html>`_.
+        <https://fabfile.org/changelog.html>`_.
+        
+        |version| |python| |license| |ci| |coverage|
+        
+        .. |version| image:: https://img.shields.io/pypi/v/fabric
+            :target: https://pypi.org/project/fabric/
+            :alt: PyPI - Package Version
+        .. |python| image:: https://img.shields.io/pypi/pyversions/fabric
+            :target: https://pypi.org/project/fabric/
+            :alt: PyPI - Python Version
+        .. |license| image:: https://img.shields.io/pypi/l/fabric
+            :target: https://github.com/fabric/fabric/blob/main/LICENSE
+            :alt: PyPI - License
+        .. |ci| image:: 
https://img.shields.io/circleci/build/github/fabric/fabric/main
+            :target: https://app.circleci.com/pipelines/github/fabric/fabric
+            :alt: CircleCI
+        .. |coverage| image:: https://img.shields.io/codecov/c/gh/fabric/fabric
+            :target: https://app.codecov.io/gh/fabric/fabric
+            :alt: Codecov
         
         Welcome to Fabric!
         ==================
         
         Fabric is a high level Python (2.7, 3.4+) library designed to execute 
shell
         commands remotely over SSH, yielding useful Python objects in return. 
It builds
-        on top of `Invoke <http://pyinvoke.org>`_ (subprocess command 
execution and
-        command-line features) and `Paramiko <http://paramiko.org>`_ (SSH 
protocol
+        on top of `Invoke <https://pyinvoke.org>`_ (subprocess command 
execution and
+        command-line features) and `Paramiko <https://paramiko.org>`_ (SSH 
protocol
         implementation), extending their APIs to complement one another and 
provide
         additional functionality.
         
         For a high level introduction, including example code, please see
-        `our main project website <http://fabfile.org>`_; or for detailed API 
docs, see
-        `the versioned API website <http://docs.fabfile.org>`_.
+        `our main project website <https://fabfile.org>`_; or for detailed API 
docs, see
+        `the versioned API website <https://docs.fabfile.org>`_.
         
         
 Platform: UNKNOWN
@@ -50,5 +74,5 @@
 Classifier: Topic :: System :: Clustering
 Classifier: Topic :: System :: Software Distribution
 Classifier: Topic :: System :: Systems Administration
-Provides-Extra: testing
 Provides-Extra: pytest
+Provides-Extra: testing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/integration/connection.py 
new/fabric-2.7.0/integration/connection.py
--- old/fabric-2.6.0/integration/connection.py  2020-01-10 22:21:00.000000000 
+0100
+++ new/fabric-2.7.0/integration/connection.py  2022-03-26 02:43:27.000000000 
+0100
@@ -1,15 +1,22 @@
 import os
 import time
 
+try:
+    from invoke.vendor.six import StringIO
+except ImportError:
+    from six import StringIO
+
 from invoke import pty_size, CommandTimedOut
+from invocations.environment import in_ci
 from pytest import skip, raises
+from pytest_relaxed import trap
 
 from fabric import Connection, Config
 
 
 # TODO: use pytest markers
-def skip_outside_travis():
-    if not os.environ.get("TRAVIS", False):
+def skip_outside_ci():
+    if not in_ci():
         skip()
 
 
@@ -56,6 +63,20 @@
             assert "\r\n" in result.stdout
             assert result.pty is True
 
+    class shell:
+        @trap
+        def base_case(self):
+            result = Connection("localhost").shell(
+                # Some extra newlines to make sure it doesn't get split up by
+                # motd/prompt
+                in_stream=StringIO("\n\nexit\n")
+            )
+            assert result.command is None
+            assert "exit" in result.stdout
+            assert result.stderr == ""
+            assert result.exited == 0
+            assert result.pty is True
+
     class local:
         def wraps_invoke_run(self):
             # NOTE: most of the interesting tests about this are in
@@ -91,14 +112,14 @@
             """
             Run command via sudo on host localhost
             """
-            skip_outside_travis()
+            skip_outside_ci()
             assert self.cxn.sudo("whoami").stdout.strip() == "root"
 
         def mixed_sudo_and_normal_commands(self):
             """
             Run command via sudo, and not via sudo, on localhost
             """
-            skip_outside_travis()
+            skip_outside_ci()
             logname = os.environ["LOGNAME"]
             assert self.cxn.run("whoami").stdout.strip() == logname
             assert self.cxn.sudo("whoami").stdout.strip() == "root"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/setup.cfg new/fabric-2.7.0/setup.cfg
--- old/fabric-2.6.0/setup.cfg  2021-01-19 02:09:55.000000000 +0100
+++ new/fabric-2.7.0/setup.cfg  2022-03-26 02:43:59.000000000 +0100
@@ -1,18 +1,9 @@
 [wheel]
 universal = 1
 
-[flake8]
-exclude = .git,sites
-ignore = E124,E125,E128,E261,E301,E302,E303,W503
-max-line-length = 79
-
 [metadata]
 license_file = LICENSE
 
-[tool:pytest]
-testpaths = tests
-python_files = *
-
 [egg_info]
 tag_build = 
 tag_date = 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/setup.py new/fabric-2.7.0/setup.py
--- old/fabric-2.6.0/setup.py   2021-01-19 01:49:26.000000000 +0100
+++ new/fabric-2.7.0/setup.py   2022-03-26 02:43:27.000000000 +0100
@@ -48,7 +48,7 @@
 # Frankenstein long_description: changelog note + README
 long_description = """
 To find out what's new in this version of Fabric, please see `the changelog
-<http://fabfile.org/changelog.html>`_.
+<https://fabfile.org/changelog.html>`_.
 
 {}
 """.format(
@@ -66,7 +66,15 @@
     long_description=long_description,
     author="Jeff Forcier",
     author_email="j...@bitprophet.org",
-    url="http://fabfile.org";,
+    url="https://fabfile.org";,
+    project_urls={
+        "Docs": "https://docs.fabfile.org";,
+        "Source": "https://github.com/fabric/fabric";,
+        "Issues": "https://github.com/fabric/fabric/issues";,
+        "Changelog": "https://www.fabfile.org/changelog.html";,
+        "CI": "https://app.circleci.com/pipelines/github/fabric/fabric";,
+        "Twitter": "https://twitter.com/pyfabric";,
+    },
     install_requires=["invoke>=1.3,<2.0", "paramiko>=2.4", "pathlib2"],
     extras_require={
         "testing": testing_deps,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/sites/docs/concepts/configuration.rst 
new/fabric-2.7.0/sites/docs/concepts/configuration.rst
--- old/fabric-2.6.0/sites/docs/concepts/configuration.rst      2020-01-10 
22:21:00.000000000 +0100
+++ new/fabric-2.7.0/sites/docs/concepts/configuration.rst      2022-03-26 
02:43:27.000000000 +0100
@@ -46,9 +46,9 @@
 Overrides of Invoke-level defaults
 ----------------------------------
 
-- ``run.replace_env``: ``True``, instead of ``False``, so that remote commands
-  run with a 'clean', empty environment instead of inheriting a copy of the
-  current process' environment.
+- ``run.replace_env``: defaults to ``True``, instead of ``False``, so that
+  remote commands run with a 'clean', empty environment instead of inheriting
+  a copy of the current process' environment.
 
   This is for security purposes: leaking local environment data remotely by
   default would be unsanitary. It's also compatible with the behavior of
@@ -57,6 +57,11 @@
   .. seealso::
     The warning under `paramiko.channel.Channel.set_environment_variable`.
 
+  .. note::
+    This is currently accomplished with a keyword argument override, as the
+    config setting itself was applying to both ``run`` and ``local``. Future
+    updates will ensure the two methods use separate config values.
+
 Extensions to Invoke-level defaults
 -----------------------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/sites/docs/conf.py 
new/fabric-2.7.0/sites/docs/conf.py
--- old/fabric-2.6.0/sites/docs/conf.py 2019-06-08 02:41:25.000000000 +0200
+++ new/fabric-2.7.0/sites/docs/conf.py 2022-03-26 02:43:27.000000000 +0100
@@ -14,12 +14,12 @@
 # under RTD.
 target = join(dirname(__file__), "..", "www", "_build")
 if on_rtd:
-    target = "http://www.fabfile.org/";
+    target = "https://www.fabfile.org/";
 www = (target, None)
 # Intersphinx connection to www site
 intersphinx_mapping.update({"www": www})
 
 # Sister-site links to WWW
 html_theme_options["extra_nav_links"] = {
-    "Main website": "http://www.fabfile.org";
+    "Main website": "https://www.fabfile.org";
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/sites/docs/getting-started.rst 
new/fabric-2.7.0/sites/docs/getting-started.rst
--- old/fabric-2.6.0/sites/docs/getting-started.rst     2020-01-10 
22:20:59.000000000 +0100
+++ new/fabric-2.7.0/sites/docs/getting-started.rst     2021-12-05 
00:24:42.000000000 +0100
@@ -291,7 +291,7 @@
 
     >>> from fabric import Connection
     >>> for host in ('web1', 'web2', 'mac1'):
-    >>>     result = Connection(host).run('uname -s')
+    ...     result = Connection(host).run('uname -s')
     ...     print("{}: {}".format(host, result.stdout.strip()))
     ...
     ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/sites/shared_conf.py 
new/fabric-2.7.0/sites/shared_conf.py
--- old/fabric-2.6.0/sites/shared_conf.py       2020-01-10 22:20:59.000000000 
+0100
+++ new/fabric-2.7.0/sites/shared_conf.py       2022-03-26 02:43:27.000000000 
+0100
@@ -2,6 +2,7 @@
 from os.path import join, dirname, abspath
 from datetime import datetime
 
+from invocations.environment import in_ci
 import alabaster
 
 
@@ -19,8 +20,8 @@
     "description": "Pythonic remote execution",
     "github_user": "fabric",
     "github_repo": "fabric",
-    "travis_button": True,
-    "codecov_button": True,
+    "travis_button": False,  # Circle now
+    "codecov_button": False,  # README badge now
     "tidelift_url": 
"https://tidelift.com/subscription/pkg/pypi-fabric?utm_source=pypi-fabric&utm_medium=referral&utm_campaign=docs";,
     "analytics_id": "UA-18486793-1",
     "link": "#3782BE",
@@ -40,28 +41,27 @@
 """
 
 on_rtd = os.environ.get("READTHEDOCS") == "True"
-on_travis = os.environ.get("TRAVIS", False)
-on_dev = not (on_rtd or on_travis)
+on_dev = not (on_rtd or in_ci())
 
 # Invoke (docs + www)
 inv_target = join(
     dirname(__file__), "..", "..", "invoke", "sites", "docs", "_build"
 )
 if not on_dev:
-    inv_target = "http://docs.pyinvoke.org/en/latest/";
+    inv_target = "https://docs.pyinvoke.org/en/latest/";
 inv_www_target = join(
     dirname(__file__), "..", "..", "invoke", "sites", "www", "_build"
 )
 if not on_dev:
-    inv_www_target = "http://pyinvoke.org/";
+    inv_www_target = "https://pyinvoke.org/";
 # Paramiko (docs)
 para_target = join(
     dirname(__file__), "..", "..", "paramiko", "sites", "docs", "_build"
 )
 if not on_dev:
-    para_target = "http://docs.paramiko.org/en/latest/";
+    para_target = "https://docs.paramiko.org/en/latest/";
 intersphinx_mapping = {
-    "python": ("http://docs.python.org/";, None),
+    "python": ("https://docs.python.org/";, None),
     "invoke": (inv_target, None),
     "invoke_www": (inv_www_target, None),
     "paramiko": (para_target, None),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/sites/www/changelog.rst 
new/fabric-2.7.0/sites/www/changelog.rst
--- old/fabric-2.6.0/sites/www/changelog.rst    2021-01-19 02:09:46.000000000 
+0100
+++ new/fabric-2.7.0/sites/www/changelog.rst    2022-03-26 02:43:56.000000000 
+0100
@@ -5,6 +5,41 @@
 .. note::
     Looking for the Fabric 1.x changelog? See :doc:`/changelog-v1`.
 
+.. warning::
+    Keep in mind that Fabric is largely a thin wrapper around `Paramiko
+    <https://paramiko.org/changelog.html>`_  and `Invoke
+    <https://pyinvoke.org/changelog.html>`_ - just because Fabric itself hasn't
+    had a release in a while doesn't mean its capabilities aren't improving!
+    Click those projects' names in this paragraph to visit their changelogs and
+    see what you might get if you upgrade your dependencies.
+
+- :release:`2.7.0 <2022-03-25>`
+- :support:`-` Overhaul administrative metadata and migrate to Circle-CI from
+  Travis-CI.
+- :feature:`-` Add `~fabric.connection.Connection.shell`, a belated port of
+  the v1 ``open_shell()`` feature.
+
+  - This wasn't needed initially, as the modern implementation of
+    `~fabric.connection.Connection.run` is as good or better for full
+    interaction than ``open_shell()`` was, provided you're happy supplying a
+    specific shell to execute.
+  - `~fabric.connection.Connection.shell` serves the corner case where you
+    *aren't* happy doing that, eg when you're speaking to network appliances or
+    other targets which are not typical Unix server environments.
+  - Like ``open_shell()``, this new method is primarily for interactive use,
+    and has a slightly less useful return value. See its API docs for more
+    details.
+
+- :feature:`-` Forward local terminal resizes to the remote end, when
+  applicable. (For the technical: this means we now turn ``SIGWINCH`` into SSH
+  ``window-change`` messages.)
+- :bug:`2142 major` Update `~fabric.connection.Connection` temporarily so that
+  it doesn't incidentally apply ``replace_env=True`` to local shell commands,
+  only remote ones. On Windows under Python 3.7+, this was causing local
+  commands to fail due to lack of some environment variables. Future updates
+  will cleanly separate the config tree for remote vs local methods.
+
+  Thanks to Bartosz Lachowicz for the report and David JM Emmett for the patch.
 - :release:`2.6.0 <2021-01-18>`
 - :bug:`- major` Fix a handful of issues in the handling and
   mocking of SFTP local paths and ``os.path`` members within
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/sites/www/conf.py 
new/fabric-2.7.0/sites/www/conf.py
--- old/fabric-2.6.0/sites/www/conf.py  2019-06-08 02:41:25.000000000 +0200
+++ new/fabric-2.7.0/sites/www/conf.py  2022-03-26 02:43:27.000000000 +0100
@@ -18,8 +18,10 @@
 # under RTD.
 target = join(dirname(__file__), "..", "docs", "_build")
 if on_rtd:
-    target = "http://docs.fabfile.org/en/latest/";
+    target = "https://docs.fabfile.org/en/latest/";
 intersphinx_mapping.update({"docs": (target, None)})
 
 # Sister-site links to API docs
-html_theme_options["extra_nav_links"] = {"API Docs": "http://docs.fabfile.org"}
+html_theme_options["extra_nav_links"] = {
+    "API Docs": "https://docs.fabfile.org";
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/sites/www/contact.rst 
new/fabric-2.7.0/sites/www/contact.rst
--- old/fabric-2.6.0/sites/www/contact.rst      2019-06-08 02:41:25.000000000 
+0200
+++ new/fabric-2.7.0/sites/www/contact.rst      2022-03-26 02:43:27.000000000 
+0100
@@ -38,10 +38,8 @@
 
 .. _irc:
 
-IRC
----
+Blog posts
+----------
 
-We maintain a semi-official IRC channel at ``#fabric`` on Freenode
-(``irc://irc.freenode.net``) where the developers and other users may be found.
-As always with IRC, we can't promise immediate responses, but some folks keep
-logs of the channel and will try to get back to you when they can.
+The developer posts occasional (but usually important) news on his blog; there
+is a dedicated Fabric category: https://bitprophet.org/categories/fabric/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/sites/www/development.rst 
new/fabric-2.7.0/sites/www/development.rst
--- old/fabric-2.6.0/sites/www/development.rst  2019-06-08 02:41:25.000000000 
+0200
+++ new/fabric-2.7.0/sites/www/development.rst  2022-03-26 02:43:27.000000000 
+0100
@@ -3,7 +3,7 @@
 ===========
 
 The Fabric development team is headed by `Jeff Forcier
-<http://bitprophet.org>`_, aka ``bitprophet``.  However, dozens of other
+<https://bitprophet.org>`_, aka ``bitprophet``.  However, dozens of other
 developers pitch in by submitting patches and ideas via `GitHub issues and pull
 requests <https://github.com/fabric/fabric>`_, :ref:`IRC <irc>` or the `mailing
 list <http://lists.nongnu.org/mailman/listinfo/fab-user>`_.
@@ -25,8 +25,8 @@
   `ticket tracker`_ first, though,
   when submitting feature ideas.)
 * **Report bugs or submit feature requests.** We follow `contribution-guide.org
-  <http://contribution-guide.org>`_'s guidelines, so please check them out 
before
-  visiting the `ticket tracker`_.
+  <https://contribution-guide.org>`_'s guidelines, so please check them out
+  before visiting the `ticket tracker`_.
 
 .. _ticket tracker: https://github.com/fabric/fabric/issues
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/sites/www/index.rst 
new/fabric-2.7.0/sites/www/index.rst
--- old/fabric-2.6.0/sites/www/index.rst        2020-01-10 22:21:00.000000000 
+0100
+++ new/fabric-2.7.0/sites/www/index.rst        2022-03-26 02:43:27.000000000 
+0100
@@ -27,8 +27,8 @@
     Ran 'uname -s' on web1.example.com, got stdout:
     Linux
 
-It builds on top of `Invoke <http://pyinvoke.org>`_ (subprocess command
-execution and command-line features) and `Paramiko <http://paramiko.org>`_ (SSH
+It builds on top of `Invoke <https://pyinvoke.org>`_ (subprocess command
+execution and command-line features) and `Paramiko <https://paramiko.org>`_ 
(SSH
 protocol implementation), extending their APIs to complement one another and
 provide additional functionality.
 
@@ -172,7 +172,7 @@
 * Multiple tasks may be given in a single CLI session, e.g. ``fab build
   deploy``;
 * Much more - all other Invoke functionality is supported - see `its
-  documentation <http://docs.pyinvoke.org>`_ for details.
+  documentation <https://docs.pyinvoke.org>`_ for details.
 
 I'm a user of Fabric 1, how do I upgrade?
 -----------------------------------------
@@ -190,7 +190,7 @@
 forth.
 
 Detailed conceptual and API documentation can be found at our code
-documentation site, `docs.fabfile.org <http://docs.fabfile.org>`_.
+documentation site, `docs.fabfile.org <https://docs.fabfile.org>`_.
 
 
 .. toctree::
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/sites/www/installing.rst 
new/fabric-2.7.0/sites/www/installing.rst
--- old/fabric-2.6.0/sites/www/installing.rst   2020-01-10 22:21:00.000000000 
+0100
+++ new/fabric-2.7.0/sites/www/installing.rst   2022-03-26 02:43:27.000000000 
+0100
@@ -6,7 +6,7 @@
     Users looking to install Fabric 1.x should see :doc:`installing-1.x`.
     However, :doc:`upgrading <upgrading>` to 2.x is strongly recommended.
 
-Fabric is best installed via `pip <http://pip-installer.org>`_::
+Fabric is best installed via `pip <https://pip-installer.org>`_::
 
     $ pip install fabric
 
@@ -103,10 +103,10 @@
 In order for Fabric's installation to succeed, you will need the following:
 
 * the Python programming language, versions 2.7 or 3.4+;
-* the `Invoke <http://pyinvoke.org>`_ command-running and task-execution
+* the `Invoke <https://pyinvoke.org>`_ command-running and task-execution
   library;
-* and the `Paramiko <http://paramiko.org>`_ SSH library (as well as its own
-  dependencies; see `its install docs <http://paramiko.org/installing.html>`_.)
+* and the `Paramiko <https://paramiko.org>`_ SSH library (as well as its own
+  dependencies; see `its install docs 
<https://paramiko.org/installing.html>`_.)
 
 Development dependencies
 ------------------------
@@ -139,7 +139,7 @@
   repository on Github <https://github.com/fabric/fabric>`_ (cloning
   instructions available on that page).
 * Make your own fork of the Github repository by making a Github account,
-  visiting `fabric/fabric <http://github.com/fabric/fabric>`_ and clicking the
+  visiting `fabric/fabric <https://github.com/fabric/fabric>`_ and clicking the
   "fork" button.
 
 .. note::
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/sites/www/roadmap.rst 
new/fabric-2.7.0/sites/www/roadmap.rst
--- old/fabric-2.6.0/sites/www/roadmap.rst      2019-06-08 02:41:25.000000000 
+0200
+++ new/fabric-2.7.0/sites/www/roadmap.rst      2022-03-26 02:43:27.000000000 
+0100
@@ -5,10 +5,15 @@
 ===================
 
 This document outlines Fabric's intended development path. Please make sure
-you're reading `the latest version <http://fabfile.org/roadmap.html>`_ of this
+you're reading `the latest version <https://fabfile.org/roadmap.html>`_ of this
 document, and also see the page about :ref:`upgrading <upgrading>` if you are
 migrating from version 1 to versions 2 or above.
 
+.. seealso::
+    The primary project maintainer keeps an overall `roadmap
+    <https://bitprophet.org/projects#roadmap>`__ on his website, which can help
+    contextualize when Fabric may get its next update.
+
 Fabric 2 and above
 ==================
 
@@ -33,8 +38,8 @@
 
 .. note::
     Many features that you may use via Fabric will only need development in the
-    libraries Fabric wraps -- `Invoke <http://pyinvoke.org>`_ and `Paramiko
-    <http://paramiko.org>`_ -- and unless Fabric itself needs changes to match,
+    libraries Fabric wraps -- `Invoke <https://pyinvoke.org>`_ and `Paramiko
+    <https://paramiko.org>`_ -- and unless Fabric itself needs changes to 
match,
     you can often get new features by upgrading only one of the three. Make
     sure to check the other projects' changelogs periodically!
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/sites/www/upgrading.rst 
new/fabric-2.7.0/sites/www/upgrading.rst
--- old/fabric-2.6.0/sites/www/upgrading.rst    2021-01-19 01:49:26.000000000 
+0100
+++ new/fabric-2.7.0/sites/www/upgrading.rst    2022-03-26 02:43:27.000000000 
+0100
@@ -265,7 +265,7 @@
 insufficient.
 
 - **Ported**: available already, possibly renamed or moved (frequently, moved
-  into the `Invoke <http://pyinvoke.org>`_ codebase.)
+  into the `Invoke <https://pyinvoke.org>`_ codebase.)
 - **Pending**: would fit, but has not yet been ported, good candidate for a
   patch. *These entries link to the appropriate Github ticket* - please do
   not make new ones!
@@ -804,10 +804,10 @@
     * - ``open_shell`` for obtaining interactive-friendly remote shell sessions
         (something that ``run`` historically was bad at )
       - Ported
-      - Technically "removed", but only because the new version of
-        ``run`` is vastly improved and can deal with interactive sessions at
-        least as well as the old ``open_shell`` did, if not moreso.
-        ``c.run("/my/favorite/shell", pty=True)`` should be all you need.
+      - Not only is the new version of ``run`` vastly improved and able to deal
+        with interactive sessions at least as well as the old ``open_shell``
+        (provided you supply ``pty=True``), but for corner cases there's also a
+        direct port: `~fabric.connection.Connection.shell`.
 
 ``run``
 ~~~~~~~
@@ -878,6 +878,28 @@
         Behavior is much the same: no shell wrapping (as in legacy ``run``),
         just informing the operating system what actual program to run.
 
+``open_shell``
+~~~~~~~~~~~~~~
+
+As noted in the main list, this is now `~fabric.connection.Connection.shell`,
+and behaves similarly to ``open_shell`` (exit codes, if any, are ignored; a PTY
+is assumed; etc). It has some improvements too, such as a return value (which
+is slightly lacking compared to that from `~fabric.connection.Connection.run`
+but still a big improvement over ``None``).
+
+.. list-table::
+    :widths: 40 10 50
+
+    * - ``command`` optional kwarg allowing 'prefilling' the input stream with
+        a specific command string plus newline
+      - Removed
+      - If you needed this, you should instead try the modern version of
+        `~fabric.connection.Connection.run`, which is equally capable of
+        interaction as `~fabric.connection.Connection.shell` but takes a
+        command to execute. There's a small chance we'll add this back later if
+        anybody misses it (there's a few corner cases that could possibly want
+        it).
+
 .. _upgrading-utility:
 
 Utilities
@@ -918,8 +940,8 @@
       - v1 required using a Fabric-specific 'unwrap_tasks' helper function
         somewhere in your Sphinx build pipeline; now you can instead just
         enable the new `invocations.autodoc
-        <http://invocations.readthedocs.io/en/latest/api/autodoc.html>`_ Sphinx
-        mini-plugin in your extensions list; see link for details.
+        <https://invocations.readthedocs.io/en/latest/api/autodoc.html>`_
+        Sphinx mini-plugin in your extensions list; see link for details.
     * - ``network.normalize``, ``denormalize`` and ``parse_host_string``,
         ostensibly internals but sometimes exposed to users for dealing with
         host strings
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/tasks.py new/fabric-2.7.0/tasks.py
--- old/fabric-2.6.0/tasks.py   2020-01-10 22:21:00.000000000 +0100
+++ new/fabric-2.7.0/tasks.py   2022-03-26 02:43:27.000000000 +0100
@@ -1,72 +1,30 @@
-from functools import partial
 from os import environ, getcwd
 import sys
 
-from invocations import travis
+from invocations import ci
 from invocations.checks import blacken
 from invocations.docs import docs, www, sites, watch_docs
-from invocations.pytest import test, integration as integration_, coverage
+from invocations.pytest import (
+    test,
+    integration as integration_,
+    coverage as coverage_,
+)
 from invocations.packaging import release
-from invocations.util import tmpdir
 
 from invoke import Collection, task
-from invoke.util import LOG_FORMAT
-
-
-# Neuter the normal release.publish task to prevent accidents, then reinstate
-# it as a custom task that does dual fabric-xxx and fabric2-xxx releases.
-# TODO: tweak this once release.all_ actually works right...sigh
-# TODO: if possible, try phrasing as a custom build that builds x2, and then
-# convince the vanilla publish() to use that custom build instead of its local
-# build?
-# NOTE: this skips the dual_wheels, alt_python bits the upstream task has,
-# which are at the moment purely for Invoke's sake (as it must publish explicit
-# py2 vs py3 wheels due to some vendored dependencies)
-@task
-def publish(
-    c,
-    sdist=True,
-    wheel=False,
-    index=None,
-    sign=False,
-    dry_run=False,
-    directory=None,
-    check_desc=False,
-):
-    # TODO: better pattern for merging kwargs + config
-    config = c.config.get("packaging", {})
-    index = config.get("index", index)
-    sign = config.get("sign", sign)
-    check_desc = config.get("check_desc", check_desc)
-    # Initial sanity check, if needed. Will die usefully.
-    # TODO: this could also get factored out harder in invocations. shrug. it's
-    # like 3 lines total...
-    if check_desc:
-        c.run("python setup.py check -r -s")
-    with tmpdir(skip_cleanup=dry_run, explicit=directory) as directory:
-        # Doesn't reeeeally need to be a partial, but if we start having to add
-        # a kwarg to one call or the other, it's nice
-        builder = partial(
-            release.build, c, sdist=sdist, wheel=wheel, directory=directory
-        )
-        # Vanilla build
-        builder()
-        # Fabric 2 build
-        environ["PACKAGE_AS_FABRIC2"] = "yes"
-        builder()
-        # Upload
-        release.upload(c, directory, index, sign, dry_run)
 
 
 @task
-def sanity_test_from_v1(c):
+def safety_test_v1_to_v2_shim(c):
     """
-    Run some very quick in-process sanity tests on a dual fabric1-v-2 env.
+    Run some very quick in-process safety checks 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.
+    # TODO: once the final Fabric 1 release is out w/ 3.x compat, fix this up
+    # and add to CI
     PYTHON = environ.get("TRAVIS_PYTHON_VERSION", "")
     if PYTHON.startswith("3") or PYTHON == "pypy3":
         return
@@ -114,33 +72,39 @@
     return integration_(c, opts, pty, x, k, verbose, color, capture, module)
 
 
-# Better than nothing, since we haven't solved "pretend I have some other
-# task's signature" yet...
-publish.__doc__ = release.publish.__doc__
-my_release = Collection(
-    "release", release.build, release.status, publish, release.prepare
-)
+# NOTE: copied from invoke's tasks.py
+@task
+def coverage(c, report="term", opts="", codecov=False):
+    """
+    Run pytest in coverage mode. See `invocations.pytest.coverage` for details.
+    """
+    # Use our own test() instead of theirs.
+    # Also add integration test so this always hits both.
+    coverage_(
+        c,
+        report=report,
+        opts=opts,
+        tester=test,
+        additional_testers=[integration],
+        codecov=codecov,
+    )
+
 
 ns = Collection(
     blacken,
+    ci,
     coverage,
     docs,
     integration,
-    my_release,
+    release,
     sites,
     test,
-    travis,
     watch_docs,
     www,
-    sanity_test_from_v1,
+    safety_test_v1_to_v2_shim,
 )
 ns.configure(
     {
-        "tests": {
-            # TODO: have pytest tasks honor these?
-            "package": "fabric",
-            "logformat": LOG_FORMAT,
-        },
         "packaging": {
             # NOTE: this is currently for identifying the source directory.
             # Should it get used for actual releasing, needs changing.
@@ -149,13 +113,7 @@
             "wheel": True,
             "check_desc": True,
             "changelog_file": "sites/www/changelog.rst",
-        },
-        # TODO: perhaps move this into a tertiary, non automatically loaded,
-        # conf file so that both this & the code under test can reference it?
-        # Meh.
-        "travis": {
-            "sudo": {"user": "sudouser", "password": "mypass"},
-            "black": {"version": "18.6b4"},
-        },
+            "rebuild_with_env": dict(PACKAGE_AS_FABRIC2="yes"),
+        }
     }
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/tests/config.py 
new/fabric-2.7.0/tests/config.py
--- old/fabric-2.6.0/tests/config.py    2020-01-10 22:21:00.000000000 +0100
+++ new/fabric-2.7.0/tests/config.py    2022-03-26 02:43:27.000000000 +0100
@@ -2,9 +2,10 @@
 from os.path import join, expanduser
 
 from paramiko.config import SSHConfig
+from invoke import Local
 from invoke.vendor.lexicon import Lexicon
 
-from fabric import Config
+from fabric import Config, Remote, RemoteShell
 from fabric.util import get_local_user
 
 from mock import patch, call
@@ -50,10 +51,14 @@
 
     def overrides_some_Invoke_defaults(self):
         config = Config()
-        # This value defaults to False in Invoke proper.
-        assert config.run.replace_env is True
         assert config.tasks.collection_name == "fabfile"
 
+    def amends_Invoke_runners_map(self):
+        config = Config()
+        assert config.runners == dict(
+            remote=Remote, remote_shell=RemoteShell, local=Local
+        )
+
     def uses_Fabric_prefix(self):
         # NOTE: see also the integration-esque tests in tests/main.py; this
         # just tests the underlying data/attribute driving the behavior.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/tests/connection.py 
new/fabric-2.7.0/tests/connection.py
--- old/fabric-2.6.0/tests/connection.py        2020-12-31 20:52:19.000000000 
+0100
+++ new/fabric-2.7.0/tests/connection.py        2022-03-26 02:43:27.000000000 
+0100
@@ -1,9 +1,10 @@
 from itertools import chain, repeat
 
 try:
-    from invoke.vendor.six import b
+    from invoke.vendor.six import b, StringIO
 except ImportError:
-    from six import b
+    from six import b, StringIO
+
 import errno
 from os.path import join
 import socket
@@ -12,7 +13,7 @@
 from mock import patch, Mock, call, ANY
 from paramiko.client import SSHClient, AutoAddPolicy
 from paramiko import SSHConfig
-import pytest  # for mark
+import pytest  # for mark, internal raises
 from pytest import skip, param
 from pytest_relaxed import raises
 from invoke.vendor.lexicon import Lexicon
@@ -29,6 +30,7 @@
 
 # Remote is woven in as a config default, so must be patched there
 remote_path = "fabric.config.Remote"
+remote_shell_path = "fabric.config.RemoteShell"
 
 
 def _select_result(obj):
@@ -950,8 +952,6 @@
             self, Remote, client
         ):
             remote = Remote.return_value
-            sentinel = object()
-            remote.run.return_value = sentinel
             c = Connection("host")
             r1 = c.run("command")
             r2 = c.run("command", warn=True, hide="stderr")
@@ -959,12 +959,76 @@
             # .assert_called_with()) stopped working, apparently triggered by
             # our code...somehow...after commit (roughly) 80906c7.
             # And yet, .call_args_list and its brethren work fine. Wha?
-            Remote.assert_any_call(c, inline_env=False)
+            Remote.assert_any_call(context=c, inline_env=False)
             remote.run.assert_has_calls(
                 [call("command"), call("command", warn=True, hide="stderr")]
             )
             for r in (r1, r2):
-                assert r is sentinel
+                assert r is remote.run.return_value
+
+    class shell:
+        def setup(self):
+            self.defaults = Config.global_defaults()["run"]
+
+        @patch(remote_shell_path)
+        def calls_RemoteShell_run_with_all_kwargs_and_returns_its_result(
+            self, RemoteShell, client
+        ):
+            remote = RemoteShell.return_value
+            cxn = Connection("host")
+            kwargs = dict(
+                env={"foo": "bar"},
+                replace_env=True,
+                encoding="utf-16",
+                in_stream=StringIO("meh"),
+                watchers=["meh"],
+            )
+            result = cxn.shell(**kwargs)
+            RemoteShell.assert_any_call(context=cxn)
+            assert remote.run.call_count == 1
+            # Expect explicit use of default values for all kwarg-settings
+            # besides what shell() itself tweaks
+            expected = dict(self.defaults, pty=True, command=None, **kwargs)
+            assert remote.run.call_args[1] == expected
+            assert result is remote.run.return_value
+
+        def raises_TypeError_for_disallowed_kwargs(self, client):
+            for key in self.defaults.keys():
+                if key in (
+                    "env",
+                    "replace_env",
+                    "encoding",
+                    "in_stream",
+                    "watchers",
+                ):
+                    continue
+                with pytest.raises(
+                    TypeError,
+                    match=r"unexpected keyword arguments: \['{}'\]".format(
+                        key
+                    ),
+                ):
+                    Connection("host").shell(**{key: "whatever"})
+
+        @patch(remote_shell_path)
+        def honors_config_system_for_allowed_kwargs(self, RemoteShell, client):
+            remote = RemoteShell.return_value
+            allowed = dict(
+                env={"foo": "bar"},
+                replace_env=True,
+                encoding="utf-16",
+                in_stream="sentinel",
+                watchers=["sentinel"],
+            )
+            ignored = dict(echo=True, hide="foo")  # Spot check
+            config = Config({"run": dict(allowed, **ignored)})
+            cxn = Connection("host", config=config)
+            cxn.shell()
+            kwargs = remote.run.call_args[1]
+            for key, value in allowed.items():
+                assert kwargs[key] == value
+            for key, value in ignored.items():
+                assert kwargs[key] == self.defaults[key]
 
     class local:
         # NOTE: most tests for this functionality live in Invoke's runner
@@ -1002,7 +1066,7 @@
             # Remote.return_value is two different Mocks now, despite Remote's
             # own Mock having the same ID here and in code under test. WTF!!)
             expected = [
-                call(cxn, inline_env=False),
+                call(context=cxn, inline_env=False),
                 call().run(cmd, watchers=ANY),
             ]
             assert Remote.mock_calls == expected
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/tests/init.py 
new/fabric-2.7.0/tests/init.py
--- old/fabric-2.6.0/tests/init.py      2020-01-10 22:20:59.000000000 +0100
+++ new/fabric-2.7.0/tests/init.py      2022-03-26 02:43:27.000000000 +0100
@@ -15,6 +15,9 @@
     def Remote(self):
         assert fabric.Remote is runners.Remote
 
+    def RemoteShell(self):
+        assert fabric.RemoteShell is runners.RemoteShell
+
     def Result(self):
         assert fabric.Result is runners.Result
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fabric-2.6.0/tests/runners.py 
new/fabric-2.7.0/tests/runners.py
--- old/fabric-2.6.0/tests/runners.py   2020-01-10 22:21:00.000000000 +0100
+++ new/fabric-2.7.0/tests/runners.py   2022-03-26 02:43:27.000000000 +0100
@@ -3,12 +3,12 @@
 except ImportError:
     from six import StringIO
 
-from mock import Mock
+from mock import Mock, patch
 from pytest import skip  # noqa
 
 from invoke import pty_size, Result
 
-from fabric import Config, Connection, Remote
+from fabric import Config, Connection, Remote, RemoteShell
 
 
 # On most systems this will explode if actually executed as a shell command;
@@ -30,6 +30,19 @@
         c = _Connection("host")
         assert Remote(context=c).context is c
 
+    class env:
+        def replaces_when_replace_env_True(self, remote):
+            env = _runner().run(CMD, env={"JUST": "ME"}, replace_env=True).env
+            assert env == {"JUST": "ME"}
+
+        def augments_when_replace_env_False(self, remote):
+            env = _runner().run(CMD, env={"JUST": "ME"}, replace_env=False).env
+            assert (
+                "PATH" in env
+            )  # assuming this will be in every test environment
+            assert "JUST" in env
+            assert env["JUST"] == "ME"
+
     class run:
         def calls_expected_paramiko_bits(self, remote):
             # remote mocking makes generic sanity checks like "were
@@ -44,12 +57,6 @@
             _runner().run(CMD, out_stream=fakeout)
             assert fakeout.getvalue() == "hello yes this is dog"
 
-        def pty_True_uses_paramiko_get_pty(self, remote):
-            chan = remote.expect()
-            _runner().run(CMD, pty=True)
-            cols, rows = pty_size()
-            chan.get_pty.assert_called_with(width=cols, height=rows)
-
         def return_value_is_Result_subclass_exposing_cxn_used(self, remote):
             c = _Connection("host")
             result = Remote(context=c).run(CMD)
@@ -107,6 +114,36 @@
             else:
                 assert False, "Weird, Oops never got raised..."
 
+        class pty_True:
+            def uses_paramiko_get_pty_with_local_size(self, remote):
+                chan = remote.expect()
+                _runner().run(CMD, pty=True)
+                cols, rows = pty_size()
+                chan.get_pty.assert_called_with(width=cols, height=rows)
+
+            @patch("fabric.runners.signal")
+            def no_SIGWINCH_means_no_handler(self, signal, remote):
+                delattr(signal, "SIGWINCH")
+                remote.expect()
+                _runner().run(CMD, pty=True)
+                assert not signal.signal.called
+
+            @patch("fabric.runners.signal")
+            def SIGWINCH_handled_when_present(self, signal, remote):
+                remote.expect()
+                runner = _runner()
+                runner.run(CMD, pty=True)
+                signal.signal.assert_called_once_with(
+                    signal.SIGWINCH, runner.handle_window_change
+                )
+
+            def window_change_handler_uses_resize_pty(self):
+                runner = _runner()
+                runner.channel = Mock()
+                runner.handle_window_change(None, None)
+                cols, rows = pty_size()
+                runner.channel.resize_pty.assert_called_once_with(cols, rows)
+
         # TODO: how much of Invoke's tests re: the upper level run() (re:
         # things like returning Result, behavior of Result, etc) to
         # duplicate here? Ideally none or very few core ones.
@@ -133,8 +170,22 @@
             r.run(CMD, env={"PATH": "/opt/bin", "DEBUG": "1"})
             assert not chan.update_environment.called
 
+    def send_start_message_sends_exec_command(self):
+        runner = Remote(context=None)
+        runner.channel = Mock()
+        runner.send_start_message(command="whatever")
+        runner.channel.exec_command.assert_called_once_with("whatever")
+
     def kill_closes_the_channel(self):
         runner = _runner()
         runner.channel = Mock()
         runner.kill()
         runner.channel.close.assert_called_once_with()
+
+
+class RemoteShell_:
+    def send_start_message_sends_invoke_shell(self):
+        runner = RemoteShell(context=None)
+        runner.channel = Mock()
+        runner.send_start_message(command=None)
+        runner.channel.invoke_shell.assert_called_once_with()

++++++ remove-mock.patch ++++++
Index: fabric-2.7.0/fabric/testing/base.py
===================================================================
--- fabric-2.7.0.orig/fabric/testing/base.py
+++ fabric-2.7.0/fabric/testing/base.py
@@ -20,7 +20,10 @@ from io import BytesIO
 import os
 
 try:
-    from mock import Mock, PropertyMock, call, patch, ANY
+    try:
+        from unittest.mock import Mock, PropertyMock, call, patch, ANY
+    except ImportError:
+        from mock import Mock, PropertyMock, call, patch, ANY
 except ImportError:
     import warnings
 
Index: fabric-2.7.0/fabric/testing/fixtures.py
===================================================================
--- fabric-2.7.0.orig/fabric/testing/fixtures.py
+++ fabric-2.7.0/fabric/testing/fixtures.py
@@ -17,7 +17,10 @@ For example, if you intend to use the `r
 
 try:
     from pytest import fixture
-    from mock import patch, Mock
+    try:
+        from unittest.mock import patch, Mock
+    except ImportError:
+        from mock import patch, Mock
 except ImportError:
     import warnings
 
Index: fabric-2.7.0/setup.py
===================================================================
--- fabric-2.7.0.orig/setup.py
+++ fabric-2.7.0/setup.py
@@ -77,8 +77,9 @@ setuptools.setup(
     },
     install_requires=["invoke>=1.3,<2.0", "paramiko>=2.4", "pathlib2"],
     extras_require={
-        "testing": testing_deps,
-        "pytest": testing_deps + pytest_deps,
+        "testing:python_version<='3.3'": testing_deps,
+        "pytest:python_version<='3.3'": testing_deps + pytest_deps,
+        "pytest:python_version>='3.4'": pytest_deps,
     },
     packages=packages,
     entry_points={
Index: fabric-2.7.0/tests/config.py
===================================================================
--- fabric-2.7.0.orig/tests/config.py
+++ fabric-2.7.0/tests/config.py
@@ -8,7 +8,10 @@ from lexicon import Lexicon
 from fabric import Config, Remote, RemoteShell
 from fabric.util import get_local_user
 
-from mock import patch, call
+try:
+    from unittest.mock import patch, call
+except ImportError:
+    from mock import patch, call
 
 from _util import support, faux_v1_env
 
Index: fabric-2.7.0/tests/conftest.py
===================================================================
--- fabric-2.7.0.orig/tests/conftest.py
+++ fabric-2.7.0/tests/conftest.py
@@ -5,7 +5,10 @@ from os.path import isfile, expanduser
 
 from pytest import fixture
 
-from mock import patch
+try:
+    from unittest.mock import patch
+except ImportError:
+    from mock import patch
 
 
 # TODO: does this want to end up in the public fixtures module too?
Index: fabric-2.7.0/tests/connection.py
===================================================================
--- fabric-2.7.0.orig/tests/connection.py
+++ fabric-2.7.0/tests/connection.py
@@ -10,7 +10,10 @@ from os.path import join
 import socket
 import time
 
-from mock import patch, Mock, call, ANY
+try:
+    from unittest.mock import patch, Mock, call, ANY
+except ImportError:
+    from mock import patch, Mock, call, ANY
 from paramiko.client import SSHClient, AutoAddPolicy
 from paramiko import SSHConfig
 import pytest  # for mark, internal raises
Index: fabric-2.7.0/tests/executor.py
===================================================================
--- fabric-2.7.0.orig/tests/executor.py
+++ fabric-2.7.0/tests/executor.py
@@ -4,7 +4,10 @@ from fabric import Executor, Task, Conne
 from fabric.executor import ConnectionCall
 from fabric.exceptions import NothingToDo
 
-from mock import Mock
+try:
+    from unittest.mock import Mock
+except ImportError:
+    from mock import Mock
 from pytest import skip, raises  # noqa
 
 
Index: fabric-2.7.0/tests/group.py
===================================================================
--- fabric-2.7.0.orig/tests/group.py
+++ fabric-2.7.0/tests/group.py
@@ -1,4 +1,7 @@
-from mock import Mock, patch, call
+try:
+    from unittest.mock import Mock, patch, call
+except ImportError:
+    from mock import Mock, patch, call
 from pytest import mark, raises
 
 from fabric import Connection, Group, SerialGroup, ThreadingGroup, GroupResult
Index: fabric-2.7.0/tests/main.py
===================================================================
--- fabric-2.7.0.orig/tests/main.py
+++ fabric-2.7.0/tests/main.py
@@ -8,7 +8,10 @@ import re
 
 from invoke import run
 from invoke.util import cd
-from mock import patch
+try:
+    from unittest.mock import patch
+except ImportError:
+    from mock import patch
 import pytest  # because WHY would you expose @skip normally? -_-
 from pytest_relaxed import raises
 
Index: fabric-2.7.0/tests/runners.py
===================================================================
--- fabric-2.7.0.orig/tests/runners.py
+++ fabric-2.7.0/tests/runners.py
@@ -3,7 +3,10 @@ try:
 except ImportError:
     from six import StringIO
 
-from mock import Mock, patch
+try:
+    from unittest.mock import Mock, patch
+except ImportError:
+    from mock import Mock, patch
 from pytest import skip  # noqa
 
 from invoke import pty_size, Result
Index: fabric-2.7.0/tests/task.py
===================================================================
--- fabric-2.7.0.orig/tests/task.py
+++ fabric-2.7.0/tests/task.py
@@ -1,6 +1,9 @@
 # NOTE: named task.py, not tasks.py, to avoid some occasional pytest weirdness
 
-from mock import Mock
+try:
+    from unittest.mock import Mock
+except ImportError:
+    from mock import Mock
 from pytest import skip  # noqa
 
 import fabric
Index: fabric-2.7.0/tests/transfer.py
===================================================================
--- fabric-2.7.0.orig/tests/transfer.py
+++ fabric-2.7.0/tests/transfer.py
@@ -3,7 +3,10 @@ try:
 except ImportError:
     from six import StringIO
 
-from mock import Mock, call, patch
+try:
+    from unittest.mock import Mock, call, patch
+except ImportError:
+    from mock import Mock, call, patch
 from pytest_relaxed import raises
 from pytest import skip  # noqa
 from paramiko import SFTPAttributes
Index: fabric-2.7.0/tests/util.py
===================================================================
--- fabric-2.7.0.orig/tests/util.py
+++ fabric-2.7.0/tests/util.py
@@ -2,7 +2,10 @@
 Tests testing the fabric.util module, not utils for the tests!
 """
 
-from mock import patch
+try:
+    from unittest.mock import patch
+except ImportError:
+    from mock import patch
 
 from fabric.util import get_local_user
 

Reply via email to