Browse Source

Revamp "borg" action to support REPOSITORY and ARCHIVE env vars instead of implicitly injecting repository/archive into the Borg command (#575).

Dan Helfman 1 year ago
parent
commit
bb6004fc4f
5 changed files with 139 additions and 136 deletions
  1. 5 1
      NEWS
  2. 11 14
      borgmatic/borg/borg.py
  3. 72 28
      docs/how-to/run-arbitrary-borg-commands.md
  4. 1 1
      setup.py
  5. 50 92
      tests/unit/borg/test_borg.py

+ 5 - 1
NEWS

@@ -1,4 +1,8 @@
-1.7.16.dev0
+1.8.0.dev0
+ * #575: BREAKING: For the "borgmatic borg" action, instead of implicitly injecting
+   repository/archive into the resulting Borg command-line, make repository and archive environment
+   variables available for explicit use in your commands. See the documentation for more
+   information: https://torsion.org/borgmatic/docs/how-to/run-arbitrary-borg-commands/
  * #719: Fix an error when running "borg key export" through borgmatic.
  * #719: Fix an error when running "borg key export" through borgmatic.
 
 
 1.7.15
 1.7.15

+ 11 - 14
borgmatic/borg/borg.py

@@ -10,7 +10,6 @@ logger = logging.getLogger(__name__)
 
 
 REPOSITORYLESS_BORG_COMMANDS = {'serve', None}
 REPOSITORYLESS_BORG_COMMANDS = {'serve', None}
 BORG_SUBCOMMANDS_WITH_SUBCOMMANDS = {'key', 'debug'}
 BORG_SUBCOMMANDS_WITH_SUBCOMMANDS = {'key', 'debug'}
-BORG_SUBCOMMANDS_WITHOUT_REPOSITORY = (('debug', 'info'), ('debug', 'convert-profile'), ())
 
 
 
 
 def run_arbitrary_borg(
 def run_arbitrary_borg(
@@ -25,7 +24,8 @@ def run_arbitrary_borg(
     '''
     '''
     Given a local or remote repository path, a storage config dict, the local Borg version, a
     Given a local or remote repository path, a storage config dict, the local Borg version, a
     sequence of arbitrary command-line Borg options, and an optional archive name, run an arbitrary
     sequence of arbitrary command-line Borg options, and an optional archive name, run an arbitrary
-    Borg command on the given repository/archive.
+    Borg command, passing in $REPOSITORY and $ARCHIVE environment variables for optional use in the
+    commmand.
     '''
     '''
     borgmatic.logger.add_custom_log_levels()
     borgmatic.logger.add_custom_log_levels()
     lock_wait = storage_config.get('lock_wait', None)
     lock_wait = storage_config.get('lock_wait', None)
@@ -46,29 +46,26 @@ def run_arbitrary_borg(
         borg_command = ()
         borg_command = ()
         command_options = ()
         command_options = ()
 
 
-    if borg_command in BORG_SUBCOMMANDS_WITHOUT_REPOSITORY:
-        repository_archive_flags = ()
-    elif archive:
-        repository_archive_flags = flags.make_repository_archive_flags(
-            repository_path, archive, local_borg_version
-        )
-    else:
-        repository_archive_flags = flags.make_repository_flags(repository_path, local_borg_version)
-
     full_command = (
     full_command = (
         (local_path,)
         (local_path,)
         + borg_command
         + borg_command
-        + repository_archive_flags
-        + command_options
         + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
         + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
         + (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
         + (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
         + flags.make_flags('remote-path', remote_path)
         + flags.make_flags('remote-path', remote_path)
         + flags.make_flags('lock-wait', lock_wait)
         + flags.make_flags('lock-wait', lock_wait)
+        + command_options
     )
     )
 
 
     return execute_command(
     return execute_command(
         full_command,
         full_command,
         output_file=DO_NOT_CAPTURE,
         output_file=DO_NOT_CAPTURE,
         borg_local_path=local_path,
         borg_local_path=local_path,
-        extra_environment=environment.make_environment(storage_config),
+        shell=True,
+        extra_environment=dict(
+            (environment.make_environment(storage_config) or {}),
+            **{
+                'REPOSITORY': repository_path,
+                'ARCHIVE': archive if archive else '',
+            },
+        ),
     )
     )

+ 72 - 28
docs/how-to/run-arbitrary-borg-commands.md

@@ -7,7 +7,7 @@ eleventyNavigation:
 ---
 ---
 ## Running Borg with borgmatic
 ## Running Borg with borgmatic
 
 
-Borg has several commands (and options) that borgmatic does not currently
+Borg has several commands and options that borgmatic does not currently
 support. Sometimes though, as a borgmatic user, you may find yourself wanting
 support. Sometimes though, as a borgmatic user, you may find yourself wanting
 to take advantage of these off-the-beaten-path Borg features. You could of
 to take advantage of these off-the-beaten-path Borg features. You could of
 course drop down to running Borg directly. But then you'd give up all the
 course drop down to running Borg directly. But then you'd give up all the
@@ -17,11 +17,11 @@ request](https://torsion.org/borgmatic/#contributing) to add the feature. But
 what if you need it *now*?
 what if you need it *now*?
 
 
 That's where borgmatic's support for running "arbitrary" Borg commands comes
 That's where borgmatic's support for running "arbitrary" Borg commands comes
-in. Running Borg commands with borgmatic takes advantage of the following, all
-based on your borgmatic configuration files or command-line arguments:
+in. Running these Borg commands with borgmatic can take advantage of the
+following, all based on your borgmatic configuration files or command-line
+arguments:
 
 
- * configured repositories (automatically runs your Borg command once for each
-   one)
+ * configured repositories, running your Borg command once for each one
  * local and remote Borg binary paths
  * local and remote Borg binary paths
  * SSH settings and Borg environment variables
  * SSH settings and Borg environment variables
  * lock wait settings
  * lock wait settings
@@ -34,36 +34,77 @@ based on your borgmatic configuration files or command-line arguments:
 you run Borg with borgmatic is via the `borg` action. Here's a simple example:
 you run Borg with borgmatic is via the `borg` action. Here's a simple example:
 
 
 ```bash
 ```bash
-borgmatic borg break-lock
+borgmatic borg break-lock '$REPOSITORY'
 ```
 ```
 
 
-(No `borg` action in borgmatic? Time to upgrade!)
-
 This runs Borg's `break-lock` command once on each configured borgmatic
 This runs Borg's `break-lock` command once on each configured borgmatic
-repository. Notice how the repository isn't present in the specified Borg
-options, as that part is provided by borgmatic.
+repository, passing the repository path in as an environment variable named
+`REPOSITORY`. The single quotes are necessary in order to pass in a literal
+`$REPOSITORY` string instead of trying to resolve it from borgmatic's shell
+where it's not yet set.
+
+<span class="minilink minilink-addedin">Prior to version 1.8.0</span>borgmatic
+provided the repository name implicitly, attempting to inject it into your
+Borg arguments in the right place (which didn't always work). So your
+command-line in these older versions looked more like:
+
+```bash
+borgmatic borg break-lock
+```
 
 
-You can also specify Borg options for relevant commands:
+You can also specify Borg options for relevant commands. In borgmatic 1.8.0+,
+that looks like:
 
 
 ```bash
 ```bash
-borgmatic borg rlist --short
+borgmatic borg rlist --short '$REPOSITORY'
 ```
 ```
 
 
 This runs Borg's `rlist` command once on each configured borgmatic repository.
 This runs Borg's `rlist` command once on each configured borgmatic repository.
-(The native `borgmatic rlist` action should be preferred for most use.)
+However, the native `borgmatic rlist` action should be preferred for most uses.
 
 
 What if you only want to run Borg on a single configured borgmatic repository
 What if you only want to run Borg on a single configured borgmatic repository
 when you've got several configured? Not a problem. The `--repository` argument
 when you've got several configured? Not a problem. The `--repository` argument
 lets you specify the repository to use, either by its path or its label:
 lets you specify the repository to use, either by its path or its label:
 
 
 ```bash
 ```bash
-borgmatic borg --repository repo.borg break-lock
+borgmatic borg --repository repo.borg break-lock '$REPOSITORY'
+```
+
+### Specifying an archive
+
+For borg commands that expect an archive name, you have a few approaches.
+Here's one:
+
+```bash
+borgmatic borg --archive latest list '$REPOSITORY::$ARCHIVE'
 ```
 ```
 
 
-And what about a single archive?
+Or if you don't need borgmatic to resolve an archive name like `latest`, you
+can just do:
+
+```bash
+borgmatic borg list '$REPOSITORY::your-actual-archive-name'
+```
+
+<span class="minilink minilink-addedin">Prior to version 1.8.0</span>borgmatic
+provided the archive name implicitly along with the repository, attempting to
+inject it into your Borg arguments in the right place (which didn't always
+work). So your command-line in these older versions of borgmatic looked more
+like:
+
+```bash
+borgmatic borg --archive latest list
+```
+
+<span class="minilink minilink-addedin">With Borg version 2.x</span> Either of
+these will list an archive:
+
+```bash
+borgmatic borg --archive latest list --repo '$REPOSITORY' '$ARCHIVE'
+```
 
 
 ```bash
 ```bash
-borgmatic borg --archive your-archive-name rlist
+borgmatic borg list --repo '$REPOSITORY' your-actual-archive-name
 ```
 ```
 
 
 ### Limitations
 ### Limitations
@@ -71,14 +112,10 @@ borgmatic borg --archive your-archive-name rlist
 borgmatic's `borg` action is not without limitations:
 borgmatic's `borg` action is not without limitations:
 
 
  * The Borg command you want to run (`create`, `list`, etc.) *must* come first
  * The Borg command you want to run (`create`, `list`, etc.) *must* come first
-   after the `borg` action. If you have any other Borg options to specify,
-   provide them after. For instance, `borgmatic borg list --progress` will work,
-   but `borgmatic borg --progress list` will not.
- * borgmatic supplies the repository/archive name to Borg for you (based on
-   your borgmatic configuration or the `borgmatic borg --repository`/`--archive`
-   arguments), so do not specify the repository/archive otherwise.
- * The `borg` action will not currently work for any Borg commands like `borg
-   serve` that do not accept a repository/archive name.
+   after the `borg` action (and any borgmatic-specific arguments). If you have
+   other Borg options to specify, provide them after. For instance,
+   `borgmatic borg list --progress ...` will work, but
+   `borgmatic borg --progress list ...` will not.
  * Do not specify any global borgmatic arguments to the right of the `borg`
  * Do not specify any global borgmatic arguments to the right of the `borg`
    action. (They will be passed to Borg instead of borgmatic.) If you have
    action. (They will be passed to Borg instead of borgmatic.) If you have
    global borgmatic arguments, specify them *before* the `borg` action.
    global borgmatic arguments, specify them *before* the `borg` action.
@@ -88,10 +125,17 @@ borgmatic's `borg` action is not without limitations:
    borgmatic action. In this case, only the Borg command is run.
    borgmatic action. In this case, only the Borg command is run.
  * Unlike normal borgmatic actions that support JSON, the `borg` action will
  * Unlike normal borgmatic actions that support JSON, the `borg` action will
    not disable certain borgmatic logs to avoid interfering with JSON output.
    not disable certain borgmatic logs to avoid interfering with JSON output.
- * Unlike other borgmatic actions, the `borg` action captures (and logs) all
-   output, so interactive prompts and flags like `--progress` will not work as
-   expected. <span class="minilink minilink-addedin">New in version
-   1.7.13</span> borgmatic now runs the `borg` action without capturing output,
+ * <span class="minilink minilink-addedin">Prior to version 1.8.0</span>
+   borgmatic implicitly supplied the repository/archive name to Borg for you
+   (based on your borgmatic configuration or the
+   `borgmatic borg --repository`/`--archive` arguments)—which meant you couldn't
+   specify the repository/archive directly in the Borg command. Also, in these
+   older versions of borgmatic, the `borg` action didn't work for any Borg
+   commands like `borg serve` that do not accept a repository/archive name.
+ * <span class="minilink minilink-addedin">Prior to version 1.7.13</span> Unlike
+   other borgmatic actions, the `borg` action captured (and logged) all output,
+   so interactive prompts and flags like `--progress` dit not work as expected.
+   In new versions, borgmatic runs the `borg` action without capturing output,
    so interactive prompts work.
    so interactive prompts work.
 
 
 In general, this `borgmatic borg` feature should be considered an escape
 In general, this `borgmatic borg` feature should be considered an escape

+ 1 - 1
setup.py

@@ -1,6 +1,6 @@
 from setuptools import find_packages, setup
 from setuptools import find_packages, setup
 
 
-VERSION = '1.7.16.dev0'
+VERSION = '1.8.0.dev0'
 
 
 
 
 setup(
 setup(

+ 50 - 92
tests/unit/borg/test_borg.py

@@ -10,35 +10,35 @@ from ..test_verbosity import insert_logging_mock
 def test_run_arbitrary_borg_calls_borg_with_flags():
 def test_run_arbitrary_borg_calls_borg_with_flags():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
-    flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
-        ('borg', 'break-lock', 'repo'),
+        ('borg', 'break-lock', '$REPOSITORY'),
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         borg_local_path='borg',
         borg_local_path='borg',
-        extra_environment=None,
+        shell=True,
+        extra_environment={'REPOSITORY': 'repo', 'ARCHIVE': ''},
     )
     )
 
 
     module.run_arbitrary_borg(
     module.run_arbitrary_borg(
         repository_path='repo',
         repository_path='repo',
         storage_config={},
         storage_config={},
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
-        options=['break-lock'],
+        options=['break-lock', '$REPOSITORY'],
     )
     )
 
 
 
 
 def test_run_arbitrary_borg_with_log_info_calls_borg_with_info_flag():
 def test_run_arbitrary_borg_with_log_info_calls_borg_with_info_flag():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
-    flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
-        ('borg', 'break-lock', 'repo', '--info'),
+        ('borg', 'break-lock', '--info', '$REPOSITORY'),
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         borg_local_path='borg',
         borg_local_path='borg',
-        extra_environment=None,
+        shell=True,
+        extra_environment={'REPOSITORY': 'repo', 'ARCHIVE': ''},
     )
     )
     insert_logging_mock(logging.INFO)
     insert_logging_mock(logging.INFO)
 
 
@@ -46,21 +46,21 @@ def test_run_arbitrary_borg_with_log_info_calls_borg_with_info_flag():
         repository_path='repo',
         repository_path='repo',
         storage_config={},
         storage_config={},
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
-        options=['break-lock'],
+        options=['break-lock', '$REPOSITORY'],
     )
     )
 
 
 
 
 def test_run_arbitrary_borg_with_log_debug_calls_borg_with_debug_flag():
 def test_run_arbitrary_borg_with_log_debug_calls_borg_with_debug_flag():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
-    flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
-        ('borg', 'break-lock', 'repo', '--debug', '--show-rc'),
+        ('borg', 'break-lock', '--debug', '--show-rc', '$REPOSITORY'),
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         borg_local_path='borg',
         borg_local_path='borg',
-        extra_environment=None,
+        shell=True,
+        extra_environment={'REPOSITORY': 'repo', 'ARCHIVE': ''},
     )
     )
     insert_logging_mock(logging.DEBUG)
     insert_logging_mock(logging.DEBUG)
 
 
@@ -68,7 +68,7 @@ def test_run_arbitrary_borg_with_log_debug_calls_borg_with_debug_flag():
         repository_path='repo',
         repository_path='repo',
         storage_config={},
         storage_config={},
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
-        options=['break-lock'],
+        options=['break-lock', '$REPOSITORY'],
     )
     )
 
 
 
 
