Display a nicer error message when the "recreate" action encounters an archive that already exists (#1053).

This commit is contained in:
Dan Helfman
2025-04-08 10:58:53 -07:00
parent a892a308bd
commit f27a96e22d
3 changed files with 119 additions and 13 deletions
+2 -2
View File
@@ -1,8 +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.
* #1053: Display a nicer error message when the "recreate" action encounters an archive that
already exists.
* #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).
+29 -11
View File
@@ -1,5 +1,7 @@
import logging
import subprocess
import borgmatic.borg.info
import borgmatic.borg.recreate
import borgmatic.borg.repo_list
import borgmatic.config.validate
@@ -8,6 +10,9 @@ from borgmatic.actions.pattern import collect_patterns, process_patterns
logger = logging.getLogger(__name__)
BORG_EXIT_CODE_ARCHIVE_ALREADY_EXISTS = 30
def run_recreate(
repository,
config,
@@ -53,14 +58,27 @@ def run_recreate(
f'The archive "{recreate_arguments.archive}" is leftover from a prior recreate. Select a different archive.'
)
borgmatic.borg.recreate.recreate_archive(
repository['path'],
archive,
config,
local_borg_version,
recreate_arguments,
global_arguments,
local_path=local_path,
remote_path=remote_path,
patterns=processed_patterns,
)
try:
borgmatic.borg.recreate.recreate_archive(
repository['path'],
archive,
config,
local_borg_version,
recreate_arguments,
global_arguments,
local_path=local_path,
remote_path=remote_path,
patterns=processed_patterns,
)
except subprocess.CalledProcessError as error:
if error.returncode == BORG_EXIT_CODE_ARCHIVE_ALREADY_EXISTS:
if recreate_arguments.target:
raise ValueError(
f'The archive "{recreate_arguments.target}" already exists. Delete it first or set a different target archive name.'
)
elif archive:
raise ValueError(
f'The archive "{archive}.recreate" is leftover from a prior recreate. Delete it first or select a different archive.'
)
raise
+88
View File
@@ -92,3 +92,91 @@ def test_run_recreate_with_latest_archive_resolving_to_leftover_recreate_archive
local_path=None,
remote_path=None,
)
def test_run_recreate_with_archive_already_exists_error_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'
)
flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive').and_raise(
module.subprocess.CalledProcessError(
returncode=module.BORG_EXIT_CODE_ARCHIVE_ALREADY_EXISTS,
cmd='borg recreate or whatever',
)
)
with pytest.raises(ValueError):
module.run_recreate(
repository={'path': 'repo'},
config={},
local_borg_version=None,
recreate_arguments=flexmock(repository=flexmock(), archive='test-archive', target=None),
global_arguments=flexmock(),
local_path=None,
remote_path=None,
)
def test_run_recreate_with_target_and_archive_already_exists_error_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'
)
flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive').and_raise(
module.subprocess.CalledProcessError(
returncode=module.BORG_EXIT_CODE_ARCHIVE_ALREADY_EXISTS,
cmd='borg recreate or whatever',
)
)
with pytest.raises(ValueError):
module.run_recreate(
repository={'path': 'repo'},
config={},
local_borg_version=None,
recreate_arguments=flexmock(
repository=flexmock(), archive='test-archive', target='target-archive'
),
global_arguments=flexmock(),
local_path=None,
remote_path=None,
)
def test_run_recreate_with_other_called_process_error_passes_it_through():
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').and_raise(
module.subprocess.CalledProcessError(
returncode=1,
cmd='borg recreate or whatever',
)
)
with pytest.raises(module.subprocess.CalledProcessError):
module.run_recreate(
repository={'path': 'repo'},
config={},
local_borg_version=None,
recreate_arguments=flexmock(
repository=flexmock(), archive='test-archive', target='target-archive'
),
global_arguments=flexmock(),
local_path=None,
remote_path=None,
)