From fa099b84713052e572fbcd3071da2a0b22c9b656 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Thu, 11 Jun 2026 15:57:52 -0700 Subject: [PATCH] Add support for the "--quick-stats" flag and the "quick_statistics" option to the "prune" action (#1309). --- NEWS | 2 ++ borgmatic/borg/prune.py | 17 ++++++++++-- borgmatic/commands/arguments.py | 7 +++++ borgmatic/config/schema.yaml | 5 ++-- tests/unit/borg/test_prune.py | 48 +++++++++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 495d6086..52ac2b04 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ 2.1.7.dev0 + * #1309: Add support for the "--quick-stats" flag and the "quick_statistics" option to the "prune" + action. Borg >= 1.4.5 and < 2 only. * #1319: Fix the ZFS hook's overzealous unmounting of snapshot paths when a source dataset is at "/". * Fix a bug in which the "compact" action does not pass a compact threshold of zero to Borg. diff --git a/borgmatic/borg/prune.py b/borgmatic/borg/prune.py index 0bdbcaf0..7789ee2e 100644 --- a/borgmatic/borg/prune.py +++ b/borgmatic/borg/prune.py @@ -83,10 +83,23 @@ def prune_archives( and not feature.available(feature.Feature.NO_PRUNE_STATS, local_borg_version) else () ) + + ( + ('--quick-stats',) + if config.get('quick_statistics') + and not dry_run + and not feature.available(feature.Feature.NO_PRUNE_STATS, local_borg_version) + else () + ) + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) + flags.make_flags_from_arguments( prune_arguments, - excludes=('repository', 'match_archives', 'statistics', 'list_details'), + excludes=( + 'repository', + 'match_archives', + 'statistics', + 'quick_statistics', + 'list_details', + ), ) + (('--list',) if config.get('list_details') else ()) + (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ()) @@ -95,7 +108,7 @@ def prune_archives( + flags.make_repository_flags(repository_path, local_borg_version) ) - if config.get('statistics') or config.get('list_details'): + if config.get('statistics') or config.get('quick_statistics') or config.get('list_details'): output_log_level = logging.ANSWER else: output_log_level = logging.INFO diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 25c49f7a..0cf3a15c 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -805,6 +805,13 @@ def make_parsers(schema, unparsed_arguments): # noqa: PLR0915 action='store_true', help='Display statistics of the pruned archive [Borg 1 only]', ) + prune_group.add_argument( + '--quick-stats', + dest='quick_statistics', + default=None, + action='store_true', + help='Display statistics of the pruned archive, skipping repository-wide "All archives" and chunk index statistics [Borg >= 1.4.5 and < 2 only]', + ) prune_group.add_argument( '--list', dest='list_details', diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index b5b16586..da5c1eae 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1097,8 +1097,9 @@ properties: Display statistics for an archive when running supported actions, skipping the repository-wide "All archives" and chunk index statistics to save some time. Corresponds to the "--quick-stats" - flag on those actions. Defaults to false. (This option is supported - for Borg 1.4.5+ only.) + flag on those actions. Defaults to false. (This option is supported + for Borg 1.4.5+ only for the "create" action and for Borg >= 1.4.5 + and < Borg 2 for the "prune" action.) example: true list_details: type: boolean diff --git a/tests/unit/borg/test_prune.py b/tests/unit/borg/test_prune.py index 00e9e98f..8ccf8a47 100644 --- a/tests/unit/borg/test_prune.py +++ b/tests/unit/borg/test_prune.py @@ -425,6 +425,31 @@ def test_prune_archives_with_stats_config_calls_borg_with_stats_flag(): ) +def test_prune_archives_with_quick_stats_config_calls_borg_with_quick_stats_flag(): + flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels') + flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER + flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS) + flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) + flexmock(module.feature).should_receive('available').with_args( + module.feature.Feature.NO_PRUNE_STATS, + '1.2.3', + ).and_return(False) + insert_execute_command_mock( + (*PRUNE_COMMAND, '--quick-stats', 'repo'), module.borgmatic.logger.ANSWER + ) + insert_logging_mock(logging.WARNING) + + prune_arguments = flexmock(statistics=None, list_details=False) + module.prune_archives( + dry_run=False, + repository_path='repo', + config={'quick_statistics': True}, + local_borg_version='1.2.3', + global_arguments=flexmock(), + prune_arguments=prune_arguments, + ) + + def test_prune_archives_with_list_config_calls_borg_with_list_flag(): flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels') flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER @@ -647,3 +672,26 @@ def test_prune_archives_calls_borg_without_stats_when_feature_is_not_available() global_arguments=flexmock(), prune_arguments=prune_arguments, ) + + +def test_prune_archives_calls_borg_without_quick_stats_when_feature_is_not_available(): + flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels') + flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER + flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS) + flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) + flexmock(module.feature).should_receive('available').with_args( + module.feature.Feature.NO_PRUNE_STATS, + '2.0.0b10', + ).and_return(True) + insert_execute_command_mock((*PRUNE_COMMAND, 'repo'), logging.ANSWER) + insert_logging_mock(logging.WARNING) + + prune_arguments = flexmock(quick_statistics=True, list_details=False) + module.prune_archives( + dry_run=False, + repository_path='repo', + config={'quick_statistics': True}, + local_borg_version='2.0.0b10', + global_arguments=flexmock(), + prune_arguments=prune_arguments, + )