@@ -76,46 +76,44 @@ def test_run_arbitrary_borg_with_lock_wait_calls_borg_with_lock_wait_flags():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     storage_config = {'lock_wait': 5}
     storage_config = {'lock_wait': 5}
-    flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_flags').and_return(()).and_return(
     flexmock(module.flags).should_receive('make_flags').and_return(()).and_return(
         ('--lock-wait', '5')
         ('--lock-wait', '5')
     )
     )
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
-        ('borg', 'break-lock', 'repo', '--lock-wait', '5'),
+        ('borg', 'break-lock', '--lock-wait', '5', '$REPOSITORY'),
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         borg_local_path='borg',
         borg_local_path='borg',
-        extra_environment=None,
+        shell=True,
+        extra_environment={'REPOSITORY': 'repo', 'ARCHIVE': ''},
     )
     )
 
 
     module.run_arbitrary_borg(
     module.run_arbitrary_borg(
         repository_path='repo',
         repository_path='repo',
         storage_config=storage_config,
         storage_config=storage_config,
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
-        options=['break-lock'],
+        options=['break-lock', '$REPOSITORY'],
     )
     )
 
 
 
 
 def test_run_arbitrary_borg_with_archive_calls_borg_with_archive_flag():
 def test_run_arbitrary_borg_with_archive_calls_borg_with_archive_flag():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
