فهرست منبع

Automatically use the "archive_name_format" option to filter which archives get used for borgmatic actions that operate on multiple archives (#479).

Dan Helfman 2 سال پیش
والد
کامیت
3f78ac4085

+ 5 - 0
NEWS

@@ -1,4 +1,9 @@
 1.7.11.dev0
 1.7.11.dev0
+ * #479: Automatically use the "archive_name_format" option to filter which archives get used for
+   borgmatic actions that operate on multiple archives. See the documentation for more information:
+   https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/#archive-naming
+ * #479: The "prefix" options have been deprecated in favor of the new "archive_name_format"
+   auto-matching behavior (see above).
  * #662: Fix regression in which "check_repositories" option failed to match repositories.
  * #662: Fix regression in which "check_repositories" option failed to match repositories.
  * #663: Fix regression in which the "transfer" action produced a traceback.
  * #663: Fix regression in which the "transfer" action produced a traceback.
 
 

+ 19 - 10
borgmatic/borg/check.py

@@ -12,7 +12,6 @@ DEFAULT_CHECKS = (
     {'name': 'repository', 'frequency': '1 month'},
     {'name': 'repository', 'frequency': '1 month'},
     {'name': 'archives', 'frequency': '1 month'},
     {'name': 'archives', 'frequency': '1 month'},
 )
 )
-DEFAULT_PREFIX = '{hostname}-'  # noqa: FS003
 
 
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
@@ -146,9 +145,10 @@ def filter_checks_on_frequency(
     return tuple(filtered_checks)
     return tuple(filtered_checks)
 
 
 
 
-def make_check_flags(local_borg_version, checks, check_last=None, prefix=None):
+def make_check_flags(local_borg_version, storage_config, checks, check_last=None, prefix=None):
     '''
     '''
-    Given the local Borg version and a parsed sequence of checks, transform the checks into tuple of
+    Given the local Borg version, a storge configuration dict, a parsed sequence of checks, the
+    check last value, and a consistency check prefix, transform the checks into tuple of
     command-line flags.
     command-line flags.
 
 
     For example, given parsed checks of:
     For example, given parsed checks of:
@@ -174,10 +174,19 @@ def make_check_flags(local_borg_version, checks, check_last=None, prefix=None):
 
 
     if 'archives' in checks:
     if 'archives' in checks:
         last_flags = ('--last', str(check_last)) if check_last else ()
         last_flags = ('--last', str(check_last)) if check_last else ()
-        if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version):
-            match_archives_flags = ('--match-archives', f'sh:{prefix}*') if prefix else ()
-        else:
-            match_archives_flags = ('--glob-archives', f'{prefix}*') if prefix else ()
+        match_archives_flags = (
+            (
+                ('--match-archives', f'sh:{prefix}*')
+                if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version)
+                else ('--glob-archives', f'{prefix}*')
+            )
+            if prefix
+            else (
+                flags.make_match_archives_flags(
+                    storage_config.get('archive_name_format'), local_borg_version
+                )
+            )
+        )
     else:
     else:
         last_flags = ()
         last_flags = ()
         match_archives_flags = ()
         match_archives_flags = ()
@@ -291,7 +300,7 @@ def check_archives(
     extra_borg_options = storage_config.get('extra_borg_options', {}).get('check', '')
     extra_borg_options = storage_config.get('extra_borg_options', {}).get('check', '')
 
 
     if set(checks).intersection({'repository', 'archives', 'data'}):
     if set(checks).intersection({'repository', 'archives', 'data'}):
-        lock_wait = storage_config.get('lock_wait', None)
+        lock_wait = storage_config.get('lock_wait')
 
 
         verbosity_flags = ()
         verbosity_flags = ()
         if logger.isEnabledFor(logging.INFO):
         if logger.isEnabledFor(logging.INFO):
@@ -299,12 +308,12 @@ def check_archives(
         if logger.isEnabledFor(logging.DEBUG):
         if logger.isEnabledFor(logging.DEBUG):
             verbosity_flags = ('--debug', '--show-rc')
             verbosity_flags = ('--debug', '--show-rc')
 
 
-        prefix = consistency_config.get('prefix', DEFAULT_PREFIX)
+        prefix = consistency_config.get('prefix')
 
 
         full_command = (
         full_command = (
             (local_path, 'check')
             (local_path, 'check')
             + (('--repair',) if repair else ())
             + (('--repair',) if repair else ())
-            + make_check_flags(local_borg_version, checks, check_last, prefix)
+            + make_check_flags(local_borg_version, storage_config, checks, check_last, prefix)
             + (('--remote-path', remote_path) if remote_path else ())
             + (('--remote-path', remote_path) if remote_path else ())
             + (('--lock-wait', str(lock_wait)) if lock_wait else ())
             + (('--lock-wait', str(lock_wait)) if lock_wait else ())
             + verbosity_flags
             + verbosity_flags

+ 18 - 0
borgmatic/borg/flags.py

@@ -1,4 +1,5 @@
 import itertools
 import itertools
+import re
 
 
 from borgmatic.borg import feature
 from borgmatic.borg import feature
 
 
@@ -56,3 +57,20 @@ def make_repository_archive_flags(repository_path, archive, local_borg_version):
         if feature.available(feature.Feature.SEPARATE_REPOSITORY_ARCHIVE, local_borg_version)
         if feature.available(feature.Feature.SEPARATE_REPOSITORY_ARCHIVE, local_borg_version)
         else (f'{repository_path}::{archive}',)
         else (f'{repository_path}::{archive}',)
     )
     )
+
+
+def make_match_archives_flags(archive_name_format, local_borg_version):
+    '''
+    Return the match archives flags that would match archives created with the given archive name
+    format (if any). This is done by replacing certain archive name format placeholders for
+    ephemeral data (like "{now}") with globs.
+    '''
+    if not archive_name_format:
+        return ()
+
+    match_archives = re.sub(r'\{(now|utcnow|pid)([:%\w\.-]*)\}', '*', archive_name_format)
+
+    if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version):
+        return ('--match-archives', f'sh:{match_archives}')
+    else:
+        return ('--glob-archives', f'{match_archives}')

+ 5 - 1
borgmatic/borg/info.py

@@ -44,7 +44,11 @@ def display_archives_info(
                 else flags.make_flags('glob-archives', f'{info_arguments.prefix}*')
                 else flags.make_flags('glob-archives', f'{info_arguments.prefix}*')
             )
             )
             if info_arguments.prefix
             if info_arguments.prefix
