From efd0f0d618582358df22f14ad36569e90c452ebf Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Sun, 18 Jan 2026 18:39:29 -0800 Subject: [PATCH] For the "recreate" action, actually pass the "--dry-run" flag through to Borg instead of just skipping the Borg call (#1241). --- NEWS | 4 ++ borgmatic/actions/recreate.py | 5 +- borgmatic/borg/recreate.py | 5 +- borgmatic/commands/borgmatic.py | 1 + borgmatic/commands/completion/bash.py | 2 +- docs/how-to/deal-with-very-large-backups.md | 2 +- pyproject.toml | 2 +- tests/unit/actions/test_recreate.py | 7 ++ tests/unit/borg/test_recreate.py | 73 ++++++++++----------- 9 files changed, 54 insertions(+), 47 deletions(-) diff --git a/NEWS b/NEWS index faccabc4..7c882f8e 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,7 @@ +2.1.1.dev0 + * #1241: For the "recreate" action, actually pass the "--dry-run" flag through to Borg instead of + just skipping the Borg call. + 2.1.0 * TL;DR: Many logging, memory, and performance improvements. Mind those breaking changes! * #485: When running commands (database clients, command hooks, etc.), elevate stderr output to diff --git a/borgmatic/actions/recreate.py b/borgmatic/actions/recreate.py index c1c54ff4..06549843 100644 --- a/borgmatic/actions/recreate.py +++ b/borgmatic/actions/recreate.py @@ -18,6 +18,7 @@ def run_recreate( local_borg_version, recreate_arguments, global_arguments, + dry_run_label, local_path, remote_path, ): @@ -25,9 +26,9 @@ def run_recreate( Run the "recreate" action for the given repository. ''' if recreate_arguments.archive: - logger.answer(f'Recreating archive {recreate_arguments.archive}') + logger.answer(f'Recreating archive {recreate_arguments.archive}{dry_run_label}') else: - logger.answer('Recreating repository') + logger.answer(f'Recreating repository{dry_run_label}') # Collect and process patterns. processed_patterns = borgmatic.actions.pattern.process_patterns( diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index b16a380c..598df637 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -72,6 +72,7 @@ def recreate_archive( + (('--chunker-params', chunker_params) if chunker_params else ()) + (('--recompress', recompress) if recompress else ()) + exclude_flags + + (('--dry-run',) if global_arguments.dry_run else ()) + (tuple(shlex.split(extra_borg_options)) if extra_borg_options else ()) + ( ( @@ -94,10 +95,6 @@ def recreate_archive( ) ) - if global_arguments.dry_run: - logger.info('Skipping the archive recreation (dry run)') - return - borgmatic.execute.execute_command( full_command=recreate_command, output_log_level=logging.INFO, diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index e1fea6de..2954ac72 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -441,6 +441,7 @@ def run_actions( # noqa: PLR0912, PLR0915 local_borg_version, action_arguments, global_arguments, + dry_run_label, local_path, remote_path, ) diff --git a/borgmatic/commands/completion/bash.py b/borgmatic/commands/completion/bash.py index e662aaea..94e05f6d 100644 --- a/borgmatic/commands/completion/bash.py +++ b/borgmatic/commands/completion/bash.py @@ -39,7 +39,7 @@ def bash_completion(): 'check_version() {', ' local this_script="$(cat "$BASH_SOURCE" 2> /dev/null)"', ' local installed_script="$(borgmatic --bash-completion 2> /dev/null)"', - ' if [ "$this_script" != "$installed_script" ] && [ "$installed_script" != "" ];' + ' if [ "$this_script" != "$installed_script" ] && [ "$installed_script" != "" ];' # noqa: ISC004 f''' then cat << EOF\n{borgmatic.commands.completion.actions.upgrade_message( 'bash', 'sudo sh -c "borgmatic --bash-completion > $BASH_SOURCE"', diff --git a/docs/how-to/deal-with-very-large-backups.md b/docs/how-to/deal-with-very-large-backups.md index 0d64b94e..feea3504 100644 --- a/docs/how-to/deal-with-very-large-backups.md +++ b/docs/how-to/deal-with-very-large-backups.md @@ -60,7 +60,7 @@ follows: unsafe_skip_path_validation_before_create: true ``` -However, this is indeed unsafe, and could lead to hangs or data being left out +However, this is indeed unsafe and could lead to hangs or data being left out of backups. Use this option at your own risk. diff --git a/pyproject.toml b/pyproject.toml index 414ad152..d6ee998d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "borgmatic" -version = "2.1.0" +version = "2.1.1.dev0" authors = [ { name="Dan Helfman", email="witten@torsion.org" }, ] diff --git a/tests/unit/actions/test_recreate.py b/tests/unit/actions/test_recreate.py index 9b92530b..7ed5a6b3 100644 --- a/tests/unit/actions/test_recreate.py +++ b/tests/unit/actions/test_recreate.py @@ -22,6 +22,7 @@ def test_run_recreate_does_not_raise(): local_borg_version=None, recreate_arguments=flexmock(repository=flexmock(), archive=None), global_arguments=flexmock(), + dry_run_label='', local_path=None, remote_path=None, ) @@ -45,6 +46,7 @@ def test_run_recreate_with_archive_does_not_raise(): local_borg_version=None, recreate_arguments=flexmock(repository=flexmock(), archive='test-archive'), global_arguments=flexmock(), + dry_run_label='', local_path=None, remote_path=None, ) @@ -69,6 +71,7 @@ def test_run_recreate_with_leftover_recreate_archive_raises(): local_borg_version=None, recreate_arguments=flexmock(repository=flexmock(), archive='test-archive.recreate'), global_arguments=flexmock(), + dry_run_label='', local_path=None, remote_path=None, ) @@ -93,6 +96,7 @@ def test_run_recreate_with_latest_archive_resolving_to_leftover_recreate_archive local_borg_version=None, recreate_arguments=flexmock(repository=flexmock(), archive='latest'), global_arguments=flexmock(), + dry_run_label='', local_path=None, remote_path=None, ) @@ -122,6 +126,7 @@ def test_run_recreate_with_archive_already_exists_error_raises(): local_borg_version=None, recreate_arguments=flexmock(repository=flexmock(), archive='test-archive', target=None), global_arguments=flexmock(), + dry_run_label='', local_path=None, remote_path=None, ) @@ -155,6 +160,7 @@ def test_run_recreate_with_target_and_archive_already_exists_error_raises(): target='target-archive', ), global_arguments=flexmock(), + dry_run_label='', local_path=None, remote_path=None, ) @@ -188,6 +194,7 @@ def test_run_recreate_with_other_called_process_error_passes_it_through(): target='target-archive', ), global_arguments=flexmock(), + dry_run_label='', local_path=None, remote_path=None, ) diff --git a/tests/unit/borg/test_recreate.py b/tests/unit/borg/test_recreate.py index d54f2f94..ac402728 100644 --- a/tests/unit/borg/test_recreate.py +++ b/tests/unit/borg/test_recreate.py @@ -20,44 +20,6 @@ def insert_execute_command_mock(command, working_directory=None, borg_exit_codes ).once() -def test_recreate_archive_dry_run_skips_execution(): - flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(()) - flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None) - flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('') - flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(()) - flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True) - flexmock(module.borgmatic.borg.flags).should_receive( - 'make_repository_archive_flags', - ).and_return( - ( - '--repo', - 'repo', - ), - ) - flexmock(module.borgmatic.execute).should_receive('execute_command').never() - - recreate_arguments = flexmock( - repository=flexmock(), - list=None, - target=None, - comment=None, - timestamp=None, - match_archives=None, - ) - - result = module.recreate_archive( - repository='repo', - archive='archive', - config={}, - local_borg_version='1.2.3', - recreate_arguments=recreate_arguments, - global_arguments=flexmock(dry_run=True), - local_path='borg', - ) - - assert result is None - - def test_recreate_calls_borg_with_required_flags(): flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(()) flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None) @@ -93,6 +55,41 @@ def test_recreate_calls_borg_with_required_flags(): ) +def test_recreate_with_dry_run_calls_borg_with_dry_run_flag(): + flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('') + flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(()) + flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True) + flexmock(module.borgmatic.borg.flags).should_receive( + 'make_repository_archive_flags', + ).and_return( + ( + '--repo', + 'repo', + ), + ) + insert_execute_command_mock(('borg', 'recreate', '--log-json', '--dry-run', '--repo', 'repo')) + + module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='1.2.3', + recreate_arguments=flexmock( + list=None, + target=None, + comment=None, + timestamp=None, + match_archives=None, + ), + global_arguments=flexmock(dry_run=True), + local_path='borg', + remote_path=None, + patterns=None, + ) + + def test_recreate_with_remote_path(): flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(()) flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)