-    flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
-        ('repo::archive',)
-    )
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
-        ('borg', 'break-lock', 'repo::archive'),
+        ('borg', 'break-lock', '$REPOSITORY::$ARCHIVE'),
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         borg_local_path='borg',
         borg_local_path='borg',
-        extra_environment=None,
+        shell=True,
+        extra_environment={'REPOSITORY': 'repo', 'ARCHIVE': 'archive'},
     )
     )
 
 
     module.run_arbitrary_borg(
     module.run_arbitrary_borg(
         repository_path='repo',
         repository_path='repo',
         storage_config={},
         storage_config={},
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
-        options=['break-lock'],
+        options=['break-lock', '$REPOSITORY::$ARCHIVE'],
         archive='archive',
         archive='archive',
     )
     )
 
 
@@ -123,21 +121,21 @@ def test_run_arbitrary_borg_with_archive_calls_borg_with_archive_flag():
 def test_run_arbitrary_borg_with_local_path_calls_borg_via_local_path():
 def test_run_arbitrary_borg_with_local_path_calls_borg_via_local_path():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
-    flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
-        ('borg1', 'break-lock', 'repo'),
+        ('borg1', 'break-lock', '$REPOSITORY'),
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         borg_local_path='borg1',
         borg_local_path='borg1',