-            else ()
+            else (
+                flags.make_match_archives_flags(
+                    storage_config.get('archive_name_format'), local_borg_version
+                )
+            )
         )
         )
         + flags.make_flags_from_arguments(
         + flags.make_flags_from_arguments(
             info_arguments, excludes=('repository', 'archive', 'prefix')
             info_arguments, excludes=('repository', 'archive', 'prefix')

+ 12 - 10
borgmatic/borg/prune.py

@@ -7,10 +7,10 @@ from borgmatic.execute import execute_command
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
-def make_prune_flags(retention_config, local_borg_version):
+def make_prune_flags(storage_config, retention_config, local_borg_version):
     '''
     '''
-    Given a retention config dict mapping from option name to value, tranform it into an iterable of
-    command-line name-value flag pairs.
+    Given a retention config dict mapping from option name to value, tranform it into an sequence of
+    command-line flags.
 
 
     For example, given a retention config of:
     For example, given a retention config of:
 
 
@@ -24,7 +24,7 @@ def make_prune_flags(retention_config, local_borg_version):
         )
         )
     '''
     '''
     config = retention_config.copy()
     config = retention_config.copy()
-    prefix = config.pop('prefix', '{hostname}-')  # noqa: FS003
+    prefix = config.pop('prefix', None)
 
 
     if prefix:
     if prefix:
         if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version):
         if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version):
@@ -32,10 +32,16 @@ def make_prune_flags(retention_config, local_borg_version):
         else:
         else:
             config['glob_archives'] = f'{prefix}*'
             config['glob_archives'] = f'{prefix}*'
 
 
-    return (
+    flag_pairs = (
         ('--' + option_name.replace('_', '-'), str(value)) for option_name, value in config.items()
         ('--' + option_name.replace('_', '-'), str(value)) for option_name, value in config.items()
     )
     )
 
 
+    return tuple(
+        element for pair in flag_pairs for element in pair
+    ) + flags.make_match_archives_flags(
+        storage_config.get('archive_name_format'), local_borg_version
+    )
+
 
 
 def prune_archives(
 def prune_archives(
     dry_run,
     dry_run,
@@ -60,11 +66,7 @@ def prune_archives(
 
 
     full_command = (
     full_command = (
         (local_path, 'prune')
         (local_path, 'prune')
-        + tuple(
-            element
-            for pair in make_prune_flags(retention_config, local_borg_version)
-            for element in pair
-        )
+        + make_prune_flags(storage_config, retention_config, local_borg_version)
         + (('--remote-path', remote_path) if remote_path else ())
         + (('--remote-path', remote_path) if remote_path else ())
         + (('--umask', str(umask)) if umask else ())
         + (('--umask', str(umask)) if umask else ())
         + (('--lock-wait', str(lock_wait)) if lock_wait else ())
         + (('--lock-wait', str(lock_wait)) if lock_wait else ())

+ 5 - 1
borgmatic/borg/rlist.py

@@ -94,7 +94,11 @@ def make_rlist_command(
                 else flags.make_flags('glob-archives', f'{rlist_arguments.prefix}*')
                 else flags.make_flags('glob-archives', f'{rlist_arguments.prefix}*')
             )
             )
             if rlist_arguments.prefix
             if rlist_arguments.prefix
-            else ()
+            else (
+                flags.make_match_archives_flags(
+                    storage_config.get('archive_name_format'), local_borg_version
+                )
+            )
         )
         )
         + flags.make_flags_from_arguments(rlist_arguments, excludes=MAKE_FLAGS_EXCLUDES)
         + flags.make_flags_from_arguments(rlist_arguments, excludes=MAKE_FLAGS_EXCLUDES)
         + flags.make_repository_flags(repository_path, local_borg_version)
         + flags.make_repository_flags(repository_path, local_borg_version)

+ 10 - 3
borgmatic/borg/transfer.py

@@ -34,9 +34,16 @@ def transfer_archives(
                 'match-archives', transfer_arguments.match_archives or transfer_arguments.archive
                 'match-archives', transfer_arguments.match_archives or transfer_arguments.archive
             )
             )
         )
         )
-        + flags.make_flags_from_arguments(
-            transfer_arguments,
-            excludes=('repository', 'source_repository', 'archive', 'match_archives'),
+        + (
+            flags.make_flags_from_arguments(
+                transfer_arguments,
+                excludes=('repository', 'source_repository', 'archive', 'match_archives'),
+            )
+            or (
+                flags.make_match_archives_flags(
+                    storage_config.get('archive_name_format'), local_borg_version
+                )
+            )
         )
         )
         + flags.make_repository_flags(repository_path, local_borg_version)
         + flags.make_repository_flags(repository_path, local_borg_version)
         + flags.make_flags('other-repo', transfer_arguments.source_repository)
         + flags.make_flags('other-repo', transfer_arguments.source_repository)

+ 15 - 14
borgmatic/config/schema.yaml

@@ -378,11 +378,9 @@ properties:
                 description: |
                 description: |
                     Name of the archive. Borg placeholders can be used. See the
                     Name of the archive. Borg placeholders can be used. See the
                     output of "borg help placeholders" for details. Defaults to
                     output of "borg help placeholders" for details. Defaults to
-                    "{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". If you specify this
-                    option, consider also specifying a prefix in the retention
-                    and consistency sections to avoid accidental
-                    pruning/checking of archives with different archive name
-                    formats.
+                    "{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". When running
+                    actions like rlist, info, or check, borgmatic automatically
+                    tries to match only archives created with this name format.
                 example: "{hostname}-documents-{now}"
                 example: "{hostname}-documents-{now}"
             relocated_repo_access_is_ok:
             relocated_repo_access_is_ok:
                 type: boolean
                 type: boolean
@@ -477,10 +475,12 @@ properties:
             prefix:
             prefix:
                 type: string
                 type: string
                 description: |
                 description: |
-                    When pruning, only consider archive names starting with this
-                    prefix.  Borg placeholders can be used. See the output of
-                    "borg help placeholders" for details. Defaults to
-                    "{hostname}-". Use an empty value to disable the default.
+                    Deprecated. When pruning, only consider archive names
+                    starting with this prefix. Borg placeholders can be used.
+                    See the output of "borg help placeholders" for details.
+                    If a prefix is not specified, borgmatic defaults to
+                    matching archives based on the archive_name_format (see
+                    above).
                 example: sourcehostname
                 example: sourcehostname
     consistency:
     consistency:
         type: object
         type: object
@@ -556,11 +556,12 @@ properties:
             prefix:
             prefix:
                 type: string
                 type: string
                 description: |
                 description: |
-                    When performing the "archives" check, only consider archive
-                    names starting with this prefix. Borg placeholders can be
-                    used. See the output of "borg help placeholders" for
-                    details. Defaults to "{hostname}-". Use an empty value to
-                    disable the default.
+                    Deprecated. When performing the "archives" check, only
+                    consider archive names starting with this prefix. Borg
+                    placeholders can be used. See the output of "borg help
+                    placeholders" for details. If a prefix is not specified,
+                    borgmatic defaults to matching archives based on the
+                    archive_name_format (see above).
                 example: sourcehostname
                 example: sourcehostname
     output:
     output:
         type: object
         type: object

