Przeglądaj źródła

Support for Borg 2's rcreate and rinfo sub-commands (#557).

Dan Helfman 2 lat temu
rodzic
commit
622caa0c21

+ 2 - 1
NEWS

@@ -1,4 +1,5 @@
-1.6.7.dev0
+2.0.0.dev0
+ * #557: Support for Borg 2 while still working with Borg 1.
  * #565: Fix handling of "repository" and "data" consistency checks to prevent invalid Borg flags.
  * #565: Fix handling of "repository" and "data" consistency checks to prevent invalid Borg flags.
  * #566: Modify "mount" and "extract" actions to require the "--repository" flag when multiple
  * #566: Modify "mount" and "extract" actions to require the "--repository" flag when multiple
    repositories are configured.
    repositories are configured.

+ 5 - 3
borgmatic/borg/check.py

@@ -5,7 +5,7 @@ import logging
 import os
 import os
 import pathlib
 import pathlib
 
 
-from borgmatic.borg import environment, extract, info, state
+from borgmatic.borg import environment, extract, rinfo, state
 from borgmatic.execute import DO_NOT_CAPTURE, execute_command
 from borgmatic.execute import DO_NOT_CAPTURE, execute_command
 
 
 DEFAULT_CHECKS = (
 DEFAULT_CHECKS = (
@@ -241,6 +241,7 @@ def check_archives(
     location_config,
     location_config,
     storage_config,
     storage_config,
     consistency_config,
     consistency_config,
+    local_borg_version,
     local_path='borg',
     local_path='borg',
     remote_path=None,
     remote_path=None,
     progress=None,
     progress=None,
@@ -260,10 +261,11 @@ def check_archives(
     '''
     '''
     try:
     try:
         borg_repository_id = json.loads(
         borg_repository_id = json.loads(
-            info.display_archives_info(
+            rinfo.display_repository_info(
                 repository,
                 repository,
                 storage_config,
                 storage_config,
-                argparse.Namespace(json=True, archive=None),
+                local_borg_version,
+                argparse.Namespace(json=True),
                 local_path,
                 local_path,
                 remote_path,
                 remote_path,
             )
             )

+ 8 - 0
borgmatic/borg/feature.py

@@ -9,6 +9,10 @@ class Feature(Enum):
     NOFLAGS = 3
     NOFLAGS = 3
     NUMERIC_IDS = 4
     NUMERIC_IDS = 4
     UPLOAD_RATELIMIT = 5
     UPLOAD_RATELIMIT = 5
+    SEPARATE_REPOSITORY_ARCHIVE = 6
+    RCREATE = 7
+    RLIST = 8
+    RINFO = 9
 
 
 
 
 FEATURE_TO_MINIMUM_BORG_VERSION = {
 FEATURE_TO_MINIMUM_BORG_VERSION = {
@@ -17,6 +21,10 @@ FEATURE_TO_MINIMUM_BORG_VERSION = {
     Feature.NOFLAGS: parse_version('1.2.0a8'),  # borg create --noflags
     Feature.NOFLAGS: parse_version('1.2.0a8'),  # borg create --noflags
     Feature.NUMERIC_IDS: parse_version('1.2.0b3'),  # borg create/extract/mount --numeric-ids
     Feature.NUMERIC_IDS: parse_version('1.2.0b3'),  # borg create/extract/mount --numeric-ids
     Feature.UPLOAD_RATELIMIT: parse_version('1.2.0b3'),  # borg create --upload-ratelimit
     Feature.UPLOAD_RATELIMIT: parse_version('1.2.0b3'),  # borg create --upload-ratelimit
+    Feature.SEPARATE_REPOSITORY_ARCHIVE: parse_version('2.0.0a2'),  # --repo with separate archive
+    Feature.RCREATE: parse_version('2.0.0a2'),  # borg rcreate
+    Feature.RLIST: parse_version('2.0.0a2'),  # borg rlist
+    Feature.RINFO: parse_version('2.0.0a2'),  # borg rinfo
 }
 }
 
 
 
 

+ 20 - 8
borgmatic/borg/info.py

@@ -1,6 +1,6 @@
 import logging
 import logging
 
 
-from borgmatic.borg import environment
+from borgmatic.borg import environment, feature
 from borgmatic.borg.flags import make_flags, make_flags_from_arguments
 from borgmatic.borg.flags import make_flags, make_flags_from_arguments
 from borgmatic.execute import execute_command
 from borgmatic.execute import execute_command
 
 
@@ -8,12 +8,17 @@ logger = logging.getLogger(__name__)
 
 
 
 
 def display_archives_info(
 def display_archives_info(
-    repository, storage_config, info_arguments, local_path='borg', remote_path=None
+    repository,
+    storage_config,
+    local_borg_version,
+    info_arguments,
+    local_path='borg',
+    remote_path=None,
 ):
 ):
     '''
     '''
-    Given a local or remote repository path, a storage config dict, and the arguments to the info
-    action, display summary information for Borg archives in the repository or return JSON summary
-    information.
+    Given a local or remote repository path, a storage config dict, the local Borg version, and the
+    arguments to the info action, display summary information for Borg archives in the repository or
+    return JSON summary information.
     '''
     '''
     lock_wait = storage_config.get('lock_wait', None)
     lock_wait = storage_config.get('lock_wait', None)
 
 
@@ -33,9 +38,16 @@ def display_archives_info(
         + make_flags('lock-wait', lock_wait)
         + make_flags('lock-wait', lock_wait)
         + make_flags_from_arguments(info_arguments, excludes=('repository', 'archive'))
         + make_flags_from_arguments(info_arguments, excludes=('repository', 'archive'))
         + (
         + (
-            '::'.join((repository, info_arguments.archive))
-            if info_arguments.archive
-            else repository,
+            (
+                ('--repo', repository)
+                + (('--glob-archives', info_arguments.archive) if info_arguments.archive else ())
+            )
+            if feature.available(feature.Feature.SEPARATE_REPOSITORY_ARCHIVE, local_borg_version)
+            else (
+                '::'.join((repository, info_arguments.archive))
+                if info_arguments.archive
+                else repository,
+            )
         )
         )
     )
     )
 
 

+ 26 - 14
borgmatic/borg/init.py → borgmatic/borg/rcreate.py

@@ -2,18 +2,19 @@ import argparse
 import logging
 import logging
 import subprocess
 import subprocess
 
 
-from borgmatic.borg import environment, info
+from borgmatic.borg import environment, feature, rinfo
 from borgmatic.execute import DO_NOT_CAPTURE, execute_command
 from borgmatic.execute import DO_NOT_CAPTURE, execute_command
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
-INFO_REPOSITORY_NOT_FOUND_EXIT_CODE = 2
+RINFO_REPOSITORY_NOT_FOUND_EXIT_CODE = 2
 
 
 
 
-def initialize_repository(
+def create_repository(
     repository,
     repository,
     storage_config,
     storage_config,
+    local_borg_version,
     encryption_mode,
     encryption_mode,
     append_only=None,
     append_only=None,
     storage_quota=None,
     storage_quota=None,
@@ -21,28 +22,34 @@ def initialize_repository(
     remote_path=None,
     remote_path=None,
 ):
 ):
     '''
     '''
-    Given a local or remote repository path, a storage configuration dict, a Borg encryption mode,
-    whether the repository should be append-only, and the storage quota to use, initialize the
-    repository. If the repository already exists, then log and skip initialization.
+    Given a local or remote repository path, a storage configuration dict, the local Borg version, a
+    Borg encryption mode, whether the repository should be append-only, and the storage quota to
+    use, create the repository. If the repository already exists, then log and skip creation.
     '''
     '''
     try:
     try:
-        info.display_archives_info(
+        rinfo.display_repository_info(
             repository,
             repository,
             storage_config,
             storage_config,
-            argparse.Namespace(json=True, archive=None),
+            local_borg_version,
+            argparse.Namespace(json=True),
             local_path,
             local_path,
             remote_path,
             remote_path,
         )
         )
-        logger.info('Repository already exists. Skipping initialization.')
+        logger.info('Repository already exists. Skipping creation.')
         return
         return
     except subprocess.CalledProcessError as error:
     except subprocess.CalledProcessError as error:
-        if error.returncode != INFO_REPOSITORY_NOT_FOUND_EXIT_CODE:
+        if error.returncode != RINFO_REPOSITORY_NOT_FOUND_EXIT_CODE:
             raise
             raise
 
 
-    extra_borg_options = storage_config.get('extra_borg_options', {}).get('init', '')
+    extra_borg_options = storage_config.get('extra_borg_options', {}).get('rcreate', '')
 
 
-    init_command = (
-        (local_path, 'init')
+    rcreate_command = (
+        (local_path,)
+        + (
+            ('rcreate',)
+            if feature.available(feature.Feature.RCREATE, local_borg_version)
+            else ('init',)
+        )
         + (('--encryption', encryption_mode) if encryption_mode else ())
         + (('--encryption', encryption_mode) if encryption_mode else ())
         + (('--append-only',) if append_only else ())
         + (('--append-only',) if append_only else ())
         + (('--storage-quota', storage_quota) if storage_quota else ())
         + (('--storage-quota', storage_quota) if storage_quota else ())
@@ -50,12 +57,17 @@ def initialize_repository(
         + (('--debug',) if logger.isEnabledFor(logging.DEBUG) else ())
         + (('--debug',) if logger.isEnabledFor(logging.DEBUG) else ())
         + (('--remote-path', remote_path) if remote_path else ())
         + (('--remote-path', remote_path) if remote_path else ())
         + (tuple(extra_borg_options.split(' ')) if extra_borg_options else ())
         + (tuple(extra_borg_options.split(' ')) if extra_borg_options else ())
+        + (
+            ('--repo',)
+            if feature.available(feature.Feature.SEPARATE_REPOSITORY_ARCHIVE, local_borg_version)
+            else ()
+        )
         + (repository,)
         + (repository,)
     )
     )
 
 
     # Do not capture output here, so as to support interactive prompts.
     # Do not capture output here, so as to support interactive prompts.
     execute_command(
     execute_command(
-        init_command,
+        rcreate_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),
         extra_environment=environment.make_environment(storage_config),

+ 22 - 15
borgmatic/commands/arguments.py

@@ -4,7 +4,7 @@ from argparse import Action, ArgumentParser
 from borgmatic.config import collect
 from borgmatic.config import collect
 
 
 SUBPARSER_ALIASES = {
 SUBPARSER_ALIASES = {
-    'init': ['--init', '-I'],
+    'rcreate': ['init', '--init', '-I'],
     'prune': ['--prune', '-p'],
     'prune': ['--prune', '-p'],
     'compact': [],
     'compact': [],
     'create': ['--create', '-C'],
     'create': ['--create', '-C'],
@@ -222,33 +222,35 @@ def make_parsers():
         metavar='',
         metavar='',
         help='Specify zero or more actions. Defaults to prune, compact, create, and check. Use --help with action for details:',
         help='Specify zero or more actions. Defaults to prune, compact, create, and check. Use --help with action for details:',
     )
     )
-    init_parser = subparsers.add_parser(
-        'init',
-        aliases=SUBPARSER_ALIASES['init'],
-        help='Initialize an empty Borg repository',
-        description='Initialize an empty Borg repository',
+    rcreate_parser = subparsers.add_parser(
+        'rcreate',
+        aliases=SUBPARSER_ALIASES['rcreate'],
+        help='Create a new, empty Borg repository',
+        description='Create a new, empty Borg repository',
         add_help=False,
         add_help=False,
     )
     )
-    init_group = init_parser.add_argument_group('init arguments')
-    init_group.add_argument(
+    rcreate_group = rcreate_parser.add_argument_group('rcreate arguments')
+    rcreate_group.add_argument(
         '-e',
         '-e',
         '--encryption',
         '--encryption',
         dest='encryption_mode',
         dest='encryption_mode',
         help='Borg repository encryption mode',
         help='Borg repository encryption mode',
         required=True,
         required=True,
     )
     )
-    init_group.add_argument(
+    rcreate_group.add_argument(
         '--append-only',
         '--append-only',
         dest='append_only',
         dest='append_only',
         action='store_true',
         action='store_true',
         help='Create an append-only repository',
         help='Create an append-only repository',
     )
     )
-    init_group.add_argument(
+    rcreate_group.add_argument(
         '--storage-quota',
         '--storage-quota',
         dest='storage_quota',
         dest='storage_quota',
         help='Create a repository with a fixed storage quota',
         help='Create a repository with a fixed storage quota',
     )
     )
-    init_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
+    rcreate_group.add_argument(
+        '-h', '--help', action='help', help='Show this help message and exit'
+    )
 
 
     prune_parser = subparsers.add_parser(
     prune_parser = subparsers.add_parser(
         'prune',
         'prune',
@@ -688,11 +690,11 @@ def parse_arguments(*unparsed_arguments):
 
 
     if arguments['global'].excludes_filename:
     if arguments['global'].excludes_filename:
         raise ValueError(
         raise ValueError(
-            'The --excludes option has been replaced with exclude_patterns in configuration'
+            'The --excludes flag has been replaced with exclude_patterns in configuration'
         )
         )
 
 
-    if 'init' in arguments and arguments['global'].dry_run:
-        raise ValueError('The init action cannot be used with the --dry-run option')
+    if 'rcreate' in arguments and arguments['global'].dry_run:
+        raise ValueError('The rcreate/init action cannot be used with the --dry-run flag')
 
 
     if (
     if (
         'list' in arguments
         'list' in arguments
@@ -700,6 +702,11 @@ def parse_arguments(*unparsed_arguments):
         and arguments['list'].json
         and arguments['list'].json
         and arguments['info'].json
         and arguments['info'].json
     ):
     ):
-        raise ValueError('With the --json option, list and info actions cannot be used together')
+        raise ValueError('With the --json flag, list and info actions cannot be used together')
+
+    if 'info' in arguments and arguments['info'].archive and arguments['info'].glob_archives:
+        raise ValueError(
+            'With the info action, the --archive and --glob-archives flags cannot be used together'
+        )
 
 
     return arguments
     return arguments

+ 10 - 7
borgmatic/commands/borgmatic.py

@@ -20,10 +20,10 @@ from borgmatic.borg import export_tar as borg_export_tar
 from borgmatic.borg import extract as borg_extract
 from borgmatic.borg import extract as borg_extract
 from borgmatic.borg import feature as borg_feature
 from borgmatic.borg import feature as borg_feature
 from borgmatic.borg import info as borg_info
 from borgmatic.borg import info as borg_info
-from borgmatic.borg import init as borg_init
 from borgmatic.borg import list as borg_list
 from borgmatic.borg import list as borg_list
 from borgmatic.borg import mount as borg_mount
 from borgmatic.borg import mount as borg_mount
 from borgmatic.borg import prune as borg_prune
 from borgmatic.borg import prune as borg_prune
+from borgmatic.borg import rcreate as borg_rcreate
 from borgmatic.borg import umount as borg_umount
 from borgmatic.borg import umount as borg_umount
 from borgmatic.borg import version as borg_version
 from borgmatic.borg import version as borg_version
 from borgmatic.commands.arguments import parse_arguments
 from borgmatic.commands.arguments import parse_arguments
@@ -249,14 +249,15 @@ def run_actions(
         'repositories': ','.join(location['repositories']),
         'repositories': ','.join(location['repositories']),
     }
     }
 
 
-    if 'init' in arguments:
-        logger.info('{}: Initializing repository'.format(repository))
-        borg_init.initialize_repository(
+    if 'rcreate' in arguments:
+        logger.info('{}: Creating repository'.format(repository))
+        borg_rcreate.create_repository(
             repository,
             repository,
             storage,
             storage,
-            arguments['init'].encryption_mode,
-            arguments['init'].append_only,
-            arguments['init'].storage_quota,
+            local_borg_version,
+            arguments['rcreate'].encryption_mode,
+            arguments['rcreate'].append_only,
+            arguments['rcreate'].storage_quota,
             local_path=local_path,
             local_path=local_path,
             remote_path=remote_path,
             remote_path=remote_path,
         )
         )
@@ -396,6 +397,7 @@ def run_actions(
             location,
             location,
             storage,
             storage,
             consistency,
             consistency,
+            local_borg_version,
             local_path=local_path,
             local_path=local_path,
             remote_path=remote_path,
             remote_path=remote_path,
             progress=arguments['check'].progress,
             progress=arguments['check'].progress,
@@ -624,6 +626,7 @@ def run_actions(
             json_output = borg_info.display_archives_info(
             json_output = borg_info.display_archives_info(
                 repository,
                 repository,
                 storage,
                 storage,
+                local_borg_version,
                 info_arguments=info_arguments,
                 info_arguments=info_arguments,
                 local_path=local_path,
                 local_path=local_path,
                 remote_path=remote_path,
                 remote_path=remote_path,

+ 3 - 4
docs/how-to/deal-with-very-large-backups.md

@@ -27,9 +27,6 @@ borgmatic create
 borgmatic check
 borgmatic check
 ```
 ```
 
 
-(No borgmatic `prune`, `create`, or `check` actions? Try the old-style
-`--prune`, `--create`, or `--check`. Or upgrade borgmatic!)
-
 You can run with only one of these actions provided, or you can mix and match
 You can run with only one of these actions provided, or you can mix and match
 any number of them in a single borgmatic run. This supports approaches like
 any number of them in a single borgmatic run. This supports approaches like
 skipping certain actions while running others. For instance, this skips
 skipping certain actions while running others. For instance, this skips
@@ -70,7 +67,9 @@ Here are the available checks from fastest to slowest:
  * `extract`: Performs an extraction dry-run of the most recent archive.
  * `extract`: Performs an extraction dry-run of the most recent archive.
  * `data`: Verifies the data integrity of all archives contents, decrypting and decompressing all data (implies `archives` as well).
  * `data`: Verifies the data integrity of all archives contents, decrypting and decompressing all data (implies `archives` as well).
 
 
-See [Borg's check documentation](https://borgbackup.readthedocs.io/en/stable/usage/check.html) for more information.
+See [Borg's check
+documentation](https://borgbackup.readthedocs.io/en/stable/usage/check.html)
+for more information.
 
 
 ### Check frequency
 ### Check frequency
 
 

+ 8 - 5
docs/how-to/inspect-your-backups.md

@@ -37,19 +37,22 @@ borgmatic --stats
 ## Existing backups
 ## Existing backups
 
 
 borgmatic provides convenient actions for Borg's
 borgmatic provides convenient actions for Borg's
-[list](https://borgbackup.readthedocs.io/en/stable/usage/list.html) and
-[info](https://borgbackup.readthedocs.io/en/stable/usage/info.html)
+[`list`](https://borgbackup.readthedocs.io/en/stable/usage/list.html) and
+[`info`](https://borgbackup.readthedocs.io/en/stable/usage/info.html)
 functionality:
 functionality:
 
 
-
 ```bash
 ```bash
 borgmatic list
 borgmatic list
 borgmatic info
 borgmatic info
 ```
 ```
 
 
-(No borgmatic `list` or `info` actions? Try the old-style `--list` or
-`--info`. Or upgrade borgmatic!)
+<span class="minilink minilink-addedin">New in borgmatic version 2.0.0</span>
+There's also an `rinfo` action for displaying repository information with Borg
+2.x:
 
 
+```bash
+borgmatic rinfo
+```
 
 
 ### Searching for a file
 ### Searching for a file
 
 

+ 17 - 13
docs/how-to/set-up-backups.md

@@ -186,32 +186,36 @@ files via configuration management, or you want to double check that your hand
 edits are valid.
 edits are valid.
 
 
 
 
-## Initialization
+## Repository creation
 
 
-Before you can create backups with borgmatic, you first need to initialize a
-Borg repository so you have a destination for your backup archives. (But skip
-this step if you already have a Borg repository.) To create a repository, run
-a command like the following:
+Before you can create backups with borgmatic, you first need to create a Borg
+repository so you have a destination for your backup archives. (But skip this
+step if you already have a Borg repository.) To create a repository, run a
+command like the following with Borg 1.x:
 
 
 ```bash
 ```bash
 sudo borgmatic init --encryption repokey
 sudo borgmatic init --encryption repokey
 ```
 ```
 
 
-(No borgmatic `init` action? Try the old-style `--init` flag, or upgrade
-borgmatic!)
+<span class="minilink minilink-addedin">New in borgmatic version 2.0.0</span>
+Or, with Borg 2.x:
+
+```bash
+sudo borgmatic rcreate --encryption repokey-aes-ocb
+```
 
 
 This uses the borgmatic configuration file you created above to determine
 This uses the borgmatic configuration file you created above to determine
 which local or remote repository to create, and encrypts it with the
 which local or remote repository to create, and encrypts it with the
 encryption passphrase specified there if one is provided. Read about [Borg
 encryption passphrase specified there if one is provided. Read about [Borg
 encryption
 encryption
-modes](https://borgbackup.readthedocs.io/en/stable/usage/init.html#encryption-modes)
+modes](https://borgbackup.readthedocs.io/en/stable/usage/init.html#encryption-mode-tldr)
 for the menu of available encryption modes.
 for the menu of available encryption modes.
 
 
 Also, optionally check out the [Borg Quick
 Also, optionally check out the [Borg Quick
 Start](https://borgbackup.readthedocs.org/en/stable/quickstart.html) for more
 Start](https://borgbackup.readthedocs.org/en/stable/quickstart.html) for more
-background about repository initialization.
+background about repository creation.
 
 
-Note that borgmatic skips repository initialization if the repository already
+Note that borgmatic skips repository creation if the repository already
 exists. This supports use cases like ensuring a repository exists prior to
 exists. This supports use cases like ensuring a repository exists prior to
 performing a backup.
 performing a backup.
 
 
@@ -221,8 +225,8 @@ key-based SSH access to the desired user account on the remote host.
 
 
 ## Backups
 ## Backups
 
 
-Now that you've configured borgmatic and initialized a repository, it's a
-good idea to test that borgmatic is working. So to run borgmatic and start a
+Now that you've configured borgmatic and created a repository, it's a good
+idea to test that borgmatic is working. So to run borgmatic and start a
 backup, you can invoke it like this:
 backup, you can invoke it like this:
 
 
 ```bash
 ```bash
@@ -230,7 +234,7 @@ sudo borgmatic create --verbosity 1 --files --stats
 ```
 ```
 
 
 (No borgmatic `--files` flag? It's only present in newer versions of
 (No borgmatic `--files` flag? It's only present in newer versions of
-borgmatic. So try leaving it out, or upgrade borgmatic!)
+borgmatic. So try leaving it out or upgrade borgmatic!)
 
 
 The `--verbosity` flag makes borgmatic show the steps it's performing. The
 The `--verbosity` flag makes borgmatic show the steps it's performing. The
 `--files` flag lists each file that's new or changed since the last backup.
 `--files` flag lists each file that's new or changed since the last backup.

+ 2 - 2
scripts/run-full-tests

@@ -14,8 +14,8 @@ apk add --no-cache python3 py3-pip borgbackup postgresql-client mariadb-client m
     py3-ruamel.yaml py3-ruamel.yaml.clib bash
     py3-ruamel.yaml py3-ruamel.yaml.clib bash
 # If certain dependencies of black are available in this version of Alpine, install them.
 # If certain dependencies of black are available in this version of Alpine, install them.
 apk add --no-cache py3-typed-ast py3-regex || true
 apk add --no-cache py3-typed-ast py3-regex || true
-python3 -m pip install --no-cache --upgrade pip==22.0.3 setuptools==60.8.1
-pip3 install tox==3.24.5
+python3 -m pip install --no-cache --upgrade pip==22.2.2 setuptools==64.0.1
+pip3 install --ignore-installed tox==3.25.1
 export COVERAGE_FILE=/tmp/.coverage
 export COVERAGE_FILE=/tmp/.coverage
 tox --workdir /tmp/.tox --sitepackages
 tox --workdir /tmp/.tox --sitepackages
 tox --workdir /tmp/.tox --sitepackages -e end-to-end
 tox --workdir /tmp/.tox --sitepackages -e end-to-end

+ 1 - 1
setup.py

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

+ 7 - 0
tests/integration/commands/test_arguments.py

@@ -496,6 +496,13 @@ def test_parse_arguments_disallows_json_with_both_list_and_info():
         module.parse_arguments('list', 'info', '--json')
         module.parse_arguments('list', 'info', '--json')
 
 
 
 
+def test_parse_arguments_disallows_info_with_both_archive_and_glob_archives():
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
+
+    with pytest.raises(ValueError):
+        module.parse_arguments('info', '--archive', 'foo', '--glob-archives', '*bar')
+
+
 def test_parse_arguments_check_only_extract_does_not_raise_extract_subparser_error():
 def test_parse_arguments_check_only_extract_does_not_raise_extract_subparser_error():
     flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
     flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
 
 

+ 28 - 14
tests/unit/borg/test_check.py

@@ -296,7 +296,7 @@ def test_check_archives_with_progress_calls_borg_with_progress_parameter():
     consistency_config = {'check_last': None}
     consistency_config = {'check_last': None}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
-    flexmock(module.info).should_receive('display_archives_info').and_return(
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return(
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').and_return(())
     flexmock(module).should_receive('make_check_flags').and_return(())
@@ -315,6 +315,7 @@ def test_check_archives_with_progress_calls_borg_with_progress_parameter():
         location_config={},
         location_config={},
         storage_config={},
         storage_config={},
         consistency_config=consistency_config,
         consistency_config=consistency_config,
+        local_borg_version='1.2.3',
         progress=True,
         progress=True,
     )
     )
 
 
@@ -324,7 +325,7 @@ def test_check_archives_with_repair_calls_borg_with_repair_parameter():
     consistency_config = {'check_last': None}
     consistency_config = {'check_last': None}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
-    flexmock(module.info).should_receive('display_archives_info').and_return(
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return(
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').and_return(())
     flexmock(module).should_receive('make_check_flags').and_return(())
@@ -343,6 +344,7 @@ def test_check_archives_with_repair_calls_borg_with_repair_parameter():
         location_config={},
         location_config={},
         storage_config={},
         storage_config={},
         consistency_config=consistency_config,
         consistency_config=consistency_config,
+        local_borg_version='1.2.3',
         repair=True,
         repair=True,
     )
     )
 
 
@@ -361,7 +363,7 @@ def test_check_archives_calls_borg_with_parameters(checks):
     consistency_config = {'check_last': check_last}
     consistency_config = {'check_last': check_last}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
-    flexmock(module.info).should_receive('display_archives_info').and_return(
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return(
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').with_args(
     flexmock(module).should_receive('make_check_flags').with_args(
@@ -376,6 +378,7 @@ def test_check_archives_calls_borg_with_parameters(checks):
         location_config={},
         location_config={},
         storage_config={},
         storage_config={},
         consistency_config=consistency_config,
         consistency_config=consistency_config,
+        local_borg_version='1.2.3',
     )
     )
 
 
 
 
@@ -385,7 +388,7 @@ def test_check_archives_with_json_error_raises():
     consistency_config = {'check_last': check_last}
     consistency_config = {'check_last': check_last}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
-    flexmock(module.info).should_receive('display_archives_info').and_return(
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return(
         '{"unexpected": {"id": "repo"}}'
         '{"unexpected": {"id": "repo"}}'
     )
     )
 
 
@@ -395,6 +398,7 @@ def test_check_archives_with_json_error_raises():
             location_config={},
             location_config={},
             storage_config={},
             storage_config={},
             consistency_config=consistency_config,
             consistency_config=consistency_config,
+            local_borg_version='1.2.3',
         )
         )
 
 
 
 
@@ -404,7 +408,7 @@ def test_check_archives_with_missing_json_keys_raises():
     consistency_config = {'check_last': check_last}
     consistency_config = {'check_last': check_last}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
-    flexmock(module.info).should_receive('display_archives_info').and_return('{invalid JSON')
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return('{invalid JSON')
 
 
     with pytest.raises(ValueError):
     with pytest.raises(ValueError):
         module.check_archives(
         module.check_archives(
@@ -412,6 +416,7 @@ def test_check_archives_with_missing_json_keys_raises():
             location_config={},
             location_config={},
             storage_config={},
             storage_config={},
             consistency_config=consistency_config,
             consistency_config=consistency_config,
+            local_borg_version='1.2.3',
         )
         )
 
 
 
 
@@ -421,7 +426,7 @@ def test_check_archives_with_extract_check_calls_extract_only():
     consistency_config = {'check_last': check_last}
     consistency_config = {'check_last': check_last}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
-    flexmock(module.info).should_receive('display_archives_info').and_return(
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return(
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').never()
     flexmock(module).should_receive('make_check_flags').never()
@@ -434,6 +439,7 @@ def test_check_archives_with_extract_check_calls_extract_only():
         location_config={},
         location_config={},
         storage_config={},
         storage_config={},
         consistency_config=consistency_config,
         consistency_config=consistency_config,
+        local_borg_version='1.2.3',
     )
     )
 
 
 
 
@@ -442,7 +448,7 @@ def test_check_archives_with_log_info_calls_borg_with_info_parameter():
     consistency_config = {'check_last': None}
     consistency_config = {'check_last': None}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
-    flexmock(module.info).should_receive('display_archives_info').and_return(
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return(
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').and_return(())
     flexmock(module).should_receive('make_check_flags').and_return(())
@@ -456,6 +462,7 @@ def test_check_archives_with_log_info_calls_borg_with_info_parameter():
         location_config={},
         location_config={},
         storage_config={},
         storage_config={},
         consistency_config=consistency_config,
         consistency_config=consistency_config,
+        local_borg_version='1.2.3',
     )
     )
 
 
 
 
@@ -464,7 +471,7 @@ def test_check_archives_with_log_debug_calls_borg_with_debug_parameter():
     consistency_config = {'check_last': None}
     consistency_config = {'check_last': None}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
-    flexmock(module.info).should_receive('display_archives_info').and_return(
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return(
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').and_return(())
     flexmock(module).should_receive('make_check_flags').and_return(())
@@ -478,6 +485,7 @@ def test_check_archives_with_log_debug_calls_borg_with_debug_parameter():
         location_config={},
         location_config={},
         storage_config={},
         storage_config={},
         consistency_config=consistency_config,
         consistency_config=consistency_config,
+        local_borg_version='1.2.3',
     )
     )
 
 
 
 
@@ -485,7 +493,7 @@ def test_check_archives_without_any_checks_bails():
     consistency_config = {'check_last': None}
     consistency_config = {'check_last': None}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(())
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(())
-    flexmock(module.info).should_receive('display_archives_info').and_return(
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return(
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     insert_execute_command_never()
     insert_execute_command_never()
@@ -495,6 +503,7 @@ def test_check_archives_without_any_checks_bails():
         location_config={},
         location_config={},
         storage_config={},
         storage_config={},
         consistency_config=consistency_config,
         consistency_config=consistency_config,
+        local_borg_version='1.2.3',
     )
     )
 
 
 
 
@@ -504,7 +513,7 @@ def test_check_archives_with_local_path_calls_borg_via_local_path():
     consistency_config = {'check_last': check_last}
     consistency_config = {'check_last': check_last}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
-    flexmock(module.info).should_receive('display_archives_info').and_return(
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return(
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').with_args(
     flexmock(module).should_receive('make_check_flags').with_args(
@@ -519,6 +528,7 @@ def test_check_archives_with_local_path_calls_borg_via_local_path():
         location_config={},
         location_config={},
         storage_config={},
         storage_config={},
         consistency_config=consistency_config,
         consistency_config=consistency_config,
+        local_borg_version='1.2.3',
         local_path='borg1',
         local_path='borg1',
     )
     )
 
 
@@ -529,7 +539,7 @@ def test_check_archives_with_remote_path_calls_borg_with_remote_path_parameters(
     consistency_config = {'check_last': check_last}
     consistency_config = {'check_last': check_last}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
-    flexmock(module.info).should_receive('display_archives_info').and_return(
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return(
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').with_args(
     flexmock(module).should_receive('make_check_flags').with_args(
@@ -544,6 +554,7 @@ def test_check_archives_with_remote_path_calls_borg_with_remote_path_parameters(
         location_config={},
         location_config={},
         storage_config={},
         storage_config={},
         consistency_config=consistency_config,
         consistency_config=consistency_config,
+        local_borg_version='1.2.3',
         remote_path='borg1',
         remote_path='borg1',
     )
     )
 
 
@@ -554,7 +565,7 @@ def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
     consistency_config = {'check_last': check_last}
     consistency_config = {'check_last': check_last}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
-    flexmock(module.info).should_receive('display_archives_info').and_return(
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return(
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').with_args(
     flexmock(module).should_receive('make_check_flags').with_args(
@@ -569,6 +580,7 @@ def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
         location_config={},
         location_config={},
         storage_config={'lock_wait': 5},
         storage_config={'lock_wait': 5},
         consistency_config=consistency_config,
         consistency_config=consistency_config,
+        local_borg_version='1.2.3',
     )
     )
 
 
 
 
@@ -579,7 +591,7 @@ def test_check_archives_with_retention_prefix():
     consistency_config = {'check_last': check_last, 'prefix': prefix}
     consistency_config = {'check_last': check_last, 'prefix': prefix}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
-    flexmock(module.info).should_receive('display_archives_info').and_return(
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return(
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').with_args(
     flexmock(module).should_receive('make_check_flags').with_args(
@@ -594,6 +606,7 @@ def test_check_archives_with_retention_prefix():
         location_config={},
         location_config={},
         storage_config={},
         storage_config={},
         consistency_config=consistency_config,
         consistency_config=consistency_config,
+        local_borg_version='1.2.3',
     )
     )
 
 
 
 
@@ -602,7 +615,7 @@ def test_check_archives_with_extra_borg_options_calls_borg_with_extra_options():
     consistency_config = {'check_last': None}
     consistency_config = {'check_last': None}
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('parse_checks')
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
-    flexmock(module.info).should_receive('display_archives_info').and_return(
+    flexmock(module.rinfo).should_receive('display_repository_info').and_return(
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').and_return(())
     flexmock(module).should_receive('make_check_flags').and_return(())
@@ -615,4 +628,5 @@ def test_check_archives_with_extra_borg_options_calls_borg_with_extra_options():
         location_config={},
         location_config={},
         storage_config={'extra_borg_options': {'check': '--extra --options'}},
         storage_config={'extra_borg_options': {'check': '--extra --options'}},
         consistency_config=consistency_config,
         consistency_config=consistency_config,
+        local_borg_version='1.2.3',
     )
     )

+ 89 - 17
tests/unit/borg/test_info.py

@@ -9,6 +9,25 @@ from ..test_verbosity import insert_logging_mock
 
 
 
 
 def test_display_archives_info_calls_borg_with_parameters():
 def test_display_archives_info_calls_borg_with_parameters():
+    flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.environment).should_receive('make_environment')
+    flexmock(module).should_receive('execute_command').with_args(
+        ('borg', 'info', '--repo', 'repo'),
+        output_log_level=logging.WARNING,
+        borg_local_path='borg',
+        extra_environment=None,
+    )
+
+    module.display_archives_info(
+        repository='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        info_arguments=flexmock(archive=None, json=False),
+    )
+
+
+def test_display_archives_info_without_borg_features_calls_borg_without_repo_flag():
+    flexmock(module.feature).should_receive('available').and_return(False)
     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', 'info', 'repo'),
         ('borg', 'info', 'repo'),
@@ -18,28 +37,36 @@ def test_display_archives_info_calls_borg_with_parameters():
     )
     )
 
 
     module.display_archives_info(
     module.display_archives_info(
-        repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=False)
+        repository='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        info_arguments=flexmock(archive=None, json=False),
     )
     )
 
 
 
 
 def test_display_archives_info_with_log_info_calls_borg_with_info_parameter():
 def test_display_archives_info_with_log_info_calls_borg_with_info_parameter():
+    flexmock(module.feature).should_receive('available').and_return(True)
     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', 'info', '--info', 'repo'),
+        ('borg', 'info', '--info', '--repo', 'repo'),
         output_log_level=logging.WARNING,
         output_log_level=logging.WARNING,
         borg_local_path='borg',
         borg_local_path='borg',
         extra_environment=None,
         extra_environment=None,
     )
     )
     insert_logging_mock(logging.INFO)
     insert_logging_mock(logging.INFO)
     module.display_archives_info(
     module.display_archives_info(
-        repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=False)
+        repository='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        info_arguments=flexmock(archive=None, json=False),
     )
     )
 
 
 
 
 def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_output():
 def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_output():
+    flexmock(module.feature).should_receive('available').and_return(True)
     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', 'info', '--json', 'repo'),
+        ('borg', 'info', '--json', '--repo', 'repo'),
         output_log_level=None,
         output_log_level=None,
         borg_local_path='borg',
         borg_local_path='borg',
         extra_environment=None,
         extra_environment=None,
@@ -47,16 +74,20 @@ def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_outpu
 
 
     insert_logging_mock(logging.INFO)
     insert_logging_mock(logging.INFO)
     json_output = module.display_archives_info(
     json_output = module.display_archives_info(
-        repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=True)
+        repository='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        info_arguments=flexmock(archive=None, json=True),
     )
     )
 
 
     assert json_output == '[]'
     assert json_output == '[]'
 
 
 
 
 def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter():
 def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter():
+    flexmock(module.feature).should_receive('available').and_return(True)
     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', 'info', '--debug', '--show-rc', 'repo'),
+        ('borg', 'info', '--debug', '--show-rc', '--repo', 'repo'),
         output_log_level=logging.WARNING,
         output_log_level=logging.WARNING,
         borg_local_path='borg',
         borg_local_path='borg',
         extra_environment=None,
         extra_environment=None,
@@ -64,14 +95,18 @@ def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter():
     insert_logging_mock(logging.DEBUG)
     insert_logging_mock(logging.DEBUG)
 
 
     module.display_archives_info(
     module.display_archives_info(
-        repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=False)
+        repository='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        info_arguments=flexmock(archive=None, json=False),
     )
     )
 
 
 
 
 def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_output():
 def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_output():
+    flexmock(module.feature).should_receive('available').and_return(True)
     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', 'info', '--json', 'repo'),
+        ('borg', 'info', '--json', '--repo', 'repo'),
         output_log_level=None,
         output_log_level=None,
         borg_local_path='borg',
         borg_local_path='borg',
         extra_environment=None,
         extra_environment=None,
@@ -79,29 +114,55 @@ def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_outp
 
 
     insert_logging_mock(logging.DEBUG)
     insert_logging_mock(logging.DEBUG)
     json_output = module.display_archives_info(
     json_output = module.display_archives_info(
-        repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=True)
+        repository='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        info_arguments=flexmock(archive=None, json=True),
     )
     )
 
 
     assert json_output == '[]'
     assert json_output == '[]'
 
 
 
 
 def test_display_archives_info_with_json_calls_borg_with_json_parameter():
 def test_display_archives_info_with_json_calls_borg_with_json_parameter():
+    flexmock(module.feature).should_receive('available').and_return(True)
     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', 'info', '--json', 'repo'),
+        ('borg', 'info', '--json', '--repo', 'repo'),
         output_log_level=None,
         output_log_level=None,
         borg_local_path='borg',
         borg_local_path='borg',
         extra_environment=None,
         extra_environment=None,
     ).and_return('[]')
     ).and_return('[]')
 
 
     json_output = module.display_archives_info(
     json_output = module.display_archives_info(
-        repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=True)
+        repository='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        info_arguments=flexmock(archive=None, json=True),
     )
     )
 
 
     assert json_output == '[]'
     assert json_output == '[]'
 
 
 
 
-def test_display_archives_info_with_archive_calls_borg_with_archive_parameter():
+def test_display_archives_info_with_archive_calls_borg_with_glob_archives_parameter():
+    flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.environment).should_receive('make_environment')
+    flexmock(module).should_receive('execute_command').with_args(
+        ('borg', 'info', '--repo', 'repo', '--glob-archives', 'archive'),
+        output_log_level=logging.WARNING,
+        borg_local_path='borg',
+        extra_environment=None,
+    )
+
+    module.display_archives_info(
+        repository='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        info_arguments=flexmock(archive='archive', json=False),
+    )
+
+
+def test_display_archives_info_with_archive_and_without_borg_features_calls_borg_with_repo_archive_parameter():
+    flexmock(module.feature).should_receive('available').and_return(False)
     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', 'info', 'repo::archive'),
         ('borg', 'info', 'repo::archive'),
@@ -111,14 +172,18 @@ def test_display_archives_info_with_archive_calls_borg_with_archive_parameter():
     )
     )
 
 
     module.display_archives_info(
     module.display_archives_info(
-        repository='repo', storage_config={}, info_arguments=flexmock(archive='archive', json=False)
+        repository='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        info_arguments=flexmock(archive='archive', json=False),
     )
     )
 
 
 
 
 def test_display_archives_info_with_local_path_calls_borg_via_local_path():
 def test_display_archives_info_with_local_path_calls_borg_via_local_path():
+    flexmock(module.feature).should_receive('available').and_return(True)
     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', 'info', 'repo'),
+        ('borg1', 'info', '--repo', 'repo'),
         output_log_level=logging.WARNING,
         output_log_level=logging.WARNING,
         borg_local_path='borg1',
         borg_local_path='borg1',
         extra_environment=None,
         extra_environment=None,
@@ -127,15 +192,17 @@ def test_display_archives_info_with_local_path_calls_borg_via_local_path():
     module.display_archives_info(
     module.display_archives_info(
         repository='repo',
         repository='repo',
         storage_config={},
         storage_config={},
+        local_borg_version='2.3.4',
         info_arguments=flexmock(archive=None, json=False),
         info_arguments=flexmock(archive=None, json=False),
         local_path='borg1',
         local_path='borg1',
     )
     )
 
 
 
 
 def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_parameters():
 def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_parameters():
+    flexmock(module.feature).should_receive('available').and_return(True)
     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', 'info', '--remote-path', 'borg1', 'repo'),
+        ('borg', 'info', '--remote-path', 'borg1', '--repo', 'repo'),
         output_log_level=logging.WARNING,
         output_log_level=logging.WARNING,
         borg_local_path='borg',
         borg_local_path='borg',
         extra_environment=None,
         extra_environment=None,
@@ -144,6 +211,7 @@ def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_para
     module.display_archives_info(
     module.display_archives_info(
         repository='repo',
         repository='repo',
         storage_config={},
         storage_config={},
+        local_borg_version='2.3.4',
         info_arguments=flexmock(archive=None, json=False),
         info_arguments=flexmock(archive=None, json=False),
         remote_path='borg1',
         remote_path='borg1',
     )
     )
@@ -151,9 +219,10 @@ def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_para
 
 
 def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_parameters():
 def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_parameters():
     storage_config = {'lock_wait': 5}
     storage_config = {'lock_wait': 5}
+    flexmock(module.feature).should_receive('available').and_return(True)
     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', 'info', '--lock-wait', '5', 'repo'),
+        ('borg', 'info', '--lock-wait', '5', '--repo', 'repo'),
         output_log_level=logging.WARNING,
         output_log_level=logging.WARNING,
         borg_local_path='borg',
         borg_local_path='borg',
         extra_environment=None,
         extra_environment=None,
@@ -162,15 +231,17 @@ def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_paramete
     module.display_archives_info(
     module.display_archives_info(
         repository='repo',
         repository='repo',
         storage_config=storage_config,
         storage_config=storage_config,
+        local_borg_version='2.3.4',
         info_arguments=flexmock(archive=None, json=False),
         info_arguments=flexmock(archive=None, json=False),
     )
     )
 
 
 
 
 @pytest.mark.parametrize('argument_name', ('prefix', 'glob_archives', 'sort_by', 'first', 'last'))
 @pytest.mark.parametrize('argument_name', ('prefix', 'glob_archives', 'sort_by', 'first', 'last'))
 def test_display_archives_info_passes_through_arguments_to_borg(argument_name):
 def test_display_archives_info_passes_through_arguments_to_borg(argument_name):
+    flexmock(module.feature).should_receive('available').and_return(True)
     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', 'info', '--' + argument_name.replace('_', '-'), 'value', 'repo'),
+        ('borg', 'info', '--' + argument_name.replace('_', '-'), 'value', '--repo', 'repo'),
         output_log_level=logging.WARNING,
         output_log_level=logging.WARNING,
         borg_local_path='borg',
         borg_local_path='borg',
         extra_environment=None,
         extra_environment=None,
@@ -179,5 +250,6 @@ def test_display_archives_info_passes_through_arguments_to_borg(argument_name):
     module.display_archives_info(
     module.display_archives_info(
         repository='repo',
         repository='repo',
         storage_config={},
         storage_config={},
+        local_borg_version='2.3.4',
         info_arguments=flexmock(archive=None, json=False, **{argument_name: 'value'}),
         info_arguments=flexmock(archive=None, json=False, **{argument_name: 'value'}),
     )
     )

+ 0 - 132
tests/unit/borg/test_init.py

@@ -1,132 +0,0 @@
-import logging
-import subprocess
-
-import pytest
-from flexmock import flexmock
-
-from borgmatic.borg import init as module
-
-from ..test_verbosity import insert_logging_mock
-
-INFO_SOME_UNKNOWN_EXIT_CODE = -999
-INIT_COMMAND = ('borg', 'init', '--encryption', 'repokey')
-
-
-def insert_info_command_found_mock():
-    flexmock(module.info).should_receive('display_archives_info')
-
-
-def insert_info_command_not_found_mock():
-    flexmock(module.info).should_receive('display_archives_info').and_raise(
-        subprocess.CalledProcessError(module.INFO_REPOSITORY_NOT_FOUND_EXIT_CODE, [])
-    )
-
-
-def insert_init_command_mock(init_command, **kwargs):
-    flexmock(module.environment).should_receive('make_environment')
-    flexmock(module).should_receive('execute_command').with_args(
-        init_command,
-        output_file=module.DO_NOT_CAPTURE,
-        borg_local_path=init_command[0],
-        extra_environment=None,
-    ).once()
-
-
-def test_initialize_repository_calls_borg_with_parameters():
-    insert_info_command_not_found_mock()
-    insert_init_command_mock(INIT_COMMAND + ('repo',))
-
-    module.initialize_repository(repository='repo', storage_config={}, encryption_mode='repokey')
-
-
-def test_initialize_repository_raises_for_borg_init_error():
-    insert_info_command_not_found_mock()
-    flexmock(module.environment).should_receive('make_environment')
-    flexmock(module).should_receive('execute_command').and_raise(
-        module.subprocess.CalledProcessError(2, 'borg init')
-    )
-
-    with pytest.raises(subprocess.CalledProcessError):
-        module.initialize_repository(
-            repository='repo', storage_config={}, encryption_mode='repokey'
-        )
-
-
-def test_initialize_repository_skips_initialization_when_repository_already_exists():
-    insert_info_command_found_mock()
-
-    module.initialize_repository(repository='repo', storage_config={}, encryption_mode='repokey')
-
-
-def test_initialize_repository_raises_for_unknown_info_command_error():
-    flexmock(module.info).should_receive('display_archives_info').and_raise(
-        subprocess.CalledProcessError(INFO_SOME_UNKNOWN_EXIT_CODE, [])
-    )
-
-    with pytest.raises(subprocess.CalledProcessError):
-        module.initialize_repository(
-            repository='repo', storage_config={}, encryption_mode='repokey'
-        )
-
-
-def test_initialize_repository_with_append_only_calls_borg_with_append_only_parameter():
-    insert_info_command_not_found_mock()
-    insert_init_command_mock(INIT_COMMAND + ('--append-only', 'repo'))
-
-    module.initialize_repository(
-        repository='repo', storage_config={}, encryption_mode='repokey', append_only=True
-    )
-
-
-def test_initialize_repository_with_storage_quota_calls_borg_with_storage_quota_parameter():
-    insert_info_command_not_found_mock()
-    insert_init_command_mock(INIT_COMMAND + ('--storage-quota', '5G', 'repo'))
-
-    module.initialize_repository(
-        repository='repo', storage_config={}, encryption_mode='repokey', storage_quota='5G'
-    )
-
-
-def test_initialize_repository_with_log_info_calls_borg_with_info_parameter():
-    insert_info_command_not_found_mock()
-    insert_init_command_mock(INIT_COMMAND + ('--info', 'repo'))
-    insert_logging_mock(logging.INFO)
-
-    module.initialize_repository(repository='repo', storage_config={}, encryption_mode='repokey')
-
-
-def test_initialize_repository_with_log_debug_calls_borg_with_debug_parameter():
-    insert_info_command_not_found_mock()
-    insert_init_command_mock(INIT_COMMAND + ('--debug', 'repo'))
-    insert_logging_mock(logging.DEBUG)
-
-    module.initialize_repository(repository='repo', storage_config={}, encryption_mode='repokey')
-
-
-def test_initialize_repository_with_local_path_calls_borg_via_local_path():
-    insert_info_command_not_found_mock()
-    insert_init_command_mock(('borg1',) + INIT_COMMAND[1:] + ('repo',))
-
-    module.initialize_repository(
-        repository='repo', storage_config={}, encryption_mode='repokey', local_path='borg1'
-    )
-
-
-def test_initialize_repository_with_remote_path_calls_borg_with_remote_path_parameter():
-    insert_info_command_not_found_mock()
-    insert_init_command_mock(INIT_COMMAND + ('--remote-path', 'borg1', 'repo'))
-
-    module.initialize_repository(
-        repository='repo', storage_config={}, encryption_mode='repokey', remote_path='borg1'
-    )
-
-
-def test_initialize_repository_with_extra_borg_options_calls_borg_with_extra_options():
-    insert_info_command_not_found_mock()
-    insert_init_command_mock(INIT_COMMAND + ('--extra', '--options', 'repo'))
-
-    module.initialize_repository(
-        repository='repo',
-        storage_config={'extra_borg_options': {'init': '--extra --options'}},
-        encryption_mode='repokey',
-    )

+ 183 - 0
tests/unit/borg/test_rcreate.py

@@ -0,0 +1,183 @@
+import logging
+import subprocess
+
+import pytest
+from flexmock import flexmock
+
+from borgmatic.borg import rcreate as module
+
+from ..test_verbosity import insert_logging_mock
+
+RINFO_SOME_UNKNOWN_EXIT_CODE = -999
+RCREATE_COMMAND = ('borg', 'rcreate', '--encryption', 'repokey')
+
+
+def insert_rinfo_command_found_mock():
+    flexmock(module.rinfo).should_receive('display_repository_info')
+
+
+def insert_rinfo_command_not_found_mock():
+    flexmock(module.rinfo).should_receive('display_repository_info').and_raise(
+        subprocess.CalledProcessError(module.RINFO_REPOSITORY_NOT_FOUND_EXIT_CODE, [])
+    )
+
+
+def insert_rcreate_command_mock(rcreate_command, **kwargs):
+    flexmock(module.environment).should_receive('make_environment')
+    flexmock(module).should_receive('execute_command').with_args(
+        rcreate_command,
+        output_file=module.DO_NOT_CAPTURE,
+        borg_local_path=rcreate_command[0],
+        extra_environment=None,
+    ).once()
+
+
+def test_create_repository_calls_borg_with_parameters():
+    insert_rinfo_command_not_found_mock()
+    insert_rcreate_command_mock(RCREATE_COMMAND + ('--repo', 'repo'))
+    flexmock(module.feature).should_receive('available').and_return(True)
+
+    module.create_repository(
+        repository='repo', storage_config={}, local_borg_version='2.3.4', encryption_mode='repokey'
+    )
+
+
+def test_create_repository_without_borg_features_calls_borg_with_init_sub_command():
+    insert_rinfo_command_not_found_mock()
+    insert_rcreate_command_mock(('borg', 'init', '--encryption', 'repokey', 'repo'))
+    flexmock(module.feature).should_receive('available').and_return(False)
+
+    module.create_repository(
+        repository='repo', storage_config={}, local_borg_version='2.3.4', encryption_mode='repokey'
+    )
+
+
+def test_create_repository_raises_for_borg_rcreate_error():
+    insert_rinfo_command_not_found_mock()
+    flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.environment).should_receive('make_environment')
+    flexmock(module).should_receive('execute_command').and_raise(
+        module.subprocess.CalledProcessError(2, 'borg rcreate')
+    )
+
+    with pytest.raises(subprocess.CalledProcessError):
+        module.create_repository(
+            repository='repo',
+            storage_config={},
+            local_borg_version='2.3.4',
+            encryption_mode='repokey',
+        )
+
+
+def test_create_repository_skips_creation_when_repository_already_exists():
+    insert_rinfo_command_found_mock()
+    flexmock(module.feature).should_receive('available').and_return(True)
+
+    module.create_repository(
+        repository='repo', storage_config={}, local_borg_version='2.3.4', encryption_mode='repokey'
+    )
+
+
+def test_create_repository_raises_for_unknown_rinfo_command_error():
+    flexmock(module.rinfo).should_receive('display_repository_info').and_raise(
+        subprocess.CalledProcessError(RINFO_SOME_UNKNOWN_EXIT_CODE, [])
+    )
+
+    with pytest.raises(subprocess.CalledProcessError):
+        module.create_repository(
+            repository='repo',
+            storage_config={},
+            local_borg_version='2.3.4',
+            encryption_mode='repokey',
+        )
+
+
+def test_create_repository_with_append_only_calls_borg_with_append_only_parameter():
+    insert_rinfo_command_not_found_mock()
+    insert_rcreate_command_mock(RCREATE_COMMAND + ('--append-only', '--repo', 'repo'))
+    flexmock(module.feature).should_receive('available').and_return(True)
+
+    module.create_repository(
+        repository='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        encryption_mode='repokey',
+        append_only=True,
+    )
+
+
+def test_create_repository_with_storage_quota_calls_borg_with_storage_quota_parameter():
+    insert_rinfo_command_not_found_mock()
+    insert_rcreate_command_mock(RCREATE_COMMAND + ('--storage-quota', '5G', '--repo', 'repo'))
+    flexmock(module.feature).should_receive('available').and_return(True)
+
+    module.create_repository(
+        repository='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        encryption_mode='repokey',
+        storage_quota='5G',
+    )
+
+
+def test_create_repository_with_log_info_calls_borg_with_info_parameter():
+    insert_rinfo_command_not_found_mock()
+    insert_rcreate_command_mock(RCREATE_COMMAND + ('--info', '--repo', 'repo'))
+    insert_logging_mock(logging.INFO)
+    flexmock(module.feature).should_receive('available').and_return(True)
+
+    module.create_repository(
+        repository='repo', storage_config={}, local_borg_version='2.3.4', encryption_mode='repokey'
+    )
+
+
+def test_create_repository_with_log_debug_calls_borg_with_debug_parameter():
+    insert_rinfo_command_not_found_mock()
+    insert_rcreate_command_mock(RCREATE_COMMAND + ('--debug', '--repo', 'repo'))
+    insert_logging_mock(logging.DEBUG)
+    flexmock(module.feature).should_receive('available').and_return(True)
+
+    module.create_repository(
+        repository='repo', storage_config={}, local_borg_version='2.3.4', encryption_mode='repokey'
+    )
+
+
+def test_create_repository_with_local_path_calls_borg_via_local_path():
+    insert_rinfo_command_not_found_mock()
+    insert_rcreate_command_mock(('borg1',) + RCREATE_COMMAND[1:] + ('--repo', 'repo'))
+    flexmock(module.feature).should_receive('available').and_return(True)
+
+    module.create_repository(
+        repository='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        encryption_mode='repokey',
+        local_path='borg1',
+    )
+
+
+def test_create_repository_with_remote_path_calls_borg_with_remote_path_parameter():
+    insert_rinfo_command_not_found_mock()
+    insert_rcreate_command_mock(RCREATE_COMMAND + ('--remote-path', 'borg1', '--repo', 'repo'))
+    flexmock(module.feature).should_receive('available').and_return(True)
+
+    module.create_repository(
+        repository='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        encryption_mode='repokey',
+        remote_path='borg1',
+    )
+
+
+def test_create_repository_with_extra_borg_options_calls_borg_with_extra_options():
+    insert_rinfo_command_not_found_mock()
+    insert_rcreate_command_mock(RCREATE_COMMAND + ('--extra', '--options', '--repo', 'repo'))
+    flexmock(module.feature).should_receive('available').and_return(True)
+
+    module.create_repository(
+        repository='repo',
+        storage_config={'extra_borg_options': {'rcreate': '--extra --options'}},
+        local_borg_version='2.3.4',
+        encryption_mode='repokey',
+    )

+ 3 - 3
tests/unit/commands/test_borgmatic.py

@@ -340,11 +340,11 @@ def test_run_configuration_retries_timeout_multiple_repos():
     assert results == error_logs
     assert results == error_logs
 
 
 
 
-def test_run_actions_does_not_raise_for_init_action():
-    flexmock(module.borg_init).should_receive('initialize_repository')
+def test_run_actions_does_not_raise_for_rcreate_action():
+    flexmock(module.borg_rcreate).should_receive('create_repository')
     arguments = {
     arguments = {
         'global': flexmock(monitoring_verbosity=1, dry_run=False),
         'global': flexmock(monitoring_verbosity=1, dry_run=False),
-        'init': flexmock(
+        'rcreate': flexmock(
             encryption_mode=flexmock(), append_only=flexmock(), storage_quota=flexmock()
             encryption_mode=flexmock(), append_only=flexmock(), storage_quota=flexmock()
         ),
         ),
     }
     }