-        extra_environment=None,
+        shell=True,
+        extra_environment={'REPOSITORY': 'repo', 'ARCHIVE': ''},
     )
     )
 
 
     module.run_arbitrary_borg(
     module.run_arbitrary_borg(
         repository_path='repo',
         repository_path='repo',
         storage_config={},
         storage_config={},
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
-        options=['break-lock'],
+        options=['break-lock', '$REPOSITORY'],
         local_path='borg1',
         local_path='borg1',
     )
     )
 
 
@@ -145,23 +143,23 @@ def test_run_arbitrary_borg_with_local_path_calls_borg_via_local_path():
 def test_run_arbitrary_borg_with_remote_path_calls_borg_with_remote_path_flags():
 def test_run_arbitrary_borg_with_remote_path_calls_borg_with_remote_path_flags():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
-    flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_flags').and_return(
     flexmock(module.flags).should_receive('make_flags').and_return(
         ('--remote-path', 'borg1')
         ('--remote-path', 'borg1')
     ).and_return(())
     ).and_return(())
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
-        ('borg', 'break-lock', 'repo', '--remote-path', 'borg1'),
+        ('borg', 'break-lock', '--remote-path', 'borg1', '$REPOSITORY'),
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         borg_local_path='borg',
         borg_local_path='borg',