+ 72 - 18
docs/how-to/make-per-application-backups.md

@@ -54,6 +54,72 @@ choice](https://torsion.org/borgmatic/docs/how-to/set-up-backups/#autopilot),
 each entry using borgmatic's `--config` flag instead of relying on
 each entry using borgmatic's `--config` flag instead of relying on
 `/etc/borgmatic.d`.
 `/etc/borgmatic.d`.
 
 
+
+## Archive naming
+
+If you've got multiple borgmatic configuration files, you might want to create
+archives with different naming schemes for each one. This is especially handy
+if each configuration file is backing up to the same Borg repository but you
+still want to be able to distinguish backup archives for one application from
+another.
+
+borgmatic supports this use case with an `archive_name_format` option. The
+idea is that you define a string format containing a number of [Borg
+placeholders](https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-placeholders),
+and borgmatic uses that format to name any new archive it creates. For
+instance:
+
+```yaml
+location:
+    ...
+    archive_name_format: home-directories-{now}
+```
+
+This means that when borgmatic creates an archive, its name will start with
+the string `home-directories-` and end with a timestamp for its creation time.
+If `archive_name_format` is unspecified, the default is
+`{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}`, meaning your system hostname plus a
+timestamp in a particular format.
+
+<span class="minilink minilink-addedin">New in version 1.7.11</span> borgmatic
+uses the `archive_name_format` option to automatically limit which archives
+get used for actions operating on multiple archives. This prevents, for
+instance, duplicate archives from showing up in `rlist` or `info` results—even
+if the same repository appears in multiple borgmatic configuration files. To
+take advantage of this feature, simply use a different `archive_name_format`
+in each configuration file.
+
+Under the hood, borgmatic accomplishes this by substituting globs for certain
+ephemeral data placeholders in your `archive_name_format`—and using the result
+to filter archives for supported actions.
+
+For instance, let's say that you have this in your configuration:
+
+```yaml
+location:
+    ...
+    archive_name_format: {hostname}-user-data-{now}
+```
+
+borgmatic considers `{now}` an emphemeral placeholder that will probably
+change per archive, while `{hostname}` won't. So it turns the example value
+into `{hostname}-user-data-*` and applies it to filter down the set of
+archives used for actions like `rlist`, `info`, `prune`, `check`, etc.
+
+The end result is that when borgmatic runs the actions for a particular
+application-specific configuration file, it only operates on the archives
+created for that application. Of course, this doesn't apply to actions like
+`compact` that operate on an entire repository.
+
+<span class="minilink minilink-addedin">Prior to 1.7.11</span> The way to
+limit the archives used was a `prefix` option in the `retention` section for
+matching against the start of archive names used for a `prune` action and 
+a separate `prefix` option in the `consistency` section for matching against
+the start of archive names used for a `check` action. Both of these options
+are deprecated in favor of the auto-matching behavior in newer versions of
+borgmatic mentioned above.
+
+
 ## Configuration includes
 ## Configuration includes
 
 
 Once you have multiple different configuration files, you might want to share
 Once you have multiple different configuration files, you might want to share
@@ -272,7 +338,7 @@ Here's an example usage:
 ```yaml
 ```yaml
 constants:
 constants:
     user: foo
     user: foo
-    my_prefix: bar-
+    archive_prefix: bar
 
 
 location:
 location:
     source_directories:
     source_directories:
@@ -281,20 +347,14 @@ location:
     ...
     ...
 
 
 storage:
 storage:
-    archive_name_format: '{my_prefix}{now}'
-
-retention:
-    prefix: {my_prefix}
-
-consistency:
-    prefix: {my_prefix}
+    archive_name_format: '{archive_prefix}-{now}'
 ```
 ```
 
 
 In this example, when borgmatic runs, all instances of `{user}` get replaced
 In this example, when borgmatic runs, all instances of `{user}` get replaced
-with `foo` and all instances of `{my_prefix}` get replaced with `bar-`. (And
-in this particular example, `{now}` doesn't get replaced with anything, but
-gets passed directly to Borg.) After substitution, the logical result looks
-something like this:
+with `foo` and all instances of `{archive-prefix}` get replaced with `bar-`.
+(And in this particular example, `{now}` doesn't get replaced with anything,
+but gets passed directly to Borg.) After substitution, the logical result
+looks something like this:
 
 
 ```yaml
 ```yaml
 location:
 location:
@@ -305,12 +365,6 @@ location:
 
 
 storage:
 storage:
     archive_name_format: 'bar-{now}'
     archive_name_format: 'bar-{now}'
-
-retention:
-    prefix: bar-
-
-consistency:
-    prefix: bar-
 ```
 ```
 
 
 An alternate to constants is passing in your values via [environment
 An alternate to constants is passing in your values via [environment

+ 55 - 34
tests/unit/borg/test_check.py

@@ -189,150 +189,170 @@ def test_filter_checks_on_frequency_restains_check_with_unelapsed_frequency_and_
 
 
 def test_make_check_flags_with_repository_check_returns_flag():
 def test_make_check_flags_with_repository_check_returns_flag():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('repository',))
+    flags = module.make_check_flags('1.2.3', {}, ('repository',))
 
 
     assert flags == ('--repository-only',)
     assert flags == ('--repository-only',)
 
 
 
 
 def test_make_check_flags_with_archives_check_returns_flag():
 def test_make_check_flags_with_archives_check_returns_flag():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('archives',))
+    flags = module.make_check_flags('1.2.3', {}, ('archives',))
 
 
     assert flags == ('--archives-only',)
     assert flags == ('--archives-only',)
 
 
 
 
 def test_make_check_flags_with_data_check_returns_flag_and_implies_archives():
 def test_make_check_flags_with_data_check_returns_flag_and_implies_archives():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('data',))
+    flags = module.make_check_flags('1.2.3', {}, ('data',))
 
 
     assert flags == ('--archives-only', '--verify-data',)
     assert flags == ('--archives-only', '--verify-data',)
 
 
 
 
 def test_make_check_flags_with_extract_omits_extract_flag():
 def test_make_check_flags_with_extract_omits_extract_flag():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('extract',))
+    flags = module.make_check_flags('1.2.3', {}, ('extract',))
 
 
     assert flags == ()
     assert flags == ()
 
 
 
 
 def test_make_check_flags_with_repository_and_data_checks_does_not_return_repository_only():
 def test_make_check_flags_with_repository_and_data_checks_does_not_return_repository_only():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('repository', 'data',))
