From a892a308bd36b83a95e9e1ee03b6b714cc6f0d80 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Tue, 8 Apr 2025 10:13:04 -0700 Subject: [PATCH] Display a nicer error message when running the "recreate" action on a leftover temporary archive from a prior recreate run (#1053). --- NEWS | 2 + borgmatic/actions/recreate.py | 31 +++++++++---- borgmatic/borg/recreate.py | 1 + tests/unit/actions/test_recreate.py | 67 ++++++++++++++++++++++++++--- 4 files changed, 86 insertions(+), 15 deletions(-) diff --git a/NEWS b/NEWS index 8f8d4bc0..32a3f0c6 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ 2.0.2.dev0 * #1035: Document potential performance issues and workarounds with the ZFS, Btrfs, and LVM hooks: https://torsion.org/borgmatic/docs/how-to/snapshot-your-filesystems/ + * #1053: Display a nicer error message when running the "recreate" action on a leftover temporary + archive from a prior recreate run. * #1059: Fix a regression in which soft failure exit codes in command hooks were not respected. * #1060: Fix action command hooks getting run too many times when multiple borgmatic actions are executed (implicitly or explicitly). diff --git a/borgmatic/actions/recreate.py b/borgmatic/actions/recreate.py index 94c50642..ad7b041e 100644 --- a/borgmatic/actions/recreate.py +++ b/borgmatic/actions/recreate.py @@ -1,6 +1,7 @@ import logging import borgmatic.borg.recreate +import borgmatic.borg.repo_list import borgmatic.config.validate from borgmatic.actions.pattern import collect_patterns, process_patterns @@ -32,17 +33,29 @@ def run_recreate( collect_patterns(config), borgmatic.config.paths.get_working_directory(config) ) + archive = borgmatic.borg.repo_list.resolve_archive_name( + repository['path'], + recreate_arguments.archive, + config, + local_borg_version, + global_arguments, + local_path, + remote_path, + ) + + if archive and archive.endswith('.recreate'): + if recreate_arguments.archive == 'latest': + raise ValueError( + f'The latest archive "{archive}" is leftover from a prior recreate. Delete it first or select a different archive.' + ) + else: + raise ValueError( + f'The archive "{recreate_arguments.archive}" is leftover from a prior recreate. Select a different archive.' + ) + borgmatic.borg.recreate.recreate_archive( repository['path'], - borgmatic.borg.repo_list.resolve_archive_name( - repository['path'], - recreate_arguments.archive, - config, - local_borg_version, - global_arguments, - local_path, - remote_path, - ), + archive, config, local_borg_version, recreate_arguments, diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index 90d4f37a..4b41d9f4 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -32,6 +32,7 @@ def recreate_archive( exclude_flags = flags.make_exclude_flags(config) compression = config.get('compression', None) chunker_params = config.get('chunker_params', None) + # Available recompress MODES: "if-different", "always", "never" (default) recompress = config.get('recompress', None) diff --git a/tests/unit/actions/test_recreate.py b/tests/unit/actions/test_recreate.py index 4250af2c..f19bfbe3 100644 --- a/tests/unit/actions/test_recreate.py +++ b/tests/unit/actions/test_recreate.py @@ -1,3 +1,4 @@ +import pytest from flexmock import flexmock from borgmatic.actions import recreate as module @@ -6,15 +7,19 @@ from borgmatic.actions import recreate as module def test_run_recreate_does_not_raise(): flexmock(module.logger).answer = lambda message: None flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return( + flexmock() + ) + flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( + None + ) flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive') - recreate_arguments = flexmock(repository=flexmock(), archive=None) - module.run_recreate( repository={'path': 'repo'}, config={}, local_borg_version=None, - recreate_arguments=recreate_arguments, + recreate_arguments=flexmock(repository=flexmock(), archive=None), global_arguments=flexmock(), local_path=None, remote_path=None, @@ -24,16 +29,66 @@ def test_run_recreate_does_not_raise(): def test_run_recreate_with_archive_does_not_raise(): flexmock(module.logger).answer = lambda message: None flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return( + flexmock() + ) + flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( + 'test-archive' + ) flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive') - recreate_arguments = flexmock(repository=flexmock(), archive='test-archive') - module.run_recreate( repository={'path': 'repo'}, config={}, local_borg_version=None, - recreate_arguments=recreate_arguments, + recreate_arguments=flexmock(repository=flexmock(), archive='test-archive'), global_arguments=flexmock(), local_path=None, remote_path=None, ) + + +def test_run_recreate_with_leftover_recreate_archive_raises(): + flexmock(module.logger).answer = lambda message: None + flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return( + flexmock() + ) + flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( + 'test-archive.recreate' + ) + flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive') + + with pytest.raises(ValueError): + module.run_recreate( + repository={'path': 'repo'}, + config={}, + local_borg_version=None, + recreate_arguments=flexmock(repository=flexmock(), archive='test-archive.recreate'), + global_arguments=flexmock(), + local_path=None, + remote_path=None, + ) + + +def test_run_recreate_with_latest_archive_resolving_to_leftover_recreate_archive_raises(): + flexmock(module.logger).answer = lambda message: None + flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return( + flexmock() + ) + flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( + 'test-archive.recreate' + ) + flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive') + + with pytest.raises(ValueError): + module.run_recreate( + repository={'path': 'repo'}, + config={}, + local_borg_version=None, + recreate_arguments=flexmock(repository=flexmock(), archive='latest'), + global_arguments=flexmock(), + local_path=None, + remote_path=None, + )