-        extra_environment=None,
+        shell=True,
+        extra_environment={'REPOSITORY': 'repo', 'ARCHIVE': ''},
     )
     )
 
 
     module.run_arbitrary_borg(
     module.run_arbitrary_borg(
         repository_path='repo',
         repository_path='repo',
         storage_config={},
         storage_config={},
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
-        options=['break-lock'],
+        options=['break-lock', '$REPOSITORY'],
         remote_path='borg1',
         remote_path='borg1',
     )
     )
 
 
@@ -169,56 +167,56 @@ def test_run_arbitrary_borg_with_remote_path_calls_borg_with_remote_path_flags()
 def test_run_arbitrary_borg_passes_borg_specific_flags_to_borg():
 def test_run_arbitrary_borg_passes_borg_specific_flags_to_borg():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
-    flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
-        ('borg', 'list', 'repo', '--progress'),
+        ('borg', 'list', '--progress', '$REPOSITORY'),
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         borg_local_path='borg',
         borg_local_path='borg',
-        extra_environment=None,
+        shell=True,
+        extra_environment={'REPOSITORY': 'repo', 'ARCHIVE': ''},
     )
     )
 
 
     module.run_arbitrary_borg(
     module.run_arbitrary_borg(
         repository_path='repo',
         repository_path='repo',
         storage_config={},
         storage_config={},
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
-        options=['list', '--progress'],
+        options=['list', '--progress', '$REPOSITORY'],
     )
     )
 
 
 
 
 def test_run_arbitrary_borg_omits_dash_dash_in_flags_passed_to_borg():
 def test_run_arbitrary_borg_omits_dash_dash_in_flags_passed_to_borg():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
-    flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
-        ('borg', 'break-lock', 'repo'),
+        ('borg', 'break-lock', '$REPOSITORY'),
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         borg_local_path='borg',
         borg_local_path='borg',
-        extra_environment=None,
+        shell=True,
+        extra_environment={'REPOSITORY': 'repo', 'ARCHIVE': ''},
     )
     )
 
 
     module.run_arbitrary_borg(
     module.run_arbitrary_borg(
         repository_path='repo',
         repository_path='repo',
         storage_config={},
         storage_config={},
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
-        options=['--', 'break-lock'],
+        options=['--', 'break-lock', '$REPOSITORY'],
     )
     )
 
 
 
 
 def test_run_arbitrary_borg_without_borg_specific_flags_does_not_raise():
 def test_run_arbitrary_borg_without_borg_specific_flags_does_not_raise():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
-    flexmock(module.flags).should_receive('make_repository_flags').never()
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
         ('borg',),
         ('borg',),
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         borg_local_path='borg',
         borg_local_path='borg',
-        extra_environment=None,
+        shell=True,
+        extra_environment={'REPOSITORY': 'repo', 'ARCHIVE': ''},
     )
     )
 
 
     module.run_arbitrary_borg(
     module.run_arbitrary_borg(
@@ -229,85 +227,45 @@ def test_run_arbitrary_borg_without_borg_specific_flags_does_not_raise():
     )
     )
 
 
 
 
-def test_run_arbitrary_borg_passes_key_sub_command_to_borg_before_repository():
-    flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
-    flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
-    flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
-    flexmock(module.flags).should_receive('make_flags').and_return(())
-    flexmock(module.environment).should_receive('make_environment')
-    flexmock(module).should_receive('execute_command').with_args(
-        ('borg', 'key', 'export', 'repo'),
-        output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
-        borg_local_path='borg',
-        extra_environment=None,
-    )
-
-    module.run_arbitrary_borg(
-        repository_path='repo',
-        storage_config={},
-        local_borg_version='1.2.3',
-        options=['key', 'export'],
-    )
-
-
-def test_run_arbitrary_borg_passes_debug_sub_command_to_borg_before_repository():
+def test_run_arbitrary_borg_passes_key_sub_command_to_borg_before_injected_flags():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
-    flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
-        ('borg', 'debug', 'dump-manifest', 'repo', 'path'),
+        ('borg', 'key', 'export', '--info', '$REPOSITORY'),
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         borg_local_path='borg',
         borg_local_path='borg',
-        extra_environment=None,
-    )
-
-    module.run_arbitrary_borg(
-        repository_path='repo',
-        storage_config={},
-        local_borg_version='1.2.3',
-        options=['debug', 'dump-manifest', 'path'],
-    )
-
-
-def test_run_arbitrary_borg_with_debug_info_command_does_not_pass_borg_repository():
-    flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
-    flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
-    flexmock(module.flags).should_receive('make_repository_flags').never()
-    flexmock(module.flags).should_receive('make_flags').and_return(())
-    flexmock(module.environment).should_receive('make_environment')
-    flexmock(module).should_receive('execute_command').with_args(
-        ('borg', 'debug', 'info'),
-        output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
-        borg_local_path='borg',
-        extra_environment=None,
+        shell=True,
+        extra_environment={'REPOSITORY': 'repo', 'ARCHIVE': ''},
     )
     )
+    insert_logging_mock(logging.INFO)
 
 
     module.run_arbitrary_borg(
     module.run_arbitrary_borg(
         repository_path='repo',
         repository_path='repo',
         storage_config={},
         storage_config={},
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
-        options=['debug', 'info'],
+        options=['key', 'export', '$REPOSITORY'],
     )
     )
 
 
 
 
-def test_run_arbitrary_borg_with_debug_convert_profile_command_does_not_pass_borg_repository():
+def test_run_arbitrary_borg_passes_debug_sub_command_to_borg_before_injected_flags():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
-    flexmock(module.flags).should_receive('make_repository_flags').never()
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
-        ('borg', 'debug', 'convert-profile', 'in', 'out'),
+        ('borg', 'debug', 'dump-manifest', '--info', '$REPOSITORY', 'path'),
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
         borg_local_path='borg',
         borg_local_path='borg',
-        extra_environment=None,
+        shell=True,
+        extra_environment={'REPOSITORY': 'repo', 'ARCHIVE': ''},
     )
     )
+    insert_logging_mock(logging.INFO)
 
 
     module.run_arbitrary_borg(
     module.run_arbitrary_borg(
         repository_path='repo',
         repository_path='repo',
         storage_config={},
         storage_config={},
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
-        options=['debug', 'convert-profile', 'in', 'out'],
+        options=['debug', 'dump-manifest', '$REPOSITORY', 'path'],
     )
     )