+    flags = module.make_check_flags('1.2.3', {}, ('repository', 'data',))
 
 
     assert flags == ('--verify-data',)
     assert flags == ('--verify-data',)
 
 
 
 
-def test_make_check_flags_with_default_checks_and_default_prefix_returns_default_flags():
+def test_make_check_flags_with_default_checks_and_prefix_returns_default_flags():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags(
-        '1.2.3', ('repository', 'archives'), prefix=module.DEFAULT_PREFIX
-    )
+    flags = module.make_check_flags('1.2.3', {}, ('repository', 'archives'), prefix='foo',)
 
 
-    assert flags == ('--match-archives', f'sh:{module.DEFAULT_PREFIX}*')
+    assert flags == ('--match-archives', 'sh:foo*')
 
 
 
 
-def test_make_check_flags_with_all_checks_and_default_prefix_returns_default_flags():
+def test_make_check_flags_with_all_checks_and_prefix_returns_default_flags():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
     flags = module.make_check_flags(
     flags = module.make_check_flags(
-        '1.2.3', ('repository', 'archives', 'extract'), prefix=module.DEFAULT_PREFIX
+        '1.2.3', {}, ('repository', 'archives', 'extract'), prefix='foo',
     )
     )
 
 
-    assert flags == ('--match-archives', f'sh:{module.DEFAULT_PREFIX}*')
+    assert flags == ('--match-archives', 'sh:foo*')
 
 
 
 
-def test_make_check_flags_with_all_checks_and_default_prefix_without_borg_features_returns_glob_archives_flags():
+def test_make_check_flags_with_all_checks_and_prefix_without_borg_features_returns_glob_archives_flags():
     flexmock(module.feature).should_receive('available').and_return(False)
     flexmock(module.feature).should_receive('available').and_return(False)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
     flags = module.make_check_flags(
     flags = module.make_check_flags(
-        '1.2.3', ('repository', 'archives', 'extract'), prefix=module.DEFAULT_PREFIX
+        '1.2.3', {}, ('repository', 'archives', 'extract'), prefix='foo',
     )
     )
 
 
-    assert flags == ('--glob-archives', f'{module.DEFAULT_PREFIX}*')
+    assert flags == ('--glob-archives', 'foo*')
 
 
 
 
 def test_make_check_flags_with_archives_check_and_last_includes_last_flag():
 def test_make_check_flags_with_archives_check_and_last_includes_last_flag():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('archives',), check_last=3)
+    flags = module.make_check_flags('1.2.3', {}, ('archives',), check_last=3)
 
 
     assert flags == ('--archives-only', '--last', '3')
     assert flags == ('--archives-only', '--last', '3')
 
 
 
 
 def test_make_check_flags_with_data_check_and_last_includes_last_flag():
 def test_make_check_flags_with_data_check_and_last_includes_last_flag():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('data',), check_last=3)
+    flags = module.make_check_flags('1.2.3', {}, ('data',), check_last=3)
 
 
     assert flags == ('--archives-only', '--last', '3', '--verify-data')
     assert flags == ('--archives-only', '--last', '3', '--verify-data')
 
 
 
 
 def test_make_check_flags_with_repository_check_and_last_omits_last_flag():
 def test_make_check_flags_with_repository_check_and_last_omits_last_flag():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('repository',), check_last=3)
+    flags = module.make_check_flags('1.2.3', {}, ('repository',), check_last=3)
 
 
     assert flags == ('--repository-only',)
     assert flags == ('--repository-only',)
 
 
 
 
 def test_make_check_flags_with_default_checks_and_last_includes_last_flag():
 def test_make_check_flags_with_default_checks_and_last_includes_last_flag():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('repository', 'archives'), check_last=3)
+    flags = module.make_check_flags('1.2.3', {}, ('repository', 'archives'), check_last=3)
 
 
     assert flags == ('--last', '3')
     assert flags == ('--last', '3')
 
 
 
 
 def test_make_check_flags_with_archives_check_and_prefix_includes_match_archives_flag():
 def test_make_check_flags_with_archives_check_and_prefix_includes_match_archives_flag():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('archives',), prefix='foo-')
+    flags = module.make_check_flags('1.2.3', {}, ('archives',), prefix='foo-')
 
 
     assert flags == ('--archives-only', '--match-archives', 'sh:foo-*')
     assert flags == ('--archives-only', '--match-archives', 'sh:foo-*')
 
 
 
 
 def test_make_check_flags_with_data_check_and_prefix_includes_match_archives_flag():
 def test_make_check_flags_with_data_check_and_prefix_includes_match_archives_flag():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('data',), prefix='foo-')
+    flags = module.make_check_flags('1.2.3', {}, ('data',), prefix='foo-')
 
 
     assert flags == ('--archives-only', '--match-archives', 'sh:foo-*', '--verify-data')
     assert flags == ('--archives-only', '--match-archives', 'sh:foo-*', '--verify-data')
 
 
 
 
-def test_make_check_flags_with_archives_check_and_empty_prefix_omits_match_archives_flag():
+def test_make_check_flags_with_archives_check_and_empty_prefix_uses_archive_name_format_instead():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        'bar-{now}', '1.2.3'  # noqa: FS003
+    ).and_return(('--match-archives', 'sh:bar-*'))
 
 
-    flags = module.make_check_flags('1.2.3', ('archives',), prefix='')
+    flags = module.make_check_flags(
+        '1.2.3', {'archive_name_format': 'bar-{now}'}, ('archives',), prefix=''  # noqa: FS003
+    )
 
 
-    assert flags == ('--archives-only',)
+    assert flags == ('--archives-only', '--match-archives', 'sh:bar-*')
 
 
 
 
 def test_make_check_flags_with_archives_check_and_none_prefix_omits_match_archives_flag():
 def test_make_check_flags_with_archives_check_and_none_prefix_omits_match_archives_flag():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('archives',), prefix=None)
+    flags = module.make_check_flags('1.2.3', {}, ('archives',), prefix=None)
 
 
     assert flags == ('--archives-only',)
     assert flags == ('--archives-only',)
 
 
 
 
 def test_make_check_flags_with_repository_check_and_prefix_omits_match_archives_flag():
 def test_make_check_flags_with_repository_check_and_prefix_omits_match_archives_flag():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('repository',), prefix='foo-')
+    flags = module.make_check_flags('1.2.3', {}, ('repository',), prefix='foo-')
 
 
     assert flags == ('--repository-only',)
     assert flags == ('--repository-only',)
 
 
 
 
 def test_make_check_flags_with_default_checks_and_prefix_includes_match_archives_flag():
 def test_make_check_flags_with_default_checks_and_prefix_includes_match_archives_flag():
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    flags = module.make_check_flags('1.2.3', ('repository', 'archives'), prefix='foo-')
+    flags = module.make_check_flags('1.2.3', {}, ('repository', 'archives'), prefix='foo-')
 
 
     assert flags == ('--match-archives', 'sh:foo-*')
     assert flags == ('--match-archives', 'sh:foo-*')
 
 
