diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 0522406..f414453 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -15,6 +15,7 @@ SUBPARSER_ALIASES = { 'umount': ['--umount', '-u'], 'restore': ['--restore', '-r'], 'list': ['--list', '-l'], + 'rinfo': [], 'info': ['--info', '-i'], 'borg': [], } @@ -613,17 +614,34 @@ def make_parsers(): ) list_group.add_argument('-h', '--help', action='help', help='Show this help message and exit') + rinfo_parser = subparsers.add_parser( + 'rinfo', + aliases=SUBPARSER_ALIASES['rinfo'], + help='Show repository summary information such as disk space used', + description='Show repository summary information such as disk space used', + add_help=False, + ) + rinfo_group = rinfo_parser.add_argument_group('rinfo arguments') + rinfo_group.add_argument( + '--repository', + help='Path of repository to show info for, defaults to the configured repository if there is only one', + ) + rinfo_group.add_argument( + '--json', dest='json', default=False, action='store_true', help='Output results as JSON' + ) + rinfo_group.add_argument('-h', '--help', action='help', help='Show this help message and exit') + info_parser = subparsers.add_parser( 'info', aliases=SUBPARSER_ALIASES['info'], - help='Display summary information on archives', - description='Display summary information on archives', + help='Show archive summary information such as disk space used', + description='Show archive summary information such as disk space used', add_help=False, ) info_group = info_parser.add_argument_group('info arguments') info_group.add_argument( '--repository', - help='Path of repository to show info for, defaults to the configured repository if there is only one', + help='Path of repository containing archive to show info for, defaults to the configured repository if there is only one', ) info_group.add_argument('--archive', help='Name of archive to show info for (or "latest")') info_group.add_argument( @@ -697,12 +715,11 @@ def parse_arguments(*unparsed_arguments): raise ValueError('The rcreate/init action cannot be used with the --dry-run flag') if ( - 'list' in arguments - and 'info' in arguments - and arguments['list'].json - and arguments['info'].json + ('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, list and info 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: raise ValueError( diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 494f41f..d9901a2 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -24,6 +24,7 @@ from borgmatic.borg import list as borg_list from borgmatic.borg import mount as borg_mount from borgmatic.borg import prune as borg_prune from borgmatic.borg import rcreate as borg_rcreate +from borgmatic.borg import rinfo as borg_rinfo from borgmatic.borg import umount as borg_umount from borgmatic.borg import version as borg_version from borgmatic.commands.arguments import parse_arguments @@ -613,13 +614,30 @@ def run_actions( ) if json_output: # pragma: nocover yield json.loads(json_output) + if 'rinfo' in arguments: + if arguments['rinfo'].repository is None or validate.repositories_match( + repository, arguments['rinfo'].repository + ): + rinfo_arguments = copy.copy(arguments['rinfo']) + if not rinfo_arguments.json: # pragma: nocover + logger.warning('{}: Displaying repository summary information'.format(repository)) + json_output = borg_rinfo.display_repository_info( + repository, + storage, + local_borg_version, + rinfo_arguments=rinfo_arguments, + local_path=local_path, + remote_path=remote_path, + ) + if json_output: # pragma: nocover + yield json.loads(json_output) if 'info' in arguments: if arguments['info'].repository is None or validate.repositories_match( repository, arguments['info'].repository ): info_arguments = copy.copy(arguments['info']) if not info_arguments.json: # pragma: nocover - logger.warning('{}: Displaying summary info for archives'.format(repository)) + logger.warning('{}: Displaying archive summary information'.format(repository)) info_arguments.archive = borg_list.resolve_archive_name( repository, info_arguments.archive, storage, local_path, remote_path ) diff --git a/tests/integration/commands/test_arguments.py b/tests/integration/commands/test_arguments.py index 5eb5101..a9e4f5c 100644 --- a/tests/integration/commands/test_arguments.py +++ b/tests/integration/commands/test_arguments.py @@ -496,6 +496,20 @@ def test_parse_arguments_disallows_json_with_both_list_and_info(): module.parse_arguments('list', 'info', '--json') +def test_parse_arguments_disallows_json_with_both_list_and_rinfo(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + + with pytest.raises(ValueError): + module.parse_arguments('list', 'rinfo', '--json') + + +def test_parse_arguments_disallows_json_with_both_rinfo_and_info(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + + with pytest.raises(ValueError): + module.parse_arguments('rinfo', 'info', '--json') + + def test_parse_arguments_disallows_info_with_both_archive_and_glob_archives(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) diff --git a/tests/unit/commands/test_borgmatic.py b/tests/unit/commands/test_borgmatic.py index 57ce1b4..e17c20e 100644 --- a/tests/unit/commands/test_borgmatic.py +++ b/tests/unit/commands/test_borgmatic.py @@ -597,6 +597,31 @@ def test_run_actions_does_not_raise_for_list_action(): ) +def test_run_actions_does_not_raise_for_rinfo_action(): + flexmock(module.validate).should_receive('repositories_match').and_return(True) + flexmock(module.borg_rinfo).should_receive('display_repository_info') + arguments = { + 'global': flexmock(monitoring_verbosity=1, dry_run=False), + 'rinfo': flexmock(repository=flexmock(), json=flexmock()), + } + + list( + module.run_actions( + arguments=arguments, + config_filename='test.yaml', + location={'repositories': ['repo']}, + storage={}, + retention={}, + consistency={}, + hooks={}, + local_path=None, + remote_path=None, + local_borg_version=None, + repository_path='repo', + ) + ) + + def test_run_actions_does_not_raise_for_info_action(): flexmock(module.validate).should_receive('repositories_match').and_return(True) flexmock(module.borg_list).should_receive('resolve_archive_name').and_return(flexmock())