diff --git a/borgmatic/borg/info.py b/borgmatic/borg/info.py index 9e56f0ede..64ee89a9a 100644 --- a/borgmatic/borg/info.py +++ b/borgmatic/borg/info.py @@ -1,7 +1,6 @@ import logging -from borgmatic.borg import environment, feature -from borgmatic.borg.flags import make_flags, make_flags_from_arguments +from borgmatic.borg import environment, feature, flags from borgmatic.execute import execute_command logger = logging.getLogger(__name__) @@ -34,13 +33,20 @@ def display_archives_info( if logger.isEnabledFor(logging.DEBUG) and not info_arguments.json else () ) - + make_flags('remote-path', remote_path) - + make_flags('lock-wait', lock_wait) - + make_flags_from_arguments(info_arguments, excludes=('repository', 'archive')) + + flags.make_flags('remote-path', remote_path) + + flags.make_flags('lock-wait', lock_wait) + + ( + flags.make_flags('glob-archives', f'{info_arguments.prefix}*') + if info_arguments.prefix + else () + ) + + flags.make_flags_from_arguments( + info_arguments, excludes=('repository', 'archive', 'prefix') + ) + ( ( - ('--repo', repository) - + (('--glob-archives', info_arguments.archive) if info_arguments.archive else ()) + flags.make_flags('repo', repository) + + flags.make_flags('glob-archives', info_arguments.archive) ) if feature.available(feature.Feature.SEPARATE_REPOSITORY_ARCHIVE, local_borg_version) else ( diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 8a2ed346f..d817f26e1 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -745,22 +745,26 @@ def parse_arguments(*unparsed_arguments): if arguments['global'].excludes_filename: raise ValueError( - 'The --excludes flag has been replaced with exclude_patterns in configuration' + 'The --excludes flag has been replaced with exclude_patterns in configuration.' ) if 'rcreate' in arguments and arguments['global'].dry_run: - raise ValueError('The rcreate/init action cannot be used with the --dry-run flag') + raise ValueError('The rcreate/init action cannot be used with the --dry-run flag.') if ( ('list' in arguments and 'rinfo' in arguments and arguments['list'].json) or ('list' in arguments and 'info' in arguments and arguments['list'].json) or ('rinfo' in arguments and 'info' in arguments and arguments['rinfo'].json) ): - raise ValueError('With the --json flag, multiple actions cannot be used together') + raise ValueError('With the --json flag, multiple actions cannot be used together.') - if 'info' in arguments and arguments['info'].archive and arguments['info'].glob_archives: + if 'info' in arguments and ( + (arguments['info'].archive and arguments['info'].prefix) + or (arguments['info'].archive and arguments['info'].glob_archives) + or (arguments['info'].prefix and arguments['info'].glob_archives) + ): raise ValueError( - 'With the info action, the --archive and --glob-archives flags cannot be used together' + 'With the info action, only one of --archive, --prefix, or --glob-archives flags can be used.' ) return arguments diff --git a/tests/integration/commands/test_arguments.py b/tests/integration/commands/test_arguments.py index a9e4f5cc8..b70135630 100644 --- a/tests/integration/commands/test_arguments.py +++ b/tests/integration/commands/test_arguments.py @@ -517,6 +517,20 @@ def test_parse_arguments_disallows_info_with_both_archive_and_glob_archives(): module.parse_arguments('info', '--archive', 'foo', '--glob-archives', '*bar') +def test_parse_arguments_disallows_info_with_both_archive_and_prefix(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + + with pytest.raises(ValueError): + module.parse_arguments('info', '--archive', 'foo', '--prefix', 'bar') + + +def test_parse_arguments_disallows_info_with_both_prefix_and_glob_archives(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + + with pytest.raises(ValueError): + module.parse_arguments('info', '--prefix', 'foo', '--glob-archives', '*bar') + + def test_parse_arguments_check_only_extract_does_not_raise_extract_subparser_error(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) diff --git a/tests/unit/borg/test_info.py b/tests/unit/borg/test_info.py index 8817bb7ee..0121d61d1 100644 --- a/tests/unit/borg/test_info.py +++ b/tests/unit/borg/test_info.py @@ -9,6 +9,11 @@ from ..test_verbosity import insert_logging_mock def test_display_archives_info_calls_borg_with_parameters(): + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags').with_args('repo', 'repo').and_return( + ('--repo', 'repo') + ) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(()) flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( @@ -22,11 +27,13 @@ def test_display_archives_info_calls_borg_with_parameters(): repository='repo', storage_config={}, local_borg_version='2.3.4', - info_arguments=flexmock(archive=None, json=False), + info_arguments=flexmock(archive=None, json=False, prefix=None), ) def test_display_archives_info_without_borg_features_calls_borg_without_repo_flag(): + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(()) flexmock(module.feature).should_receive('available').and_return(False) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( @@ -40,11 +47,16 @@ def test_display_archives_info_without_borg_features_calls_borg_without_repo_fla repository='repo', storage_config={}, local_borg_version='2.3.4', - info_arguments=flexmock(archive=None, json=False), + info_arguments=flexmock(archive=None, json=False, prefix=None), ) def test_display_archives_info_with_log_info_calls_borg_with_info_parameter(): + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags').with_args('repo', 'repo').and_return( + ('--repo', 'repo') + ) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(()) flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( @@ -58,11 +70,16 @@ def test_display_archives_info_with_log_info_calls_borg_with_info_parameter(): repository='repo', storage_config={}, local_borg_version='2.3.4', - info_arguments=flexmock(archive=None, json=False), + info_arguments=flexmock(archive=None, json=False, prefix=None), ) def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_output(): + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags').with_args('repo', 'repo').and_return( + ('--repo', 'repo') + ) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',)) flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( @@ -77,13 +94,18 @@ def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_outpu repository='repo', storage_config={}, local_borg_version='2.3.4', - info_arguments=flexmock(archive=None, json=True), + info_arguments=flexmock(archive=None, json=True, prefix=None), ) assert json_output == '[]' def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter(): + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags').with_args('repo', 'repo').and_return( + ('--repo', 'repo') + ) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(()) flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( @@ -98,11 +120,16 @@ def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter(): repository='repo', storage_config={}, local_borg_version='2.3.4', - info_arguments=flexmock(archive=None, json=False), + info_arguments=flexmock(archive=None, json=False, prefix=None), ) def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_output(): + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags').with_args('repo', 'repo').and_return( + ('--repo', 'repo') + ) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',)) flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( @@ -117,13 +144,18 @@ def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_outp repository='repo', storage_config={}, local_borg_version='2.3.4', - info_arguments=flexmock(archive=None, json=True), + info_arguments=flexmock(archive=None, json=True, prefix=None), ) assert json_output == '[]' def test_display_archives_info_with_json_calls_borg_with_json_parameter(): + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags').with_args('repo', 'repo').and_return( + ('--repo', 'repo') + ) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',)) flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( @@ -137,13 +169,21 @@ def test_display_archives_info_with_json_calls_borg_with_json_parameter(): repository='repo', storage_config={}, local_borg_version='2.3.4', - info_arguments=flexmock(archive=None, json=True), + info_arguments=flexmock(archive=None, json=True, prefix=None), ) assert json_output == '[]' def test_display_archives_info_with_archive_calls_borg_with_glob_archives_parameter(): + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags').with_args('repo', 'repo').and_return( + ('--repo', 'repo') + ) + flexmock(module.flags).should_receive('make_flags').with_args( + 'glob-archives', 'archive' + ).and_return(('--glob-archives', 'archive')) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(()) flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( @@ -157,11 +197,13 @@ def test_display_archives_info_with_archive_calls_borg_with_glob_archives_parame repository='repo', storage_config={}, local_borg_version='2.3.4', - info_arguments=flexmock(archive='archive', json=False), + info_arguments=flexmock(archive='archive', json=False, prefix=None), ) def test_display_archives_info_with_archive_and_without_borg_features_calls_borg_with_repo_archive_parameter(): + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(()) flexmock(module.feature).should_receive('available').and_return(False) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( @@ -175,11 +217,16 @@ def test_display_archives_info_with_archive_and_without_borg_features_calls_borg repository='repo', storage_config={}, local_borg_version='2.3.4', - info_arguments=flexmock(archive='archive', json=False), + info_arguments=flexmock(archive='archive', json=False, prefix=None), ) def test_display_archives_info_with_local_path_calls_borg_via_local_path(): + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags').with_args('repo', 'repo').and_return( + ('--repo', 'repo') + ) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(()) flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( @@ -193,12 +240,20 @@ def test_display_archives_info_with_local_path_calls_borg_via_local_path(): repository='repo', storage_config={}, local_borg_version='2.3.4', - info_arguments=flexmock(archive=None, json=False), + info_arguments=flexmock(archive=None, json=False, prefix=None), local_path='borg1', ) def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_parameters(): + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags').with_args('repo', 'repo').and_return( + ('--repo', 'repo') + ) + flexmock(module.flags).should_receive('make_flags').with_args( + 'remote-path', 'borg1' + ).and_return(('--remote-path', 'borg1')) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(()) flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( @@ -212,12 +267,20 @@ def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_para repository='repo', storage_config={}, local_borg_version='2.3.4', - info_arguments=flexmock(archive=None, json=False), + info_arguments=flexmock(archive=None, json=False, prefix=None), remote_path='borg1', ) def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_parameters(): + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags').with_args('repo', 'repo').and_return( + ('--repo', 'repo') + ) + flexmock(module.flags).should_receive('make_flags').with_args('lock-wait', 5).and_return( + ('--lock-wait', '5') + ) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(()) storage_config = {'lock_wait': 5} flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') @@ -232,16 +295,23 @@ def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_paramete repository='repo', storage_config=storage_config, local_borg_version='2.3.4', - info_arguments=flexmock(archive=None, json=False), + info_arguments=flexmock(archive=None, json=False, prefix=None), ) -@pytest.mark.parametrize('argument_name', ('prefix', 'glob_archives', 'sort_by', 'first', 'last')) -def test_display_archives_info_passes_through_arguments_to_borg(argument_name): +def test_display_archives_info_with_prefix_calls_borg_with_glob_archives_parameters(): + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags').with_args('repo', 'repo').and_return( + ('--repo', 'repo') + ) + flexmock(module.flags).should_receive('make_flags').with_args( + 'glob-archives', 'foo*' + ).and_return(('--glob-archives', 'foo*')) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(()) flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( - ('borg', 'info', '--' + argument_name.replace('_', '-'), 'value', '--repo', 'repo'), + ('borg', 'info', '--glob-archives', 'foo*', '--repo', 'repo'), output_log_level=logging.WARNING, borg_local_path='borg', extra_environment=None, @@ -251,5 +321,32 @@ def test_display_archives_info_passes_through_arguments_to_borg(argument_name): repository='repo', storage_config={}, local_borg_version='2.3.4', - info_arguments=flexmock(archive=None, json=False, **{argument_name: 'value'}), + info_arguments=flexmock(archive=None, json=False, prefix='foo'), + ) + + +@pytest.mark.parametrize('argument_name', ('glob_archives', 'sort_by', 'first', 'last')) +def test_display_archives_info_passes_through_arguments_to_borg(argument_name): + flag_name = f"--{argument_name.replace('_', ' ')}" + flexmock(module.flags).should_receive('make_flags').and_return(()) + flexmock(module.flags).should_receive('make_flags').with_args('repo', 'repo').and_return( + ('--repo', 'repo') + ) + flexmock(module.flags).should_receive('make_flags_from_arguments').and_return( + (flag_name, 'value') + ) + flexmock(module.feature).should_receive('available').and_return(True) + flexmock(module.environment).should_receive('make_environment') + flexmock(module).should_receive('execute_command').with_args( + ('borg', 'info', flag_name, 'value', '--repo', 'repo'), + output_log_level=logging.WARNING, + borg_local_path='borg', + extra_environment=None, + ) + + module.display_archives_info( + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + info_arguments=flexmock(archive=None, json=False, prefix=None, **{argument_name: 'value'}), )