@@ -427,7 +447,7 @@ def test_check_archives_calls_borg_with_parameters(checks):
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').with_args(
     flexmock(module).should_receive('make_check_flags').with_args(
-        '1.2.3', checks, check_last, module.DEFAULT_PREFIX
+        '1.2.3', {}, checks, check_last, prefix=None,
     ).and_return(())
     ).and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(('borg', 'check', 'repo'))
     insert_execute_command_mock(('borg', 'check', 'repo'))
@@ -581,7 +601,7 @@ def test_check_archives_with_local_path_calls_borg_via_local_path():
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').with_args(
     flexmock(module).should_receive('make_check_flags').with_args(
-        '1.2.3', checks, check_last, module.DEFAULT_PREFIX
+        '1.2.3', {}, checks, check_last, prefix=None,
     ).and_return(())
     ).and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(('borg1', 'check', 'repo'))
     insert_execute_command_mock(('borg1', 'check', 'repo'))
@@ -608,7 +628,7 @@ def test_check_archives_with_remote_path_calls_borg_with_remote_path_parameters(
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').with_args(
     flexmock(module).should_receive('make_check_flags').with_args(
-        '1.2.3', checks, check_last, module.DEFAULT_PREFIX
+        '1.2.3', {}, checks, check_last, prefix=None,
     ).and_return(())
     ).and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(('borg', 'check', '--remote-path', 'borg1', 'repo'))
     insert_execute_command_mock(('borg', 'check', '--remote-path', 'borg1', 'repo'))
@@ -628,6 +648,7 @@ def test_check_archives_with_remote_path_calls_borg_with_remote_path_parameters(
 def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
 def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
     checks = ('repository',)
     checks = ('repository',)
     check_last = flexmock()
     check_last = flexmock()
+    storage_config = {'lock_wait': 5}
     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)
@@ -635,7 +656,7 @@ def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').with_args(
     flexmock(module).should_receive('make_check_flags').with_args(
-        '1.2.3', checks, check_last, module.DEFAULT_PREFIX
+        '1.2.3', storage_config, checks, check_last, None,
     ).and_return(())
     ).and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(('borg', 'check', '--lock-wait', '5', 'repo'))
     insert_execute_command_mock(('borg', 'check', '--lock-wait', '5', 'repo'))
@@ -645,7 +666,7 @@ def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
     module.check_archives(
     module.check_archives(
         repository_path='repo',
         repository_path='repo',
         location_config={},
         location_config={},
-        storage_config={'lock_wait': 5},
+        storage_config=storage_config,
         consistency_config=consistency_config,
         consistency_config=consistency_config,
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
     )
     )
@@ -662,7 +683,7 @@ def test_check_archives_with_retention_prefix():
         '{"repository": {"id": "repo"}}'
         '{"repository": {"id": "repo"}}'
     )
     )
     flexmock(module).should_receive('make_check_flags').with_args(
     flexmock(module).should_receive('make_check_flags').with_args(
-        '1.2.3', checks, check_last, prefix
+        '1.2.3', {}, checks, check_last, prefix
     ).and_return(())
     ).and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(('borg', 'check', 'repo'))
     insert_execute_command_mock(('borg', 'check', 'repo'))

+ 33 - 0
tests/unit/borg/test_flags.py

@@ -1,3 +1,4 @@
+import pytest
 from flexmock import flexmock
 from flexmock import flexmock
 
 
 from borgmatic.borg import flags as module
 from borgmatic.borg import flags as module
@@ -78,3 +79,35 @@ def test_make_repository_archive_flags_with_borg_features_joins_repository_and_a
     assert module.make_repository_archive_flags(
     assert module.make_repository_archive_flags(
         repository_path='repo', archive='archive', local_borg_version='1.2.3'
         repository_path='repo', archive='archive', local_borg_version='1.2.3'
     ) == ('repo::archive',)
     ) == ('repo::archive',)
+
+
+@pytest.mark.parametrize(
+    'archive_name_format,feature_available,expected_result',
+    (
+        (None, True, ()),
+        ('', True, ()),
+        (
+            '{hostname}-docs-{now}',  # noqa: FS003
+            True,
+            ('--match-archives', 'sh:{hostname}-docs-*'),  # noqa: FS003
+        ),
+        ('{utcnow}-docs-{user}', True, ('--match-archives', 'sh:*-docs-{user}')),  # noqa: FS003
+        ('{fqdn}-{pid}', True, ('--match-archives', 'sh:{fqdn}-*')),  # noqa: FS003
+        (
+            'stuff-{now:%Y-%m-%dT%H:%M:%S.%f}',  # noqa: FS003
+            True,
+            ('--match-archives', 'sh:stuff-*'),
+        ),
+        ('{hostname}-docs-{now}', False, ('--glob-archives', '{hostname}-docs-*')),  # noqa: FS003
+        ('{utcnow}-docs-{user}', False, ('--glob-archives', '*-docs-{user}')),  # noqa: FS003
+    ),
+)
+def test_make_match_archives_flags_makes_flags_with_globs(
+    archive_name_format, feature_available, expected_result
+):
+    flexmock(module.feature).should_receive('available').and_return(feature_available)
+
+    assert (
+        module.make_match_archives_flags(archive_name_format, local_borg_version=flexmock())
+        == expected_result
+    )

+ 90 - 1
tests/unit/borg/test_info.py

@@ -12,6 +12,9 @@ def test_display_archives_info_calls_borg_with_parameters():
     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_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '2.3.4'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -34,6 +37,9 @@ def test_display_archives_info_with_log_info_calls_borg_with_info_parameter():
     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_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '2.3.4'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -56,6 +62,9 @@ def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_outpu
     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_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '2.3.4'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -78,6 +87,9 @@ def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter():
     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_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '2.3.4'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -101,6 +113,9 @@ def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_outp
     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_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '2.3.4'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -123,6 +138,9 @@ def test_display_archives_info_with_json_calls_borg_with_json_parameter():
     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_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '2.3.4'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -147,6 +165,9 @@ def test_display_archives_info_with_archive_calls_borg_with_match_archives_param
     flexmock(module.flags).should_receive('make_flags').with_args(
     flexmock(module.flags).should_receive('make_flags').with_args(
         'match-archives', 'archive'
         'match-archives', 'archive'
     ).and_return(('--match-archives', 'archive'))
     ).and_return(('--match-archives', 'archive'))
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '2.3.4'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -169,6 +190,9 @@ def test_display_archives_info_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_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '2.3.4'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -195,6 +219,9 @@ def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_para
     flexmock(module.flags).should_receive('make_flags').with_args(
     flexmock(module.flags).should_receive('make_flags').with_args(
         'remote-path', 'borg1'
         'remote-path', 'borg1'
     ).and_return(('--remote-path', 'borg1'))
     ).and_return(('--remote-path', 'borg1'))
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '2.3.4'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -221,6 +248,9 @@ def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_paramete
     flexmock(module.flags).should_receive('make_flags').with_args('lock-wait', 5).and_return(
     flexmock(module.flags).should_receive('make_flags').with_args('lock-wait', 5).and_return(
         ('--lock-wait', '5')
         ('--lock-wait', '5')
     )
     )
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '2.3.4'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     storage_config = {'lock_wait': 5}
     storage_config = {'lock_wait': 5}
@@ -240,13 +270,16 @@ def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_paramete
     )
     )
 
 
 
 
-def test_display_archives_info_with_prefix_calls_borg_with_match_archives_parameters():
+def test_display_archives_info_transforms_prefix_into_match_archives_parameters():
     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_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').with_args(
     flexmock(module.flags).should_receive('make_flags').with_args(
         'match-archives', 'sh:foo*'
         'match-archives', 'sh:foo*'
     ).and_return(('--match-archives', 'sh:foo*'))
     ).and_return(('--match-archives', 'sh:foo*'))
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '2.3.4'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -265,12 +298,68 @@ def test_display_archives_info_with_prefix_calls_borg_with_match_archives_parame
     )
     )
 
 
 
 
+def test_display_archives_info_prefers_prefix_over_archive_name_format():
+    flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
+    flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
+    flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_flags').with_args(
+        'match-archives', 'sh:foo*'
+    ).and_return(('--match-archives', 'sh:foo*'))
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '2.3.4'
+    ).and_return(())
+    flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
+    flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
+    flexmock(module.environment).should_receive('make_environment')
+    flexmock(module).should_receive('execute_command').with_args(
+        ('borg', 'info', '--match-archives', 'sh:foo*', '--repo', 'repo'),
+        output_log_level=module.borgmatic.logger.ANSWER,
+        borg_local_path='borg',
+        extra_environment=None,
+    )
+
+    module.display_archives_info(
+        repository_path='repo',
+        storage_config={'archive_name_format': 'bar-{now}'},  # noqa: FS003
+        local_borg_version='2.3.4',
+        info_arguments=flexmock(archive=None, json=False, prefix='foo'),
+    )
+
+
+def test_display_archives_info_transforms_archive_name_format_into_match_archives_parameters():
+    flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
+    flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
+    flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        'bar-{now}', '2.3.4'  # noqa: FS003
+    ).and_return(('--match-archives', 'sh:bar-*'))
+    flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
+    flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
+    flexmock(module.environment).should_receive('make_environment')
+    flexmock(module).should_receive('execute_command').with_args(
+        ('borg', 'info', '--match-archives', 'sh:bar-*', '--repo', 'repo'),
+        output_log_level=module.borgmatic.logger.ANSWER,
+        borg_local_path='borg',
+        extra_environment=None,
+    )
+
+    module.display_archives_info(
+        repository_path='repo',
+        storage_config={'archive_name_format': 'bar-{now}'},  # noqa: FS003
+        local_borg_version='2.3.4',
+        info_arguments=flexmock(archive=None, json=False, prefix=None),
+    )
+
+
 @pytest.mark.parametrize('argument_name', ('match_archives', 'sort_by', 'first', 'last'))
 @pytest.mark.parametrize('argument_name', ('match_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.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
     flag_name = f"--{argument_name.replace('_', ' ')}"
     flag_name = f"--{argument_name.replace('_', ' ')}"
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '2.3.4'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
         (flag_name, 'value')
         (flag_name, 'value')
     )
     )

+ 32 - 29
tests/unit/borg/test_prune.py

@@ -18,18 +18,17 @@ def insert_execute_command_mock(prune_command, output_log_level):
     ).once()
     ).once()
 
 
 
 
-BASE_PRUNE_FLAGS = (('--keep-daily', '1'), ('--keep-weekly', '2'), ('--keep-monthly', '3'))
+BASE_PRUNE_FLAGS = ('--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3')
 
 
 
 
-def test_make_prune_flags_returns_flags_from_config_plus_default_prefix_glob():
+def test_make_prune_flags_returns_flags_from_config():
     retention_config = OrderedDict((('keep_daily', 1), ('keep_weekly', 2), ('keep_monthly', 3)))
     retention_config = OrderedDict((('keep_daily', 1), ('keep_weekly', 2), ('keep_monthly', 3)))
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
+    result = module.make_prune_flags({}, retention_config, local_borg_version='1.2.3')
 
 
-    assert tuple(result) == BASE_PRUNE_FLAGS + (
-        ('--match-archives', 'sh:{hostname}-*'),  # noqa: FS003
-    )
+    assert result == BASE_PRUNE_FLAGS
 
 
 
 
 def test_make_prune_flags_accepts_prefix_with_placeholders():
 def test_make_prune_flags_accepts_prefix_with_placeholders():
@@ -37,15 +36,18 @@ def test_make_prune_flags_accepts_prefix_with_placeholders():
         (('keep_daily', 1), ('prefix', 'Documents_{hostname}-{now}'))  # noqa: FS003
         (('keep_daily', 1), ('prefix', 'Documents_{hostname}-{now}'))  # noqa: FS003
     )
     )
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
+    result = module.make_prune_flags({}, retention_config, local_borg_version='1.2.3')
 
 
     expected = (
     expected = (
-        ('--keep-daily', '1'),
-        ('--match-archives', 'sh:Documents_{hostname}-{now}*'),  # noqa: FS003
+        '--keep-daily',
+        '1',
+        '--match-archives',
+        'sh:Documents_{hostname}-{now}*',  # noqa: FS003
     )
     )
 
 
-    assert tuple(result) == expected
+    assert result == expected
 
 
 
 
 def test_make_prune_flags_with_prefix_without_borg_features_uses_glob_archives():
 def test_make_prune_flags_with_prefix_without_borg_features_uses_glob_archives():
@@ -53,37 +55,38 @@ def test_make_prune_flags_with_prefix_without_borg_features_uses_glob_archives()
         (('keep_daily', 1), ('prefix', 'Documents_{hostname}-{now}'))  # noqa: FS003
         (('keep_daily', 1), ('prefix', 'Documents_{hostname}-{now}'))  # noqa: FS003
     )
     )
     flexmock(module.feature).should_receive('available').and_return(False)
     flexmock(module.feature).should_receive('available').and_return(False)
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
 
 
-    result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
+    result = module.make_prune_flags({}, retention_config, local_borg_version='1.2.3')
 
 
     expected = (
     expected = (
-        ('--keep-daily', '1'),
-        ('--glob-archives', 'Documents_{hostname}-{now}*'),  # noqa: FS003
+        '--keep-daily',
+        '1',
+        '--glob-archives',
+        'Documents_{hostname}-{now}*',  # noqa: FS003
     )
     )
 
 
-    assert tuple(result) == expected
-
-
-def test_make_prune_flags_treats_empty_prefix_as_no_prefix():
-    retention_config = OrderedDict((('keep_daily', 1), ('prefix', '')))
-    flexmock(module.feature).should_receive('available').and_return(True)
-
-    result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
+    assert result == expected
 
 
-    expected = (('--keep-daily', '1'),)
 
 
-    assert tuple(result) == expected
-
-
-def test_make_prune_flags_treats_none_prefix_as_no_prefix():
+def test_make_prune_flags_without_prefix_uses_archive_name_format_instead():
+    storage_config = {'archive_name_format': 'bar-{now}'}  # noqa: FS003
     retention_config = OrderedDict((('keep_daily', 1), ('prefix', None)))
     retention_config = OrderedDict((('keep_daily', 1), ('prefix', None)))
     flexmock(module.feature).should_receive('available').and_return(True)
     flexmock(module.feature).should_receive('available').and_return(True)
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        'bar-{now}', '1.2.3'  # noqa: FS003
+    ).and_return(('--match-archives', 'sh:bar-*'))
 
 
-    result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
+    result = module.make_prune_flags(storage_config, retention_config, local_borg_version='1.2.3')
 
 
-    expected = (('--keep-daily', '1'),)
+    expected = (
+        '--keep-daily',
+        '1',
+        '--match-archives',
+        'sh:bar-*',  # noqa: FS003
+    )
 
 
-    assert tuple(result) == expected
+    assert result == expected
 
 
 
 
 PRUNE_COMMAND = ('borg', 'prune', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3')
 PRUNE_COMMAND = ('borg', 'prune', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3')

+ 69 - 0
tests/unit/borg/test_rlist.py

@@ -127,6 +127,9 @@ def test_resolve_archive_name_with_lock_wait_calls_borg_with_lock_wait_parameter
 def test_make_rlist_command_includes_log_info():
 def test_make_rlist_command_includes_log_info():
     insert_logging_mock(logging.INFO)
     insert_logging_mock(logging.INFO)
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '1.2.3'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
 
 
@@ -143,6 +146,9 @@ def test_make_rlist_command_includes_log_info():
 def test_make_rlist_command_includes_json_but_not_info():
 def test_make_rlist_command_includes_json_but_not_info():
     insert_logging_mock(logging.INFO)
     insert_logging_mock(logging.INFO)
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '1.2.3'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
 
 
@@ -159,6 +165,9 @@ def test_make_rlist_command_includes_json_but_not_info():
 def test_make_rlist_command_includes_log_debug():
 def test_make_rlist_command_includes_log_debug():
     insert_logging_mock(logging.DEBUG)
     insert_logging_mock(logging.DEBUG)
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '1.2.3'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
 
 
@@ -175,6 +184,9 @@ def test_make_rlist_command_includes_log_debug():
 def test_make_rlist_command_includes_json_but_not_debug():
 def test_make_rlist_command_includes_json_but_not_debug():
     insert_logging_mock(logging.DEBUG)
     insert_logging_mock(logging.DEBUG)
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '1.2.3'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
 
 
@@ -190,6 +202,9 @@ def test_make_rlist_command_includes_json_but_not_debug():
 
 
 def test_make_rlist_command_includes_json():
 def test_make_rlist_command_includes_json():
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '1.2.3'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
 
 
@@ -207,6 +222,9 @@ def test_make_rlist_command_includes_lock_wait():
     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')
     ).and_return(())
     ).and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '1.2.3'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
 
 
@@ -222,6 +240,9 @@ def test_make_rlist_command_includes_lock_wait():
 
 
 def test_make_rlist_command_includes_local_path():
 def test_make_rlist_command_includes_local_path():
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '1.2.3'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
 
 
@@ -240,6 +261,9 @@ def test_make_rlist_command_includes_remote_path():
     flexmock(module.flags).should_receive('make_flags').and_return(
     flexmock(module.flags).should_receive('make_flags').and_return(
         ('--remote-path', 'borg2')
         ('--remote-path', 'borg2')
     ).and_return(()).and_return(())
     ).and_return(()).and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '1.2.3'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
 
 
@@ -258,6 +282,9 @@ def test_make_rlist_command_transforms_prefix_into_match_archives():
     flexmock(module.flags).should_receive('make_flags').and_return(()).and_return(()).and_return(
     flexmock(module.flags).should_receive('make_flags').and_return(()).and_return(()).and_return(
         ('--match-archives', 'sh:foo*')
         ('--match-archives', 'sh:foo*')
     )
     )
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '1.2.3'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
 
 
@@ -271,8 +298,47 @@ def test_make_rlist_command_transforms_prefix_into_match_archives():
     assert command == ('borg', 'list', '--match-archives', 'sh:foo*', 'repo')
     assert command == ('borg', 'list', '--match-archives', 'sh:foo*', 'repo')
 
 
 
 
+def test_make_rlist_command_prefers_prefix_over_archive_name_format():
+    flexmock(module.flags).should_receive('make_flags').and_return(()).and_return(()).and_return(
+        ('--match-archives', 'sh:foo*')
+    )
+    flexmock(module.flags).should_receive('make_match_archives_flags').never()
+    flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
+    flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
+
+    command = module.make_rlist_command(
+        repository_path='repo',
+        storage_config={'archive_name_format': 'bar-{now}'},  # noqa: FS003
+        local_borg_version='1.2.3',
+        rlist_arguments=flexmock(archive=None, paths=None, json=False, prefix='foo'),
+    )
+
+    assert command == ('borg', 'list', '--match-archives', 'sh:foo*', 'repo')
+
+
+def test_make_rlist_command_transforms_archive_name_format_into_match_archives():
+    flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        'bar-{now}', '1.2.3'  # noqa: FS003
+    ).and_return(('--match-archives', 'sh:bar-*'))
+    flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
+    flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
+
+    command = module.make_rlist_command(
+        repository_path='repo',
+        storage_config={'archive_name_format': 'bar-{now}'},  # noqa: FS003
+        local_borg_version='1.2.3',
+        rlist_arguments=flexmock(archive=None, paths=None, json=False, prefix=None),
+    )
+
+    assert command == ('borg', 'list', '--match-archives', 'sh:bar-*', 'repo')
+
+
 def test_make_rlist_command_includes_short():
 def test_make_rlist_command_includes_short():
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '1.2.3'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--short',))
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--short',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
 
 
@@ -301,6 +367,9 @@ def test_make_rlist_command_includes_short():
 )
 )
 def test_make_rlist_command_includes_additional_flags(argument_name):
 def test_make_rlist_command_includes_additional_flags(argument_name):
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, '1.2.3'
+    ).and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
         (f"--{argument_name.replace('_', '-')}", 'value')
         (f"--{argument_name.replace('_', '-')}", 'value')
     )
     )

+ 43 - 2
tests/unit/borg/test_transfer.py

@@ -12,6 +12,7 @@ def test_transfer_archives_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_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -41,6 +42,7 @@ def test_transfer_archives_with_dry_run_calls_borg_with_dry_run_flag():
     flexmock(module.flags).should_receive('make_flags').with_args('dry-run', True).and_return(
     flexmock(module.flags).should_receive('make_flags').with_args('dry-run', True).and_return(
         ('--dry-run',)
         ('--dry-run',)
     )
     )
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -67,6 +69,7 @@ def test_transfer_archives_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_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -93,6 +96,7 @@ def test_transfer_archives_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_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -123,6 +127,7 @@ def test_transfer_archives_with_archive_calls_borg_with_match_archives_flag():
     flexmock(module.flags).should_receive('make_flags').with_args(
     flexmock(module.flags).should_receive('make_flags').with_args(
         'match-archives', 'archive'
         'match-archives', 'archive'
     ).and_return(('--match-archives', 'archive'))
     ).and_return(('--match-archives', 'archive'))
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -137,7 +142,7 @@ def test_transfer_archives_with_archive_calls_borg_with_match_archives_flag():
     module.transfer_archives(
     module.transfer_archives(
         dry_run=False,
         dry_run=False,
         repository_path='repo',
         repository_path='repo',
-        storage_config={},
+        storage_config={'archive_name_format': 'bar-{now}'},  # noqa: FS003
         local_borg_version='2.3.4',
         local_borg_version='2.3.4',
         transfer_arguments=flexmock(
         transfer_arguments=flexmock(
             archive='archive', progress=None, match_archives=None, source_repository=None
             archive='archive', progress=None, match_archives=None, source_repository=None
@@ -152,6 +157,7 @@ def test_transfer_archives_with_match_archives_calls_borg_with_match_archives_fl
     flexmock(module.flags).should_receive('make_flags').with_args(
     flexmock(module.flags).should_receive('make_flags').with_args(
         'match-archives', 'sh:foo*'
         'match-archives', 'sh:foo*'
     ).and_return(('--match-archives', 'sh:foo*'))
     ).and_return(('--match-archives', 'sh:foo*'))
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -166,7 +172,7 @@ def test_transfer_archives_with_match_archives_calls_borg_with_match_archives_fl
     module.transfer_archives(
     module.transfer_archives(
         dry_run=False,
         dry_run=False,
         repository_path='repo',
         repository_path='repo',
-        storage_config={},
+        storage_config={'archive_name_format': 'bar-{now}'},  # noqa: FS003
         local_borg_version='2.3.4',
         local_borg_version='2.3.4',
         transfer_arguments=flexmock(
         transfer_arguments=flexmock(
             archive=None, progress=None, match_archives='sh:foo*', source_repository=None
             archive=None, progress=None, match_archives='sh:foo*', source_repository=None
@@ -174,10 +180,40 @@ def test_transfer_archives_with_match_archives_calls_borg_with_match_archives_fl
     )
     )
 
 
 
 
+def test_transfer_archives_with_archive_name_format_calls_borg_with_match_archives_flag():
+    flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
+    flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
+    flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        'bar-{now}', '2.3.4'  # noqa: FS003
+    ).and_return(('--match-archives', 'sh:bar-*'))
+    flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
+    flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
+    flexmock(module.environment).should_receive('make_environment')
+    flexmock(module).should_receive('execute_command').with_args(
+        ('borg', 'transfer', '--match-archives', 'sh:bar-*', '--repo', 'repo'),
+        output_log_level=module.borgmatic.logger.ANSWER,
+        output_file=None,
+        borg_local_path='borg',
+        extra_environment=None,
+    )
+
+    module.transfer_archives(
+        dry_run=False,
+        repository_path='repo',
+        storage_config={'archive_name_format': 'bar-{now}'},  # noqa: FS003
+        local_borg_version='2.3.4',
+        transfer_arguments=flexmock(
+            archive=None, progress=None, match_archives=None, source_repository=None
+        ),
+    )
+
+
 def test_transfer_archives_with_local_path_calls_borg_via_local_path():
 def test_transfer_archives_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_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -208,6 +244,7 @@ def test_transfer_archives_with_remote_path_calls_borg_with_remote_path_flags():
     flexmock(module.flags).should_receive('make_flags').with_args(
     flexmock(module.flags).should_receive('make_flags').with_args(
         'remote-path', 'borg2'
         'remote-path', 'borg2'
     ).and_return(('--remote-path', 'borg2'))
     ).and_return(('--remote-path', 'borg2'))
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -238,6 +275,7 @@ def test_transfer_archives_with_lock_wait_calls_borg_with_lock_wait_flags():
     flexmock(module.flags).should_receive('make_flags').with_args('lock-wait', 5).and_return(
     flexmock(module.flags).should_receive('make_flags').with_args('lock-wait', 5).and_return(
         ('--lock-wait', '5')
         ('--lock-wait', '5')
     )
     )
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     storage_config = {'lock_wait': 5}
     storage_config = {'lock_wait': 5}
@@ -265,6 +303,7 @@ def test_transfer_archives_with_progress_calls_borg_with_progress_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_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')
@@ -293,6 +332,7 @@ def test_transfer_archives_passes_through_arguments_to_borg(argument_name):
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flag_name = f"--{argument_name.replace('_', ' ')}"
     flag_name = f"--{argument_name.replace('_', ' ')}"
     flexmock(module.flags).should_receive('make_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
         (flag_name, 'value')
         (flag_name, 'value')
     )
     )
@@ -327,6 +367,7 @@ def test_transfer_archives_with_source_repository_calls_borg_with_other_repo_fla
     flexmock(module.flags).should_receive('make_flags').with_args('other-repo', 'other').and_return(
     flexmock(module.flags).should_receive('make_flags').with_args('other-repo', 'other').and_return(
         ('--other-repo', 'other')
         ('--other-repo', 'other')
     )
     )
+    flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
     flexmock(module.environment).should_receive('make_environment')
     flexmock(module.environment).should_receive('make_environment')