Browse Source

Add Borg 2 date-based matching flags for archive selection (#659).

Reviewed-on: https://projects.torsion.org/borgmatic-collective/borgmatic/pulls/661
Dan Helfman 2 years ago
parent
commit
35b5c62ca6

+ 1 - 4
borgmatic/actions/mount.py

@@ -40,10 +40,7 @@ def run_mount(
                 local_path,
                 remote_path,
             ),
-            mount_arguments.mount_point,
-            mount_arguments.paths,
-            mount_arguments.foreground,
-            mount_arguments.options,
+            mount_arguments,
             storage,
             local_borg_version,
             global_arguments,

+ 1 - 2
borgmatic/actions/prune.py

@@ -45,10 +45,9 @@ def run_prune(
         retention,
         local_borg_version,
         global_arguments,
+        prune_arguments,
         local_path=local_path,
         remote_path=remote_path,
-        stats=prune_arguments.stats,
-        list_archives=prune_arguments.list_archives,
     )
     borgmatic.hooks.command.execute_hook(
         hooks.get('after_prune'),

+ 9 - 9
borgmatic/borg/mount.py

@@ -9,10 +9,7 @@ logger = logging.getLogger(__name__)
 def mount_archive(
     repository_path,
     archive,
-    mount_point,
-    paths,
-    foreground,
-    options,
+    mount_arguments,
     storage_config,
     local_borg_version,
     global_arguments,
@@ -36,8 +33,11 @@ def mount_archive(
         + (('--lock-wait', str(lock_wait)) if lock_wait else ())
         + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
         + (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
-        + (('--foreground',) if foreground else ())
-        + (('-o', options) if options else ())
+        + flags.make_flags_from_arguments(
+            mount_arguments,
+            excludes=('repository', 'archive', 'mount_point', 'paths', 'options'),
+        )
+        + (('-o', mount_arguments.options) if mount_arguments.options else ())
         + (
             (
                 flags.make_repository_flags(repository_path, local_borg_version)
@@ -54,14 +54,14 @@ def mount_archive(
                 else flags.make_repository_flags(repository_path, local_borg_version)
             )
         )
-        + (mount_point,)
-        + (tuple(paths) if paths else ())
+        + (mount_arguments.mount_point,)
+        + (tuple(mount_arguments.paths) if mount_arguments.paths else ())
     )
 
     borg_environment = environment.make_environment(storage_config)
 
     # Don't capture the output when foreground mode is used so that ctrl-C can work properly.
-    if foreground:
+    if mount_arguments.foreground:
         execute_command(
             full_command,
             output_file=DO_NOT_CAPTURE,

+ 8 - 5
borgmatic/borg/prune.py

@@ -53,11 +53,10 @@ def prune_archives(
     storage_config,
     retention_config,
     local_borg_version,
+    prune_arguments,
     global_arguments,
     local_path='borg',
     remote_path=None,
-    stats=False,
-    list_archives=False,
 ):
     '''
     Given dry-run flag, a local or remote repository path, a storage config dict, and a
@@ -76,16 +75,20 @@ def prune_archives(
         + (('--umask', str(umask)) if umask else ())
         + (('--log-json',) if global_arguments.log_json else ())
         + (('--lock-wait', str(lock_wait)) if lock_wait else ())
-        + (('--stats',) if stats and not dry_run else ())
+        + (('--stats',) if prune_arguments.stats and not dry_run else ())
         + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
-        + (('--list',) if list_archives else ())
+        + flags.make_flags_from_arguments(
+            prune_arguments,
+            excludes=('repository', 'stats', 'list_archives'),
+        )
+        + (('--list',) if prune_arguments.list_archives else ())
         + (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
         + (('--dry-run',) if dry_run else ())
         + (tuple(extra_borg_options.split(' ')) if extra_borg_options else ())
         + flags.make_repository_flags(repository_path, local_borg_version)
     )
 
-    if stats or list_archives:
+    if prune_arguments.stats or prune_arguments.list_archives:
         output_log_level = logging.ANSWER
     else:
         output_log_level = logging.INFO

+ 115 - 7
borgmatic/commands/arguments.py

@@ -259,7 +259,7 @@ def make_parsers():
         '--source-repository',
         '--other-repo',
         metavar='KEY_REPOSITORY',
-        help='Path to an existing Borg repository whose key material should be reused (Borg 2.x+ only)',
+        help='Path to an existing Borg repository whose key material should be reused [Borg 2.x+ only]',
     )
     rcreate_group.add_argument(
         '--repository',
@@ -268,7 +268,7 @@ def make_parsers():
     rcreate_group.add_argument(
         '--copy-crypt-key',
         action='store_true',
-        help='Copy the crypt key used for authenticated encryption from the source repository, defaults to a new random key (Borg 2.x+ only)',
+        help='Copy the crypt key used for authenticated encryption from the source repository, defaults to a new random key [Borg 2.x+ only]',
     )
     rcreate_group.add_argument(
         '--append-only',
@@ -291,8 +291,8 @@ def make_parsers():
     transfer_parser = subparsers.add_parser(
         'transfer',
         aliases=SUBPARSER_ALIASES['transfer'],
-        help='Transfer archives from one repository to another, optionally upgrading the transferred data (Borg 2.0+ only)',
-        description='Transfer archives from one repository to another, optionally upgrading the transferred data (Borg 2.0+ only)',
+        help='Transfer archives from one repository to another, optionally upgrading the transferred data [Borg 2.0+ only]',
+        description='Transfer archives from one repository to another, optionally upgrading the transferred data [Borg 2.0+ only]',
         add_help=False,
     )
     transfer_group = transfer_parser.add_argument_group('transfer arguments')
@@ -337,6 +337,26 @@ def make_parsers():
     transfer_group.add_argument(
         '--last', metavar='N', help='Only transfer last N archives after other filters are applied'
     )
+    transfer_group.add_argument(
+        '--oldest',
+        metavar='TIMESPAN',
+        help='Transfer archives within a specified time range starting from the timestamp of the oldest archive (e.g. 7d or 12m) [Borg 2.x+ only]',
+    )
+    transfer_group.add_argument(
+        '--newest',
+        metavar='TIMESPAN',
+        help='Transfer archives within a time range that ends at timestamp of the newest archive and starts a specified time range ago (e.g. 7d or 12m) [Borg 2.x+ only]',
+    )
+    transfer_group.add_argument(
+        '--older',
+        metavar='TIMESPAN',
+        help='Transfer archives that are older than the specified time range (e.g. 7d or 12m) from the current time [Borg 2.x+ only]',
+    )
+    transfer_group.add_argument(
+        '--newer',
+        metavar='TIMESPAN',
+        help='Transfer archives that are newer than the specified time range (e.g. 7d or 12m) from the current time [Borg 2.x+ only]',
+    )
     transfer_group.add_argument(
         '-h', '--help', action='help', help='Show this help message and exit'
     )
@@ -363,13 +383,33 @@ def make_parsers():
     prune_group.add_argument(
         '--list', dest='list_archives', action='store_true', help='List archives kept/pruned'
     )
+    prune_group.add_argument(
+        '--oldest',
+        metavar='TIMESPAN',
+        help='Prune archives within a specified time range starting from the timestamp of the oldest archive (e.g. 7d or 12m) [Borg 2.x+ only]',
+    )
+    prune_group.add_argument(
+        '--newest',
+        metavar='TIMESPAN',
+        help='Prune archives within a time range that ends at timestamp of the newest archive and starts a specified time range ago (e.g. 7d or 12m) [Borg 2.x+ only]',
+    )
+    prune_group.add_argument(
+        '--older',
+        metavar='TIMESPAN',
+        help='Prune archives that are older than the specified time range (e.g. 7d or 12m) from the current time [Borg 2.x+ only]',
+    )
+    prune_group.add_argument(
+        '--newer',
+        metavar='TIMESPAN',
+        help='Prune archives that are newer than the specified time range (e.g. 7d or 12m) from the current time [Borg 2.x+ only]',
+    )
     prune_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
 
     compact_parser = subparsers.add_parser(
         'compact',
         aliases=SUBPARSER_ALIASES['compact'],
-        help='Compact segments to free space (Borg 1.2+, borgmatic 1.5.23+ only)',
-        description='Compact segments to free space (Borg 1.2+, borgmatic 1.5.23+ only)',
+        help='Compact segments to free space [Borg 1.2+, borgmatic 1.5.23+ only]',
+        description='Compact segments to free space [Borg 1.2+, borgmatic 1.5.23+ only]',
         add_help=False,
     )
     compact_group = compact_parser.add_argument_group('compact arguments')
@@ -389,7 +429,7 @@ def make_parsers():
         dest='cleanup_commits',
         default=False,
         action='store_true',
-        help='Cleanup commit-only 17-byte segment files left behind by Borg 1.1 (flag in Borg 1.2 only)',
+        help='Cleanup commit-only 17-byte segment files left behind by Borg 1.1 [flag in Borg 1.2 only]',
     )
     compact_group.add_argument(
         '--threshold',
@@ -603,6 +643,34 @@ def make_parsers():
         action='store_true',
         help='Stay in foreground until ctrl-C is pressed',
     )
+    mount_group.add_argument(
+        '--first',
+        metavar='N',
+        help='Mount first N archives after other filters are applied',
+    )
+    mount_group.add_argument(
+        '--last', metavar='N', help='Mount last N archives after other filters are applied'
+    )
+    mount_group.add_argument(
+        '--oldest',
+        metavar='TIMESPAN',
+        help='Mount archives within a specified time range starting from the timestamp of the oldest archive (e.g. 7d or 12m) [Borg 2.x+ only]',
+    )
+    mount_group.add_argument(
+        '--newest',
+        metavar='TIMESPAN',
+        help='Mount archives within a time range that ends at timestamp of the newest archive and starts a specified time range ago (e.g. 7d or 12m) [Borg 2.x+ only]',
+    )
+    mount_group.add_argument(
+        '--older',
+        metavar='TIMESPAN',
+        help='Mount archives that are older than the specified time range (e.g. 7d or 12m) from the current time [Borg 2.x+ only]',
+    )
+    mount_group.add_argument(
+        '--newer',
+        metavar='TIMESPAN',
+        help='Mount archives that are newer than the specified time range (e.g. 7d or 12m) from the current time [Borg 2.x+ only]',
+    )
     mount_group.add_argument('--options', dest='options', help='Extra Borg mount options')
     mount_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
 
@@ -694,6 +762,26 @@ def make_parsers():
     rlist_group.add_argument(
         '--last', metavar='N', help='List last N archives after other filters are applied'
     )
+    rlist_group.add_argument(
+        '--oldest',
+        metavar='TIMESPAN',
+        help='List archives within a specified time range starting from the timestamp of the oldest archive (e.g. 7d or 12m) [Borg 2.x+ only]',
+    )
+    rlist_group.add_argument(
+        '--newest',
+        metavar='TIMESPAN',
+        help='List archives within a time range that ends at timestamp of the newest archive and starts a specified time range ago (e.g. 7d or 12m) [Borg 2.x+ only]',
+    )
+    rlist_group.add_argument(
+        '--older',
+        metavar='TIMESPAN',
+        help='List archives that are older than the specified time range (e.g. 7d or 12m) from the current time [Borg 2.x+ only]',
+    )
+    rlist_group.add_argument(
+        '--newer',
+        metavar='TIMESPAN',
+        help='List archives that are newer than the specified time range (e.g. 7d or 12m) from the current time [Borg 2.x+ only]',
+    )
     rlist_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
 
     list_parser = subparsers.add_parser(
@@ -825,6 +913,26 @@ def make_parsers():
     info_group.add_argument(
         '--last', metavar='N', help='Show info for last N archives after other filters are applied'
     )
+    info_group.add_argument(
+        '--oldest',
+        metavar='TIMESPAN',
+        help='Show info for archives within a specified time range starting from the timestamp of the oldest archive (e.g. 7d or 12m) [Borg 2.x+ only]',
+    )
+    info_group.add_argument(
+        '--newest',
+        metavar='TIMESPAN',
+        help='Show info for archives within a time range that ends at timestamp of the newest archive and starts a specified time range ago (e.g. 7d or 12m) [Borg 2.x+ only]',
+    )
+    info_group.add_argument(
+        '--older',
+        metavar='TIMESPAN',
+        help='Show info for archives that are older than the specified time range (e.g. 7d or 12m) from the current time [Borg 2.x+ only]',
+    )
+    info_group.add_argument(
+        '--newer',
+        metavar='TIMESPAN',
+        help='Show info for archives that are newer than the specified time range (e.g. 7d or 12m) from the current time [Borg 2.x+ only]',
+    )
     info_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
 
     break_lock_parser = subparsers.add_parser(

+ 21 - 0
tests/integration/borg/test_commands.py

@@ -124,3 +124,24 @@ def test_display_archives_info_command_does_not_duplicate_flags_or_raise():
             fuzz_argument(arguments, argument_name),
             argparse.Namespace(log_json=False),
         )
+
+
+def test_prune_archives_command_does_not_duplicate_flags_or_raise():
+    arguments = borgmatic.commands.arguments.parse_arguments('prune')['prune']
+    flexmock(borgmatic.borg.prune).should_receive('execute_command').replace_with(
+        assert_command_does_not_duplicate_flags
+    )
+
+    for argument_name in dir(arguments):
+        if argument_name.startswith('_'):
+            continue
+
+        borgmatic.borg.prune.prune_archives(
+            False,
+            'repo',
+            {},
+            {},
+            '2.3.4',
+            fuzz_argument(arguments, argument_name),
+            argparse.Namespace(log_json=False),
+        )

+ 50 - 0
tests/unit/borg/test_info.py

@@ -478,3 +478,53 @@ def test_display_archives_info_passes_through_arguments_to_borg(argument_name):
             archive=None, json=False, prefix=None, match_archives=None, **{argument_name: 'value'}
         ),
     )
+
+
+def test_display_archives_info_with_date_based_matching_calls_borg_with_date_based_flags():
+    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(
+        None, None, '2.3.4'
+    ).and_return(())
+    flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
+        ('--newer', '1d', '--newest', '1y', '--older', '1m', '--oldest', '1w')
+    )
+    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',
+            '--newer',
+            '1d',
+            '--newest',
+            '1y',
+            '--older',
+            '1m',
+            '--oldest',
+            '1w',
+            '--repo',
+            'repo',
+        ),
+        output_log_level=module.borgmatic.logger.ANSWER,
+        borg_local_path='borg',
+        extra_environment=None,
+    )
+    info_arguments = flexmock(
+        archive=None,
+        json=False,
+        prefix=None,
+        match_archives=None,
+        newer='1d',
+        newest='1y',
+        older='1m',
+        oldest='1w',
+    )
+    module.display_archives_info(
+        repository_path='repo',
+        storage_config={},
+        local_borg_version='2.3.4',
+        global_arguments=flexmock(log_json=False),
+        info_arguments=info_arguments,
+    )

+ 86 - 45
tests/unit/borg/test_mount.py

@@ -21,13 +21,11 @@ def test_mount_archive_calls_borg_with_required_flags():
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(('borg', 'mount', 'repo', '/mnt'))
 
+    mount_arguments = flexmock(mount_point='/mnt', options=None, paths=None, foreground=False)
     module.mount_archive(
         repository_path='repo',
         archive=None,
-        mount_point='/mnt',
-        paths=None,
-        foreground=False,
-        options=None,
+        mount_arguments=mount_arguments,
         storage_config={},
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
@@ -46,13 +44,11 @@ def test_mount_archive_with_borg_features_calls_borg_with_repository_and_match_a
         ('borg', 'mount', '--repo', 'repo', '--match-archives', 'archive', '/mnt')
     )
 
+    mount_arguments = flexmock(mount_point='/mnt', options=None, paths=None, foreground=False)
     module.mount_archive(
         repository_path='repo',
         archive='archive',
-        mount_point='/mnt',
-        paths=None,
-        foreground=False,
-        options=None,
+        mount_arguments=mount_arguments,
         storage_config={},
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
@@ -66,13 +62,11 @@ def test_mount_archive_without_archive_calls_borg_with_repository_flags_only():
     )
     insert_execute_command_mock(('borg', 'mount', 'repo::archive', '/mnt'))
 
+    mount_arguments = flexmock(mount_point='/mnt', options=None, paths=None, foreground=False)
     module.mount_archive(
         repository_path='repo',
         archive='archive',
-        mount_point='/mnt',
-        paths=None,
-        foreground=False,
-        options=None,
+        mount_arguments=mount_arguments,
         storage_config={},
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
@@ -86,13 +80,13 @@ def test_mount_archive_calls_borg_with_path_flags():
     )
     insert_execute_command_mock(('borg', 'mount', 'repo::archive', '/mnt', 'path1', 'path2'))
 
+    mount_arguments = flexmock(
+        mount_point='/mnt', options=None, paths=['path1', 'path2'], foreground=False
+    )
     module.mount_archive(
         repository_path='repo',
         archive='archive',
-        mount_point='/mnt',
-        paths=['path1', 'path2'],
-        foreground=False,
-        options=None,
+        mount_arguments=mount_arguments,
         storage_config={},
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
@@ -108,13 +102,11 @@ def test_mount_archive_calls_borg_with_remote_path_flags():
         ('borg', 'mount', '--remote-path', 'borg1', 'repo::archive', '/mnt')
     )
 
+    mount_arguments = flexmock(mount_point='/mnt', options=None, paths=None, foreground=False)
     module.mount_archive(
         repository_path='repo',
         archive='archive',
-        mount_point='/mnt',
-        paths=None,
-        foreground=False,
-        options=None,
+        mount_arguments=mount_arguments,
         storage_config={},
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
@@ -129,13 +121,11 @@ def test_mount_archive_calls_borg_with_umask_flags():
     )
     insert_execute_command_mock(('borg', 'mount', '--umask', '0770', 'repo::archive', '/mnt'))
 
+    mount_arguments = flexmock(mount_point='/mnt', options=None, paths=None, foreground=False)
     module.mount_archive(
         repository_path='repo',
         archive='archive',
-        mount_point='/mnt',
-        paths=None,
-        foreground=False,
-        options=None,
+        mount_arguments=mount_arguments,
         storage_config={'umask': '0770'},
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
@@ -149,13 +139,11 @@ def test_mount_archive_calls_borg_with_log_json_flags():
     )
     insert_execute_command_mock(('borg', 'mount', '--log-json', 'repo::archive', '/mnt'))
 
+    mount_arguments = flexmock(mount_point='/mnt', options=None, paths=None, foreground=False)
     module.mount_archive(
         repository_path='repo',
         archive='archive',
-        mount_point='/mnt',
-        paths=None,
-        foreground=False,
-        options=None,
+        mount_arguments=mount_arguments,
         storage_config={},
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=True),
@@ -169,13 +157,11 @@ def test_mount_archive_calls_borg_with_lock_wait_flags():
     )
     insert_execute_command_mock(('borg', 'mount', '--lock-wait', '5', 'repo::archive', '/mnt'))
 
+    mount_arguments = flexmock(mount_point='/mnt', options=None, paths=None, foreground=False)
     module.mount_archive(
         repository_path='repo',
         archive='archive',
-        mount_point='/mnt',
-        paths=None,
-        foreground=False,
-        options=None,
+        mount_arguments=mount_arguments,
         storage_config={'lock_wait': '5'},
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
@@ -190,13 +176,11 @@ def test_mount_archive_with_log_info_calls_borg_with_info_parameter():
     insert_execute_command_mock(('borg', 'mount', '--info', 'repo::archive', '/mnt'))
     insert_logging_mock(logging.INFO)
 
+    mount_arguments = flexmock(mount_point='/mnt', options=None, paths=None, foreground=False)
     module.mount_archive(
         repository_path='repo',
         archive='archive',
-        mount_point='/mnt',
-        paths=None,
-        foreground=False,
-        options=None,
+        mount_arguments=mount_arguments,
         storage_config={},
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
@@ -211,13 +195,11 @@ def test_mount_archive_with_log_debug_calls_borg_with_debug_flags():
     insert_execute_command_mock(('borg', 'mount', '--debug', '--show-rc', 'repo::archive', '/mnt'))
     insert_logging_mock(logging.DEBUG)
 
+    mount_arguments = flexmock(mount_point='/mnt', options=None, paths=None, foreground=False)
     module.mount_archive(
         repository_path='repo',
         archive='archive',
-        mount_point='/mnt',
-        paths=None,
-        foreground=False,
-        options=None,
+        mount_arguments=mount_arguments,
         storage_config={},
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
@@ -237,13 +219,11 @@ def test_mount_archive_calls_borg_with_foreground_parameter():
         extra_environment=None,
     ).once()
 
+    mount_arguments = flexmock(mount_point='/mnt', options=None, paths=None, foreground=True)
     module.mount_archive(
         repository_path='repo',
         archive='archive',
-        mount_point='/mnt',
-        paths=None,
-        foreground=True,
-        options=None,
+        mount_arguments=mount_arguments,
         storage_config={},
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
@@ -257,13 +237,74 @@ def test_mount_archive_calls_borg_with_options_flags():
     )
     insert_execute_command_mock(('borg', 'mount', '-o', 'super_mount', 'repo::archive', '/mnt'))
 
+    mount_arguments = flexmock(
+        mount_point='/mnt', options='super_mount', paths=None, foreground=False
+    )
     module.mount_archive(
         repository_path='repo',
         archive='archive',
+        mount_arguments=mount_arguments,
+        storage_config={},
+        local_borg_version='1.2.3',
+        global_arguments=flexmock(log_json=False),
+    )
+
+
+def test_mount_archive_with_date_based_matching_calls_borg_with_date_based_flags():
+    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(
+        (
+            '--newer',
+            '1d',
+            '--newest',
+            '1y',
+            '--older',
+            '1m',
+            '--oldest',
+            '1w',
+            '--match-archives',
+            None,
+        )
+    )
+    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',
+            'mount',
+            '--newer',
+            '1d',
+            '--newest',
+            '1y',
+            '--older',
+            '1m',
+            '--oldest',
+            '1w',
+            '--match-archives',
+            None,
+            '--repo',
+            'repo',
+            '/mnt',
+        ),
+        borg_local_path='borg',
+        extra_environment=None,
+    )
+
+    mount_arguments = flexmock(
         mount_point='/mnt',
+        options=None,
         paths=None,
         foreground=False,
-        options='super_mount',
+        newer='1d',
+        newest='1y',
+        older='1m',
+        oldest='1w',
+    )
+    module.mount_archive(
+        repository_path='repo',
+        archive=None,
+        mount_arguments=mount_arguments,
         storage_config={},
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),

+ 88 - 2
tests/unit/borg/test_prune.py

@@ -117,6 +117,7 @@ def test_prune_archives_calls_borg_with_flags():
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(PRUNE_COMMAND + ('repo',), logging.INFO)
 
+    prune_arguments = flexmock(stats=False, list_archives=False)
     module.prune_archives(
         dry_run=False,
         repository_path='repo',
@@ -124,6 +125,7 @@ def test_prune_archives_calls_borg_with_flags():
         retention_config=flexmock(),
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
+        prune_arguments=prune_arguments,
     )
 
 
@@ -135,6 +137,7 @@ def test_prune_archives_with_log_info_calls_borg_with_info_flag():
     insert_execute_command_mock(PRUNE_COMMAND + ('--info', 'repo'), logging.INFO)
     insert_logging_mock(logging.INFO)
 
+    prune_arguments = flexmock(stats=False, list_archives=False)
     module.prune_archives(
         repository_path='repo',
         storage_config={},
@@ -142,6 +145,7 @@ def test_prune_archives_with_log_info_calls_borg_with_info_flag():
         retention_config=flexmock(),
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
+        prune_arguments=prune_arguments,
     )
 
 
@@ -153,6 +157,7 @@ def test_prune_archives_with_log_debug_calls_borg_with_debug_flag():
     insert_execute_command_mock(PRUNE_COMMAND + ('--debug', '--show-rc', 'repo'), logging.INFO)
     insert_logging_mock(logging.DEBUG)
 
+    prune_arguments = flexmock(stats=False, list_archives=False)
     module.prune_archives(
         repository_path='repo',
         storage_config={},
@@ -160,6 +165,7 @@ def test_prune_archives_with_log_debug_calls_borg_with_debug_flag():
         retention_config=flexmock(),
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
+        prune_arguments=prune_arguments,
     )
 
 
@@ -170,6 +176,7 @@ def test_prune_archives_with_dry_run_calls_borg_with_dry_run_flag():
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(PRUNE_COMMAND + ('--dry-run', 'repo'), logging.INFO)
 
+    prune_arguments = flexmock(stats=False, list_archives=False)
     module.prune_archives(
         repository_path='repo',
         storage_config={},
@@ -177,6 +184,7 @@ def test_prune_archives_with_dry_run_calls_borg_with_dry_run_flag():
         retention_config=flexmock(),
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
+        prune_arguments=prune_arguments,
     )
 
 
@@ -187,6 +195,7 @@ def test_prune_archives_with_local_path_calls_borg_via_local_path():
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(('borg1',) + PRUNE_COMMAND[1:] + ('repo',), logging.INFO)
 
+    prune_arguments = flexmock(stats=False, list_archives=False)
     module.prune_archives(
         dry_run=False,
         repository_path='repo',
@@ -195,6 +204,7 @@ def test_prune_archives_with_local_path_calls_borg_via_local_path():
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         local_path='borg1',
+        prune_arguments=prune_arguments,
     )
 
 
@@ -205,6 +215,7 @@ def test_prune_archives_with_remote_path_calls_borg_with_remote_path_flags():
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(PRUNE_COMMAND + ('--remote-path', 'borg1', 'repo'), logging.INFO)
 
+    prune_arguments = flexmock(stats=False, list_archives=False)
     module.prune_archives(
         dry_run=False,
         repository_path='repo',
@@ -213,6 +224,7 @@ def test_prune_archives_with_remote_path_calls_borg_with_remote_path_flags():
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         remote_path='borg1',
+        prune_arguments=prune_arguments,
     )
 
 
@@ -223,6 +235,7 @@ def test_prune_archives_with_stats_calls_borg_with_stats_flag_and_answer_output_
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(PRUNE_COMMAND + ('--stats', 'repo'), module.borgmatic.logger.ANSWER)
 
+    prune_arguments = flexmock(stats=True, list_archives=False)
     module.prune_archives(
         dry_run=False,
         repository_path='repo',
@@ -230,7 +243,7 @@ def test_prune_archives_with_stats_calls_borg_with_stats_flag_and_answer_output_
         retention_config=flexmock(),
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
-        stats=True,
+        prune_arguments=prune_arguments,
     )
 
 
@@ -241,6 +254,7 @@ def test_prune_archives_with_files_calls_borg_with_list_flag_and_answer_output_l
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(PRUNE_COMMAND + ('--list', 'repo'), module.borgmatic.logger.ANSWER)
 
+    prune_arguments = flexmock(stats=False, list_archives=True)
     module.prune_archives(
         dry_run=False,
         repository_path='repo',
@@ -248,7 +262,7 @@ def test_prune_archives_with_files_calls_borg_with_list_flag_and_answer_output_l
         retention_config=flexmock(),
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
-        list_archives=True,
+        prune_arguments=prune_arguments,
     )
 
 
@@ -260,6 +274,7 @@ def test_prune_archives_with_umask_calls_borg_with_umask_flags():
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(PRUNE_COMMAND + ('--umask', '077', 'repo'), logging.INFO)
 
+    prune_arguments = flexmock(stats=False, list_archives=False)
     module.prune_archives(
         dry_run=False,
         repository_path='repo',
@@ -267,6 +282,7 @@ def test_prune_archives_with_umask_calls_borg_with_umask_flags():
         retention_config=flexmock(),
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
+        prune_arguments=prune_arguments,
     )
 
 
@@ -277,6 +293,7 @@ def test_prune_archives_with_log_json_calls_borg_with_log_json_flag():
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(PRUNE_COMMAND + ('--log-json', 'repo'), logging.INFO)
 
+    prune_arguments = flexmock(stats=False, list_archives=False)
     module.prune_archives(
         dry_run=False,
         repository_path='repo',
@@ -284,6 +301,7 @@ def test_prune_archives_with_log_json_calls_borg_with_log_json_flag():
         retention_config=flexmock(),
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=True),
+        prune_arguments=prune_arguments,
     )
 
 
@@ -295,6 +313,7 @@ def test_prune_archives_with_lock_wait_calls_borg_with_lock_wait_flags():
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(PRUNE_COMMAND + ('--lock-wait', '5', 'repo'), logging.INFO)
 
+    prune_arguments = flexmock(stats=False, list_archives=False)
     module.prune_archives(
         dry_run=False,
         repository_path='repo',
@@ -302,6 +321,7 @@ def test_prune_archives_with_lock_wait_calls_borg_with_lock_wait_flags():
         retention_config=flexmock(),
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
+        prune_arguments=prune_arguments,
     )
 
 
@@ -312,6 +332,7 @@ def test_prune_archives_with_extra_borg_options_calls_borg_with_extra_options():
     flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
     insert_execute_command_mock(PRUNE_COMMAND + ('--extra', '--options', 'repo'), logging.INFO)
 
+    prune_arguments = flexmock(stats=False, list_archives=False)
     module.prune_archives(
         dry_run=False,
         repository_path='repo',
@@ -319,4 +340,69 @@ def test_prune_archives_with_extra_borg_options_calls_borg_with_extra_options():
         retention_config=flexmock(),
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
+        prune_arguments=prune_arguments,
+    )
+
+
+def test_prune_archives_with_date_based_matching_calls_borg_with_date_based_flags():
+    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').and_return(())
+    flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
+    flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
+        (
+            '--newer',
+            '1d',
+            '--newest',
+            '1y',
+            '--older',
+            '1m',
+            '--oldest',
+            '1w',
+            '--match-archives',
+            None,
+        )
+    )
+    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',
+            'prune',
+            '--keep-daily',
+            '1',
+            '--keep-weekly',
+            '2',
+            '--keep-monthly',
+            '3',
+            '--newer',
+            '1d',
+            '--newest',
+            '1y',
+            '--older',
+            '1m',
+            '--oldest',
+            '1w',
+            '--match-archives',
+            None,
+            '--repo',
+            'repo',
+        ),
+        output_log_level=logging.INFO,
+        borg_local_path='borg',
+        extra_environment=None,
+    )
+
+    prune_arguments = flexmock(
+        stats=False, list_archives=False, newer='1d', newest='1y', older='1m', oldest='1w'
+    )
+    module.prune_archives(
+        dry_run=False,
+        repository_path='repo',
+        storage_config={},
+        retention_config=flexmock(),
+        local_borg_version='1.2.3',
+        global_arguments=flexmock(log_json=False),
+        prune_arguments=prune_arguments,
     )

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

@@ -614,3 +614,46 @@ def test_list_repository_with_json_returns_borg_output():
         )
         == json_output
     )
+
+
+def test_make_rlist_command_with_date_based_matching_calls_borg_with_date_based_flags():
+    flexmock(module.flags).should_receive('make_flags').and_return(())
+    flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
+        None, None, '1.2.3'
+    ).and_return(())
+    flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
+        ('--newer', '1d', '--newest', '1y', '--older', '1m', '--oldest', '1w')
+    )
+    flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
+
+    command = module.make_rlist_command(
+        repository_path='repo',
+        storage_config={},
+        local_borg_version='1.2.3',
+        rlist_arguments=flexmock(
+            archive=None,
+            paths=None,
+            json=False,
+            prefix=None,
+            match_archives=None,
+            newer='1d',
+            newest='1y',
+            older='1m',
+            oldest='1w',
+        ),
+        global_arguments=flexmock(log_json=False),
+    )
+
+    assert command == (
+        'borg',
+        'list',
+        '--newer',
+        '1d',
+        '--newest',
+        '1y',
+        '--older',
+        '1m',
+        '--oldest',
+        '1w',
+        'repo',
+    )

+ 48 - 0
tests/unit/borg/test_transfer.py

@@ -430,3 +430,51 @@ def test_transfer_archives_with_source_repository_calls_borg_with_other_repo_fla
         ),
         global_arguments=flexmock(log_json=False),
     )
+
+
+def test_transfer_archives_with_date_based_matching_calls_borg_with_date_based_flags():
+    flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
+    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(
+        ('--newer', '1d', '--newest', '1y', '--older', '1m', '--oldest', '1w')
+    )
+    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',
+            '--newer',
+            '1d',
+            '--newest',
+            '1y',
+            '--older',
+            '1m',
+            '--oldest',
+            '1w',
+            '--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={},
+        local_borg_version='2.3.4',
+        global_arguments=flexmock(log_json=False),
+        transfer_arguments=flexmock(
+            archive=None,
+            progress=None,
+            source_repository='other',
+            newer='1d',
+            newest='1y',
+            older='1m',
+            oldest='1w',
+        ),
+    )