Repository: borgbackup/borg

Issue:
Add --rsh option for setting BORG_RSH from the command line



Discussion:

Resolving patch:
diff --git a/src/borg/archiver.py b/src/borg/archiver.py
--- a/src/borg/archiver.py
+++ b/src/borg/archiver.py
@@ -2474,6 +2474,8 @@ def define_common_options(add_common_option):
             add_common_option('--debug-profile', metavar='FILE', dest='debug_profile', default=None,
                               help='Write execution profile in Borg format into FILE. For local use a Python-'
                                    'compatible file can be generated by suffixing FILE with ".pyprof".')
+            add_common_option('--rsh', metavar='RSH', dest='rsh',
+                              help="Use this command to connect to the 'borg serve' process (default: 'ssh')")
 
         def define_exclude_and_patterns(add_option, *, tag_files=False, strip_components=False):
             add_option('-e', '--exclude', metavar='PATTERN', dest='patterns',
diff --git a/src/borg/remote.py b/src/borg/remote.py
--- a/src/borg/remote.py
+++ b/src/borg/remote.py
@@ -540,6 +540,7 @@ def __init__(self, location, create=False, exclusive=False, lock_wait=None, lock
         self.unpacker = get_limited_unpacker('client')
         self.server_version = parse_version('1.0.8')  # fallback version if server is too old to send version information
         self.p = None
+        self._args = args
         testing = location.host == '__testsuite__'
         # when testing, we invoke and talk to a borg process directly (no ssh).
         # when not testing, we invoke the system-installed ssh binary to talk to a remote borg.
@@ -685,7 +686,8 @@ def borg_cmd(self, args, testing):
 
     def ssh_cmd(self, location):
         """return a ssh command line that can be prefixed to a borg command line"""
-        args = shlex.split(os.environ.get('BORG_RSH', 'ssh'))
+        rsh = self._args.rsh or os.environ.get('BORG_RSH', 'ssh')
+        args = shlex.split(rsh)
         if location.port:
             args += ['-p', str(location.port)]
         if location.user:
diff --git a/src/borg/testsuite/repository.py b/src/borg/testsuite/repository.py
--- a/src/borg/testsuite/repository.py
+++ b/src/borg/testsuite/repository.py
@@ -799,6 +799,19 @@ def open(self, create=False):
         return RemoteRepository(Location('__testsuite__:' + os.path.join(self.tmppath, 'repository')),
                                 exclusive=True, create=create)
 
+    def _get_mock_args(self):
+        class MockArgs:
+            remote_path = 'borg'
+            umask = 0o077
+            debug_topics = []
+            rsh = None
+
+            def __contains__(self, item):
+                # To behave like argparse.Namespace
+                return hasattr(self, item)
+
+        return MockArgs()
+
     def test_invalid_rpc(self):
         self.assert_raises(InvalidRPCMethod, lambda: self.repository.call('__init__', {}))
 
@@ -857,6 +870,8 @@ def test_rpc_exception_transport(self):
             assert len(e.exception_full) > 0
 
     def test_ssh_cmd(self):
+        args = self._get_mock_args()
+        self.repository._args = args
         assert self.repository.ssh_cmd(Location('example.com:foo')) == ['ssh', 'example.com']
         assert self.repository.ssh_cmd(Location('ssh://example.com/foo')) == ['ssh', 'example.com']
         assert self.repository.ssh_cmd(Location('ssh://user@example.com/foo')) == ['ssh', 'user@example.com']
@@ -865,17 +880,8 @@ def test_ssh_cmd(self):
         assert self.repository.ssh_cmd(Location('example.com:foo')) == ['ssh', '--foo', 'example.com']
 
     def test_borg_cmd(self):
-        class MockArgs:
-            remote_path = 'borg'
-            umask = 0o077
-            debug_topics = []
-
-            def __contains__(self, item):
-                # To behave like argparse.Namespace
-                return hasattr(self, item)
-
         assert self.repository.borg_cmd(None, testing=True) == [sys.executable, '-m', 'borg.archiver', 'serve']
-        args = MockArgs()
+        args = self._get_mock_args()
         # XXX without next line we get spurious test fails when using pytest-xdist, root cause unknown:
         logging.getLogger().setLevel(logging.INFO)
         # note: test logger is on info log level, so --info gets added automagically
@@ -885,12 +891,15 @@ def __contains__(self, item):
         args.debug_topics = ['something_client_side', 'repository_compaction']
         assert self.repository.borg_cmd(args, testing=False) == ['borg-0.28.2', 'serve', '--umask=077', '--info',
                                                                  '--debug-topic=borg.debug.repository_compaction']
-        args = MockArgs()
+        args = self._get_mock_args()
         args.storage_quota = 0
         assert self.repository.borg_cmd(args, testing=False) == ['borg', 'serve', '--umask=077', '--info']
         args.storage_quota = 314159265
         assert self.repository.borg_cmd(args, testing=False) == ['borg', 'serve', '--umask=077', '--info',
                                                                  '--storage-quota=314159265']
+        args.rsh = 'ssh -i foo'
+        self.repository._args = args
+        assert self.repository.ssh_cmd(Location('example.com:foo')) == ['ssh', '-i', 'foo', 'example.com']
 
 
 class RemoteLegacyFree(RepositoryTestCaseBase):


Test patch: