From 6adb0fd44cc7be8d7dded5f39ee005622607ff6d Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Mon, 17 Mar 2025 22:24:53 +0530 Subject: [PATCH 01/18] add borg recreate --- borgmatic/actions/recreate.py | 0 borgmatic/borg/recreate.py | 82 +++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 borgmatic/actions/recreate.py create mode 100644 borgmatic/borg/recreate.py diff --git a/borgmatic/actions/recreate.py b/borgmatic/actions/recreate.py new file mode 100644 index 00000000..e69de29b diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py new file mode 100644 index 00000000..41cd4eef --- /dev/null +++ b/borgmatic/borg/recreate.py @@ -0,0 +1,82 @@ +import argparse +import logging + +import borgmatic.borg.environment +import borgmatic.borg.feature +import borgmatic.borg.flags +import borgmatic.borg.repo_delete +import borgmatic.config.paths +import borgmatic.execute +from borgmatic.borg.create import make_exclude_flags, write_patterns_file + +logger = logging.getLogger(__name__) + + +def make_recreate_command( + repository, + config, + patterns, + local_borg_version, + recreate_arguments, + global_arguments, + local_path, + remote_path=None, +): + ''' + Given a repository path, configuration dict, patterns, and Borg options, return a command + list for recreating a Borg archive. + ''' + verbosity_flags = () + if logger.isEnabledFor(logging.DEBUG): + verbosity_flags = ('--debug', '--show-rc') + elif logger.isEnabledFor(logging.INFO): + verbosity_flags = ('--info',) + + command = [local_path, 'recreate', repository] + command.extend(verbosity_flags) + command.extend(global_arguments) + command.extend(recreate_arguments) + + exclude_flags = make_exclude_flags(config) + command.extend(exclude_flags) + + return command + + +def recreate_archive( + repository, + config, + patterns, + local_borg_version, + recreate_arguments, + global_arguments, + local_path='borg', + remote_path=None, +): + ''' + Recreate a Borg archive with the given repository and configuration. + ''' + command = make_recreate_command( + repository, + config, + patterns, + local_borg_version, + recreate_arguments, + global_arguments, + local_path, + remote_path, + ) + + patterns_file = write_patterns_file(patterns, borgmatic.config.paths.get_runtime_directory()) + if patterns_file: + command.extend(['--patterns-from', patterns_file.name]) + + borgmatic.execute.execute_command( + command, + output_log_level=logging.ANSWER, + environment=borgmatic.borg.environment.make_environment(config), + working_directory=borgmatic.config.paths.get_working_directory(config), + remote_path=remote_path, + borg_local_path=local_path, + borg_exit_codes=config.get('borg_exit_codes') + ) From 4e2805918d84991ceca661a9d873def9e977951c Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Tue, 18 Mar 2025 23:19:33 +0530 Subject: [PATCH 02/18] update borg/recreate.py --- borgmatic/borg/recreate.py | 64 +++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index 41cd4eef..a9c6ff56 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -1,21 +1,18 @@ -import argparse import logging import borgmatic.borg.environment -import borgmatic.borg.feature -import borgmatic.borg.flags -import borgmatic.borg.repo_delete import borgmatic.config.paths import borgmatic.execute -from borgmatic.borg.create import make_exclude_flags, write_patterns_file +from borgmatic.borg.create import make_exclude_flags +from borgmatic.borg.flags import make_flags_from_arguments, make_repository_archive_flags logger = logging.getLogger(__name__) def make_recreate_command( repository, + archive, config, - patterns, local_borg_version, recreate_arguments, global_arguments, @@ -23,30 +20,39 @@ def make_recreate_command( remote_path=None, ): ''' - Given a repository path, configuration dict, patterns, and Borg options, return a command - list for recreating a Borg archive. + Given a local or remote repository path, an archive name, a configuration dict, + the local Borg version string, an argparse.Namespace of recreate arguments, + an argparse.Namespace of global arguments, optional local and remote Borg paths. + + Returns the recreate command as a tuple of strings ready for execution. ''' - verbosity_flags = () - if logger.isEnabledFor(logging.DEBUG): - verbosity_flags = ('--debug', '--show-rc') - elif logger.isEnabledFor(logging.INFO): - verbosity_flags = ('--info',) + verbosity_flags = (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ()) + ( + ('--info',) if logger.isEnabledFor(logging.INFO) else () + ) - command = [local_path, 'recreate', repository] - command.extend(verbosity_flags) - command.extend(global_arguments) - command.extend(recreate_arguments) + # handle both the recreate and global arguments + recreate_flags = make_flags_from_arguments( + recreate_arguments, excludes=('repository', 'archive') + ) + global_flags = make_flags_from_arguments(global_arguments) + repo_archive_flags = make_repository_archive_flags(repository, archive, local_borg_version) exclude_flags = make_exclude_flags(config) - command.extend(exclude_flags) - return command + return ( + (local_path, 'recreate') + + repo_archive_flags + + verbosity_flags + + global_flags + + recreate_flags + + exclude_flags + ) def recreate_archive( repository, + archive, config, - patterns, local_borg_version, recreate_arguments, global_arguments, @@ -54,12 +60,16 @@ def recreate_archive( remote_path=None, ): ''' - Recreate a Borg archive with the given repository and configuration. + Given a local or remote repository path, an archive name, a configuration dict, + the local Borg version string, an argparse.Namespace of recreate arguments, + an argparse.Namespace of global arguments, optional local and remote Borg paths. + + Executes the recreate command with the given arguments. ''' command = make_recreate_command( repository, + archive, config, - patterns, local_borg_version, recreate_arguments, global_arguments, @@ -67,16 +77,12 @@ def recreate_archive( remote_path, ) - patterns_file = write_patterns_file(patterns, borgmatic.config.paths.get_runtime_directory()) - if patterns_file: - command.extend(['--patterns-from', patterns_file.name]) - borgmatic.execute.execute_command( command, output_log_level=logging.ANSWER, environment=borgmatic.borg.environment.make_environment(config), working_directory=borgmatic.config.paths.get_working_directory(config), - remote_path=remote_path, + remote_path=remote_path, borg_local_path=local_path, - borg_exit_codes=config.get('borg_exit_codes') - ) + borg_exit_codes=config.get('borg_exit_codes'), + ) From a750d58a2d972e2255c9e014e27a3800c5270a62 Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Sat, 22 Mar 2025 21:18:28 +0530 Subject: [PATCH 03/18] add recreate action --- borgmatic/actions/recreate.py | 46 +++++++++++++++++++++++++ borgmatic/borg/recreate.py | 61 ++++++++------------------------- borgmatic/commands/arguments.py | 21 ++++++++++++ borgmatic/commands/borgmatic.py | 11 ++++++ 4 files changed, 93 insertions(+), 46 deletions(-) diff --git a/borgmatic/actions/recreate.py b/borgmatic/actions/recreate.py index e69de29b..acac0d44 100644 --- a/borgmatic/actions/recreate.py +++ b/borgmatic/actions/recreate.py @@ -0,0 +1,46 @@ +import logging + +import borgmatic.borg.recreate +import borgmatic.config.validate + +logger = logging.getLogger(__name__) + + +def run_recreate( + repository, + config, + local_borg_version, + recreate_arguments, + global_arguments, + local_path, + remote_path, +): + ''' + Run the "recreate" action for the given repository. + ''' + if recreate_arguments.repository is None or borgmatic.config.validate.repositories_match( + repository, recreate_arguments.repository + ): + if recreate_arguments.archive: + logger.info(f'Recreating archive {recreate_arguments.archive}') + else: + logger.info('Recreating repository') + + 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, + ), + config, + local_borg_version, + recreate_arguments, + global_arguments, + local_path=local_path, + remote_path=remote_path, + ) diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index a9c6ff56..a1bd60aa 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -9,7 +9,7 @@ from borgmatic.borg.flags import make_flags_from_arguments, make_repository_arch logger = logging.getLogger(__name__) -def make_recreate_command( +def recreate_archive( repository, archive, config, @@ -24,61 +24,30 @@ def make_recreate_command( the local Borg version string, an argparse.Namespace of recreate arguments, an argparse.Namespace of global arguments, optional local and remote Borg paths. - Returns the recreate command as a tuple of strings ready for execution. + Executes the recreate command with the given arguments. ''' - verbosity_flags = (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ()) + ( - ('--info',) if logger.isEnabledFor(logging.INFO) else () - ) + lock_wait = config.get('lock_wait', None) - # handle both the recreate and global arguments - recreate_flags = make_flags_from_arguments( - recreate_arguments, excludes=('repository', 'archive') - ) - global_flags = make_flags_from_arguments(global_arguments) - - repo_archive_flags = make_repository_archive_flags(repository, archive, local_borg_version) + repo_archive_arg = make_repository_archive_flags(repository, archive, local_borg_version) exclude_flags = make_exclude_flags(config) - return ( + recreate_cmd = ( (local_path, 'recreate') - + repo_archive_flags - + verbosity_flags - + global_flags - + recreate_flags + + (('--remote-path', remote_path) if remote_path else ()) + + repo_archive_arg + + (('--log-json',) if global_arguments.log_json else ()) + + (('--lock-wait', str(lock_wait)) if lock_wait else ()) + + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) + + (('--debug', '--show-rc', '--list') if logger.isEnabledFor(logging.DEBUG) else ()) + exclude_flags ) - -def recreate_archive( - repository, - archive, - config, - local_borg_version, - recreate_arguments, - global_arguments, - local_path='borg', - remote_path=None, -): - ''' - Given a local or remote repository path, an archive name, a configuration dict, - the local Borg version string, an argparse.Namespace of recreate arguments, - an argparse.Namespace of global arguments, optional local and remote Borg paths. - - Executes the recreate command with the given arguments. - ''' - command = make_recreate_command( - repository, - archive, - config, - local_borg_version, - recreate_arguments, - global_arguments, - local_path, - remote_path, - ) + if global_arguments.dry_run: + logger.info('Skipping the archive recreation (dry run)') + return borgmatic.execute.execute_command( - command, + recreate_cmd, output_log_level=logging.ANSWER, environment=borgmatic.borg.environment.make_environment(config), working_directory=borgmatic.config.paths.get_working_directory(config), diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 56842ea8..4018547e 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -27,6 +27,7 @@ ACTION_ALIASES = { 'break-lock': [], 'key': [], 'borg': [], + 'recreate': [], } @@ -1545,6 +1546,26 @@ def make_parsers(): ) borg_group.add_argument('-h', '--help', action='help', help='Show this help message and exit') + recreate_parser = action_parsers.add_parser( + 'recreate', + aliases=ACTION_ALIASES['recreate'], + help='Recreate an archive in a repository', + description='Recreate an archive in a repository', + add_help=False, + ) + recreate_group = recreate_parser.add_argument_group('recreate arguments') + recreate_group.add_argument( + '--repository', + help='Path of the repository containing the archive', + ) + recreate_group.add_argument( + '--archive', + help='Name of the archive to recreate', + ) + recreate_group.add_argument( + '-h', '--help', action='help', help='Show this help message and exit' + ) + return global_parser, action_parsers, global_plus_action_parser diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index bd1ec1e6..467673fe 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -17,6 +17,7 @@ import borgmatic.actions.config.bootstrap import borgmatic.actions.config.generate import borgmatic.actions.config.validate import borgmatic.actions.create +import borgmatic.actions.recreate import borgmatic.actions.delete import borgmatic.actions.export_key import borgmatic.actions.export_tar @@ -397,6 +398,16 @@ def run_actions( local_path, remote_path, ) + elif action_name == 'recreate' and action_name not in skip_actions: + borgmatic.actions.recreate.run_recreate( + repository, + config, + local_borg_version, + action_arguments, + global_arguments, + local_path, + remote_path, + ) elif action_name == 'prune' and action_name not in skip_actions: borgmatic.actions.prune.run_prune( config_filename, From a1d2f7f2218f2bc44dcc147957c89f6844a9eaa8 Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Mon, 24 Mar 2025 11:51:33 +0530 Subject: [PATCH 04/18] add path --- borgmatic/borg/recreate.py | 4 ++++ borgmatic/commands/arguments.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index a1bd60aa..fd547e2f 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -30,11 +30,15 @@ def recreate_archive( repo_archive_arg = make_repository_archive_flags(repository, archive, local_borg_version) exclude_flags = make_exclude_flags(config) + # handle path from recreate_arguments + path_flag = ('--path', recreate_arguments.path) if recreate_arguments.path else () + recreate_cmd = ( (local_path, 'recreate') + (('--remote-path', remote_path) if remote_path else ()) + repo_archive_arg + + path_flag + (('--log-json',) if global_arguments.log_json else ()) + (('--lock-wait', str(lock_wait)) if lock_wait else ()) + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 4018547e..7e942c63 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -1562,6 +1562,13 @@ def make_parsers(): '--archive', help='Name of the archive to recreate', ) + recreate_group.add_argument( + '--path', + metavar='PATH', + dest='paths', + action='append', + help='Path to recreate the repository/archive', + ) recreate_group.add_argument( '-h', '--help', action='help', help='Show this help message and exit' ) From fa3b1405902940e20f22e7e8a796a2b6428aee11 Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Mon, 24 Mar 2025 12:09:08 +0530 Subject: [PATCH 05/18] add patterns --- borgmatic/actions/recreate.py | 8 ++++++++ borgmatic/borg/recreate.py | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/borgmatic/actions/recreate.py b/borgmatic/actions/recreate.py index acac0d44..0f3a0d79 100644 --- a/borgmatic/actions/recreate.py +++ b/borgmatic/actions/recreate.py @@ -2,6 +2,7 @@ import logging import borgmatic.borg.recreate import borgmatic.config.validate +from borgmatic.actions.create import collect_patterns, process_patterns logger = logging.getLogger(__name__) @@ -26,6 +27,12 @@ def run_recreate( else: logger.info('Recreating repository') + # collect and process patterns + patterns = collect_patterns(config) + processed_patterns = process_patterns( + patterns, borgmatic.config.paths.get_working_directory(config) + ) + borgmatic.borg.recreate.recreate_archive( repository['path'], borgmatic.borg.repo_list.resolve_archive_name( @@ -43,4 +50,5 @@ def run_recreate( global_arguments, local_path=local_path, remote_path=remote_path, + patterns=processed_patterns, ) diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index fd547e2f..9cc54679 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -18,6 +18,7 @@ def recreate_archive( global_arguments, local_path, remote_path=None, + patterns=None, ): ''' Given a local or remote repository path, an archive name, a configuration dict, @@ -32,7 +33,7 @@ def recreate_archive( exclude_flags = make_exclude_flags(config) # handle path from recreate_arguments path_flag = ('--path', recreate_arguments.path) if recreate_arguments.path else () - + pattern_flags = ('--patterns-from', patterns) if patterns else () recreate_cmd = ( (local_path, 'recreate') @@ -43,6 +44,7 @@ def recreate_archive( + (('--lock-wait', str(lock_wait)) if lock_wait else ()) + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) + (('--debug', '--show-rc', '--list') if logger.isEnabledFor(logging.DEBUG) else ()) + + pattern_flags + exclude_flags ) From e7f14bca87724b0d74d8d74d4ab85a225cd1dc0e Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Tue, 25 Mar 2025 00:16:20 +0530 Subject: [PATCH 06/18] add tests and requested changes --- borgmatic/actions/recreate.py | 5 ++-- borgmatic/borg/recreate.py | 29 +++++++++++++------- tests/unit/actions/test_recreate.py | 39 +++++++++++++++++++++++++++ tests/unit/borg/test_recreate.py | 0 tests/unit/commands/test_borgmatic.py | 21 +++++++++++++++ 5 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 tests/unit/actions/test_recreate.py create mode 100644 tests/unit/borg/test_recreate.py diff --git a/borgmatic/actions/recreate.py b/borgmatic/actions/recreate.py index 0f3a0d79..53ee7fce 100644 --- a/borgmatic/actions/recreate.py +++ b/borgmatic/actions/recreate.py @@ -27,10 +27,9 @@ def run_recreate( else: logger.info('Recreating repository') - # collect and process patterns - patterns = collect_patterns(config) + # Collect and process patterns. processed_patterns = process_patterns( - patterns, borgmatic.config.paths.get_working_directory(config) + collect_patterns(config), borgmatic.config.paths.get_working_directory(config) ) borgmatic.borg.recreate.recreate_archive( diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index 9cc54679..a3c2e7e4 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -3,8 +3,8 @@ import logging import borgmatic.borg.environment import borgmatic.config.paths import borgmatic.execute -from borgmatic.borg.create import make_exclude_flags -from borgmatic.borg.flags import make_flags_from_arguments, make_repository_archive_flags +from borgmatic.borg.create import make_exclude_flags, write_patterns_file, make_list_filter_flags +from borgmatic.borg.flags import make_repository_archive_flags logger = logging.getLogger(__name__) @@ -31,20 +31,31 @@ def recreate_archive( repo_archive_arg = make_repository_archive_flags(repository, archive, local_borg_version) exclude_flags = make_exclude_flags(config) - # handle path from recreate_arguments - path_flag = ('--path', recreate_arguments.path) if recreate_arguments.path else () - pattern_flags = ('--patterns-from', patterns) if patterns else () + + # Write patterns to a temporary file and use that file with --patterns-from. + patterns_file = write_patterns_file( + patterns, borgmatic.config.paths.get_working_directory(config) + ) recreate_cmd = ( (local_path, 'recreate') + (('--remote-path', remote_path) if remote_path else ()) + repo_archive_arg - + path_flag + + (('--path', recreate_arguments.path) if recreate_arguments.path else ()) + (('--log-json',) if global_arguments.log_json else ()) + (('--lock-wait', str(lock_wait)) if lock_wait else ()) + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) - + (('--debug', '--show-rc', '--list') if logger.isEnabledFor(logging.DEBUG) else ()) - + pattern_flags + + (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ()) + + (('--patterns-from', patterns_file.name) if patterns_file else ()) + + ( + ( + '--list', + '--filter', + make_list_filter_flags(local_borg_version, global_arguments.dry_run), + ) + if recreate_arguments.list + else () + ) + exclude_flags ) @@ -54,7 +65,7 @@ def recreate_archive( borgmatic.execute.execute_command( recreate_cmd, - output_log_level=logging.ANSWER, + output_log_level=logging.INFO, environment=borgmatic.borg.environment.make_environment(config), working_directory=borgmatic.config.paths.get_working_directory(config), remote_path=remote_path, diff --git a/tests/unit/actions/test_recreate.py b/tests/unit/actions/test_recreate.py new file mode 100644 index 00000000..4250af2c --- /dev/null +++ b/tests/unit/actions/test_recreate.py @@ -0,0 +1,39 @@ +from flexmock import flexmock + +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.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, + global_arguments=flexmock(), + local_path=None, + remote_path=None, + ) + + +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.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, + global_arguments=flexmock(), + local_path=None, + remote_path=None, + ) diff --git a/tests/unit/borg/test_recreate.py b/tests/unit/borg/test_recreate.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/commands/test_borgmatic.py b/tests/unit/commands/test_borgmatic.py index 160f4454..0c474c7d 100644 --- a/tests/unit/commands/test_borgmatic.py +++ b/tests/unit/commands/test_borgmatic.py @@ -1019,6 +1019,27 @@ def test_run_actions_runs_create(): assert result == (expected,) +def test_run_actions_runs_recreate(): + flexmock(module).should_receive('add_custom_log_levels') + flexmock(module).should_receive('get_skip_actions').and_return([]) + flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock()) + + flexmock(borgmatic.actions.recreate).should_receive('run_recreate').once() + + tuple( + module.run_actions( + arguments={'global': flexmock(dry_run=False, log_file='foo'), 'recreate': flexmock()}, + config_filename=flexmock(), + config={'repositories': []}, + config_paths=[], + local_path=flexmock(), + remote_path=flexmock(), + local_borg_version=flexmock(), + repository={'path': 'repo'}, + ) + ) + + def test_run_actions_with_skip_actions_skips_create(): flexmock(module).should_receive('add_custom_log_levels') flexmock(module).should_receive('get_skip_actions').and_return(['create']) From b60cf2449afa1f316230b86773e4435163949a26 Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Tue, 25 Mar 2025 00:48:27 +0530 Subject: [PATCH 07/18] add recreate to schema --- borgmatic/config/schema.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 7b309ea0..9fb8f361 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -767,6 +767,7 @@ properties: - prune - compact - create + - recreate - check - delete - extract @@ -982,6 +983,7 @@ properties: - prune - compact - create + - recreate - check - delete - extract @@ -1046,6 +1048,7 @@ properties: - prune - compact - create + - recreate - check - delete - extract From 354267344601e831b9ebf973786f3c4e631c5f1c Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Tue, 25 Mar 2025 11:36:06 +0530 Subject: [PATCH 08/18] add test recreate with skip action --- tests/unit/commands/test_borgmatic.py | 30 ++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/tests/unit/commands/test_borgmatic.py b/tests/unit/commands/test_borgmatic.py index 0c474c7d..481a856a 100644 --- a/tests/unit/commands/test_borgmatic.py +++ b/tests/unit/commands/test_borgmatic.py @@ -1019,6 +1019,26 @@ def test_run_actions_runs_create(): assert result == (expected,) +def test_run_actions_with_skip_actions_skips_create(): + flexmock(module).should_receive('add_custom_log_levels') + flexmock(module).should_receive('get_skip_actions').and_return(['create']) + flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock()) + flexmock(borgmatic.actions.create).should_receive('run_create').never() + + tuple( + module.run_actions( + arguments={'global': flexmock(dry_run=False, log_file='foo'), 'create': flexmock()}, + config_filename=flexmock(), + config={'repositories': [], 'skip_actions': ['create']}, + config_paths=[], + local_path=flexmock(), + remote_path=flexmock(), + local_borg_version=flexmock(), + repository={'path': 'repo'}, + ) + ) + + def test_run_actions_runs_recreate(): flexmock(module).should_receive('add_custom_log_levels') flexmock(module).should_receive('get_skip_actions').and_return([]) @@ -1040,17 +1060,17 @@ def test_run_actions_runs_recreate(): ) -def test_run_actions_with_skip_actions_skips_create(): +def test_run_actions_with_skip_actions_skips_recreate(): flexmock(module).should_receive('add_custom_log_levels') - flexmock(module).should_receive('get_skip_actions').and_return(['create']) + flexmock(module).should_receive('get_skip_actions').and_return(['recreate']) flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock()) - flexmock(borgmatic.actions.create).should_receive('run_create').never() + flexmock(borgmatic.actions.recreate).should_receive('run_recreate').never() tuple( module.run_actions( - arguments={'global': flexmock(dry_run=False, log_file='foo'), 'create': flexmock()}, + arguments={'global': flexmock(dry_run=False, log_file='foo'), 'recreate': flexmock()}, config_filename=flexmock(), - config={'repositories': [], 'skip_actions': ['create']}, + config={'repositories': [], 'skip_actions': ['recreate']}, config_paths=[], local_path=flexmock(), remote_path=flexmock(), From a8726c408aa5a6cc4920649081a84da4f4365c5a Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Tue, 25 Mar 2025 19:35:15 +0530 Subject: [PATCH 09/18] add tests --- borgmatic/borg/recreate.py | 5 +-- borgmatic/commands/borgmatic.py | 2 +- tests/unit/borg/test_recreate.py | 64 ++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index a3c2e7e4..8a85908c 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -3,7 +3,7 @@ import logging import borgmatic.borg.environment import borgmatic.config.paths import borgmatic.execute -from borgmatic.borg.create import make_exclude_flags, write_patterns_file, make_list_filter_flags +from borgmatic.borg.create import make_exclude_flags, make_list_filter_flags, write_patterns_file from borgmatic.borg.flags import make_repository_archive_flags logger = logging.getLogger(__name__) @@ -64,11 +64,10 @@ def recreate_archive( return borgmatic.execute.execute_command( - recreate_cmd, + full_command=recreate_cmd, output_log_level=logging.INFO, environment=borgmatic.borg.environment.make_environment(config), working_directory=borgmatic.config.paths.get_working_directory(config), - remote_path=remote_path, borg_local_path=local_path, borg_exit_codes=config.get('borg_exit_codes'), ) diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 467673fe..53d9582c 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -17,7 +17,6 @@ import borgmatic.actions.config.bootstrap import borgmatic.actions.config.generate import borgmatic.actions.config.validate import borgmatic.actions.create -import borgmatic.actions.recreate import borgmatic.actions.delete import borgmatic.actions.export_key import borgmatic.actions.export_tar @@ -27,6 +26,7 @@ import borgmatic.actions.info import borgmatic.actions.list import borgmatic.actions.mount import borgmatic.actions.prune +import borgmatic.actions.recreate import borgmatic.actions.repo_create import borgmatic.actions.repo_delete import borgmatic.actions.repo_info diff --git a/tests/unit/borg/test_recreate.py b/tests/unit/borg/test_recreate.py index e69de29b..8e229eea 100644 --- a/tests/unit/borg/test_recreate.py +++ b/tests/unit/borg/test_recreate.py @@ -0,0 +1,64 @@ +# import logging + +from flexmock import flexmock + +from borgmatic.borg import recreate as module + +# from ..test_verbosity import insert_logging_mock + +# from borgmatic.borg.pattern import Pattern, Pattern_type, Pattern_style, Pattern_source +# from borgmatic.borg.create import make_exclude_flags, make_list_filter_flags + + +def insert_execute_command_mock(command, working_directory=None, borg_exit_codes=None): + flexmock(module.borgmatic.borg.environment).should_receive('make_environment') + flexmock(module.borgmatic.execute).should_receive('execute_command').with_args( + full_command=command, + output_log_level=module.logging.INFO, + environment=None, + working_directory=working_directory, + borg_local_path=command[0], + borg_exit_codes=borg_exit_codes, + ).once() + + +def test_recreate_archive_dry_run_skips_execution(): + flexmock(module.borgmatic.borg.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) + flexmock(module.borgmatic.execute).should_receive('execute_command').never() + + recreate_arguments = flexmock(repository=flexmock(), list=None, path=None) + + result = module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='1.2.3', + recreate_arguments=recreate_arguments, + global_arguments=flexmock(log_json=False, 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_repository_flags').and_return( + ('repo::archive',) + ) + flexmock(module).should_receive('write_patterns_file').and_return(None) + flexmock(module).should_receive('make_list_filter_flags').and_return(None) + insert_execute_command_mock(('borg', 'recreate', 'repo::archive')) + + module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='1.2.3', + recreate_arguments=flexmock(path=None, list=None), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + remote_path=None, + patterns=None, + ) From ea5a2d8a462cef1b8eb39d485306d6da73b41a0e Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:39:02 +0530 Subject: [PATCH 10/18] add tests for the flags --- borgmatic/borg/recreate.py | 2 +- tests/unit/borg/test_recreate.py | 196 ++++++++++++++++++++++++++++++- 2 files changed, 195 insertions(+), 3 deletions(-) diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index 8a85908c..a2a45047 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -40,7 +40,6 @@ def recreate_archive( recreate_cmd = ( (local_path, 'recreate') + (('--remote-path', remote_path) if remote_path else ()) - + repo_archive_arg + (('--path', recreate_arguments.path) if recreate_arguments.path else ()) + (('--log-json',) if global_arguments.log_json else ()) + (('--lock-wait', str(lock_wait)) if lock_wait else ()) @@ -57,6 +56,7 @@ def recreate_archive( else () ) + exclude_flags + + repo_archive_arg ) if global_arguments.dry_run: diff --git a/tests/unit/borg/test_recreate.py b/tests/unit/borg/test_recreate.py index 8e229eea..379ecc3a 100644 --- a/tests/unit/borg/test_recreate.py +++ b/tests/unit/borg/test_recreate.py @@ -1,10 +1,10 @@ -# import logging +import logging from flexmock import flexmock from borgmatic.borg import recreate as module -# from ..test_verbosity import insert_logging_mock +from ..test_verbosity import insert_logging_mock # from borgmatic.borg.pattern import Pattern, Pattern_type, Pattern_style, Pattern_source # from borgmatic.borg.create import make_exclude_flags, make_list_filter_flags @@ -62,3 +62,195 @@ def test_recreate_calls_borg_with_required_flags(): remote_path=None, patterns=None, ) + + +def test_recreate_with_remote_path(): + flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( + ('repo::archive',) + ) + flexmock(module).should_receive('write_patterns_file').and_return(None) + flexmock(module).should_receive('make_list_filter_flags').and_return(None) + insert_execute_command_mock(('borg', 'recreate', '--remote-path', 'borg1', 'repo::archive')) + + module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='1.2.3', + recreate_arguments=flexmock(path=None, list=None), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + remote_path='borg1', + patterns=None, + ) + + +def test_recreate_with_lock_wait(): + flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( + ('repo::archive',) + ) + flexmock(module).should_receive('write_patterns_file').and_return(None) + flexmock(module).should_receive('make_list_filter_flags').and_return(None) + insert_execute_command_mock(('borg', 'recreate', '--lock-wait', '5', 'repo::archive')) + + module.recreate_archive( + repository='repo', + archive='archive', + config={'lock_wait': '5'}, + local_borg_version='1.2.3', + recreate_arguments=flexmock(path=None, list=None), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_log_info(): + flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( + ('repo::archive',) + ) + flexmock(module).should_receive('write_patterns_file').and_return(None) + flexmock(module).should_receive('make_list_filter_flags').and_return(None) + insert_execute_command_mock(('borg', 'recreate', '--info', 'repo::archive')) + + insert_logging_mock(logging.INFO) + + module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='1.2.3', + recreate_arguments=flexmock(path=None, list=None), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_log_debug(): + flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( + ('repo::archive',) + ) + flexmock(module).should_receive('write_patterns_file').and_return(None) + flexmock(module).should_receive('make_list_filter_flags').and_return(None) + insert_execute_command_mock(('borg', 'recreate', '--debug', '--show-rc', 'repo::archive')) + insert_logging_mock(logging.DEBUG) + + module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='1.2.3', + recreate_arguments=flexmock(path=None, list=None), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_log_json(): + flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( + ('repo::archive',) + ) + flexmock(module).should_receive('write_patterns_file').and_return(None) + flexmock(module).should_receive('make_list_filter_flags').and_return(None) + insert_execute_command_mock(('borg', 'recreate', '--log-json', 'repo::archive')) + + module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='1.2.3', + recreate_arguments=flexmock(path=None, list=None), + global_arguments=flexmock(dry_run=False, log_json=True), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_path_flag(): + flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( + ('repo::archive',) + ) + flexmock(module).should_receive('write_patterns_file').and_return(None) + flexmock(module).should_receive('make_list_filter_flags').and_return(None) + insert_execute_command_mock(('borg', 'recreate', '--path', '/some/path', 'repo::archive')) + + module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='1.2.3', + recreate_arguments=flexmock(path='/some/path', list=None), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_list_filter_flags(): + flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( + ('repo::archive',) + ) + flexmock(module).should_receive('write_patterns_file').and_return(None) + flexmock(module).should_receive('make_list_filter_flags').and_return('AME+-') + insert_execute_command_mock( + ('borg', 'recreate', '--list', '--filter', 'AME+-', 'repo::archive') + ) + + module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='1.2.3', + recreate_arguments=flexmock(path=None, list=True), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_patterns_from_flag(): + flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( + ('repo::archive',) + ) + mock_patterns_file = flexmock(name='patterns_file') + flexmock(module).should_receive('write_patterns_file').and_return(mock_patterns_file) + flexmock(module).should_receive('make_list_filter_flags').and_return(None) + insert_execute_command_mock( + ('borg', 'recreate', '--patterns-from', 'patterns_file', 'repo::archive') + ) + + module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='1.2.3', + recreate_arguments=flexmock(path=None, list=None), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=['pattern1', 'pattern2'], + ) + + +def test_recreate_with_exclude_flags(): + flexmock(module.borgmatic.borg.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) + flexmock(module).should_receive('write_patterns_file').and_return(None) + flexmock(module).should_receive('make_list_filter_flags').and_return(None) + # Mock the make_exclude_flags to return a sample exclude flag + flexmock(module).should_receive('make_exclude_flags').and_return(('--exclude', 'pattern')) + + insert_execute_command_mock(('borg', 'recreate', '--exclude', 'pattern', 'repo::archive')) + + module.recreate_archive( + repository='repo', + archive='archive', + config={'exclude_patterns': ['pattern']}, + local_borg_version='1.2.3', + recreate_arguments=flexmock(path=None, list=None), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) From 203e84b91f17e743a249dfa2b52a44fb6c8173af Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Tue, 25 Mar 2025 21:57:06 +0530 Subject: [PATCH 11/18] hotfix --- borgmatic/borg/recreate.py | 14 +++++++++++--- tests/unit/borg/test_recreate.py | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index a2a45047..bd35651f 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -29,6 +29,10 @@ def recreate_archive( ''' lock_wait = config.get('lock_wait', None) + if archive is None: + logger.error('Please provide a valid archive name.') + return + repo_archive_arg = make_repository_archive_flags(repository, archive, local_borg_version) exclude_flags = make_exclude_flags(config) @@ -40,9 +44,13 @@ def recreate_archive( recreate_cmd = ( (local_path, 'recreate') + (('--remote-path', remote_path) if remote_path else ()) - + (('--path', recreate_arguments.path) if recreate_arguments.path else ()) + + ( + ('--path', recreate_arguments.path) + if hasattr(recreate_arguments, 'path') and recreate_arguments.path + else () + ) + (('--log-json',) if global_arguments.log_json else ()) - + (('--lock-wait', str(lock_wait)) if lock_wait else ()) + + (('--lock-wait', str(lock_wait)) if lock_wait is not None else ()) + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) + (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ()) + (('--patterns-from', patterns_file.name) if patterns_file else ()) @@ -52,7 +60,7 @@ def recreate_archive( '--filter', make_list_filter_flags(local_borg_version, global_arguments.dry_run), ) - if recreate_arguments.list + if hasattr(recreate_arguments, 'list') and recreate_arguments.list else () ) + exclude_flags diff --git a/tests/unit/borg/test_recreate.py b/tests/unit/borg/test_recreate.py index 379ecc3a..4bab00d9 100644 --- a/tests/unit/borg/test_recreate.py +++ b/tests/unit/borg/test_recreate.py @@ -64,6 +64,24 @@ def test_recreate_calls_borg_with_required_flags(): ) +def test_recreate_calls_borg_without_archive(): + logger_mock = flexmock(module.logger) + logger_mock.should_receive('error').with_args('Please provide a valid archive name.').once() + + flexmock(module.borgmatic.execute).should_receive('execute_command').never() + + module.recreate_archive( + repository='repo', + archive=None, + config={}, + local_borg_version='1.2.3', + recreate_arguments=flexmock(path=None, list=None), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) + + def test_recreate_with_remote_path(): flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( ('repo::archive',) From 93569244189e119a7aa12193b939dd543c195022 Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Wed, 26 Mar 2025 22:30:11 +0530 Subject: [PATCH 12/18] add archive options --- borgmatic/borg/recreate.py | 34 +++++++++++++++++---------------- borgmatic/commands/arguments.py | 34 ++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index bd35651f..fcf5ac0c 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -4,7 +4,7 @@ import borgmatic.borg.environment import borgmatic.config.paths import borgmatic.execute from borgmatic.borg.create import make_exclude_flags, make_list_filter_flags, write_patterns_file -from borgmatic.borg.flags import make_repository_archive_flags +from borgmatic.borg import flags logger = logging.getLogger(__name__) @@ -28,27 +28,22 @@ def recreate_archive( Executes the recreate command with the given arguments. ''' lock_wait = config.get('lock_wait', None) - - if archive is None: - logger.error('Please provide a valid archive name.') - return - - repo_archive_arg = make_repository_archive_flags(repository, archive, local_borg_version) exclude_flags = make_exclude_flags(config) + compression = config.get('compression', None) # Write patterns to a temporary file and use that file with --patterns-from. patterns_file = write_patterns_file( patterns, borgmatic.config.paths.get_working_directory(config) ) - recreate_cmd = ( + recreate_command = ( (local_path, 'recreate') + (('--remote-path', remote_path) if remote_path else ()) - + ( - ('--path', recreate_arguments.path) - if hasattr(recreate_arguments, 'path') and recreate_arguments.path - else () - ) + # + ( + # ('--path', recreate_arguments.path) + # if recreate_arguments.path + # else () + # ) + (('--log-json',) if global_arguments.log_json else ()) + (('--lock-wait', str(lock_wait)) if lock_wait is not None else ()) + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) @@ -60,11 +55,18 @@ def recreate_archive( '--filter', make_list_filter_flags(local_borg_version, global_arguments.dry_run), ) - if hasattr(recreate_arguments, 'list') and recreate_arguments.list + if recreate_arguments.list else () ) + + (('--target', recreate_arguments.target) if recreate_arguments.target else ()) + + (('--comment', f'"{recreate_arguments.comment}"') if recreate_arguments.comment else ()) + + (('--compression', compression) if compression else ()) + exclude_flags - + repo_archive_arg + + ( + flags.make_repository_archive_flags(repository, archive, local_borg_version) + if archive + else flags.make_repository_flags(repository, local_borg_version) + ) ) if global_arguments.dry_run: @@ -72,7 +74,7 @@ def recreate_archive( return borgmatic.execute.execute_command( - full_command=recreate_cmd, + full_command=recreate_command, output_log_level=logging.INFO, environment=borgmatic.borg.environment.make_environment(config), working_directory=borgmatic.config.paths.get_working_directory(config), diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 7e942c63..8c3d7c6f 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -1556,18 +1556,38 @@ def make_parsers(): recreate_group = recreate_parser.add_argument_group('recreate arguments') recreate_group.add_argument( '--repository', - help='Path of the repository containing the archive', + help='Path of repository containing archive to recreate, defaults to the configured repository if there is only one, quoted globs supported', ) recreate_group.add_argument( '--archive', - help='Name of the archive to recreate', + help='Archive name, hash, or series to recreate', + ) + # recreate_group.add_argument( + # '--path', + # metavar='PATH', + # dest='path', + # help='Path to recreate the repository/archive', + # required=True, + # ) + recreate_group.add_argument( + '--list', dest='list', action='store_true', help='Show per-file details' ) recreate_group.add_argument( - '--path', - metavar='PATH', - dest='paths', - action='append', - help='Path to recreate the repository/archive', + '--target', + metavar='TARGET', + help='Name of new archive name', + ) + recreate_group.add_argument( + '--comment', + metavar='COMMENT', + help='Add a comment text to the archive, if archive not provided, consider all archives', + ) + recreate_group.add_argument( + '--compression', + '-C', + dest='compression', + metavar='COMPRESSION', + help='Select compression algorithm', ) recreate_group.add_argument( '-h', '--help', action='help', help='Show this help message and exit' From acc2814f117f0c884bd3186d93e9f6cf2df71916 Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Wed, 26 Mar 2025 23:39:06 +0530 Subject: [PATCH 13/18] add archive timestamp filter --- borgmatic/borg/recreate.py | 25 +++++++++++++++++++++++-- borgmatic/commands/arguments.py | 7 ++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index fcf5ac0c..640bcd05 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -1,14 +1,24 @@ import logging +from datetime import datetime import borgmatic.borg.environment import borgmatic.config.paths import borgmatic.execute -from borgmatic.borg.create import make_exclude_flags, make_list_filter_flags, write_patterns_file from borgmatic.borg import flags +from borgmatic.borg.create import make_exclude_flags, make_list_filter_flags, write_patterns_file logger = logging.getLogger(__name__) +def is_valid_timestamp(time_str): + try: + # Attempt to parse the time string using the expected format + datetime.strptime(time_str, r"%Y-%m-%dT%H:%M:%S") + return True + except ValueError: + return False + + def recreate_archive( repository, archive, @@ -27,6 +37,12 @@ def recreate_archive( Executes the recreate command with the given arguments. ''' + if recreate_arguments.timestamp and not is_valid_timestamp(recreate_arguments.timestamp): + logger.error( + 'Please provide a valid timestamp of format: yyyy-mm-ddThh:mm:ss. Example: 2025-03-26T14:45:59' + ) + return + lock_wait = config.get('lock_wait', None) exclude_flags = make_exclude_flags(config) compression = config.get('compression', None) @@ -58,7 +74,12 @@ def recreate_archive( if recreate_arguments.list else () ) - + (('--target', recreate_arguments.target) if recreate_arguments.target else ()) + # Flag --target works only for a single archive + + ( + ('--target', recreate_arguments.target) + if recreate_arguments.target and recreate_arguments.archive + else () + ) + (('--comment', f'"{recreate_arguments.comment}"') if recreate_arguments.comment else ()) + (('--compression', compression) if compression else ()) + exclude_flags diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 8c3d7c6f..f9d0e7af 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -1587,7 +1587,12 @@ def make_parsers(): '-C', dest='compression', metavar='COMPRESSION', - help='Select compression algorithm', + help='Select the compression algorithm', + ) + recreate_group.add_argument( + '--timestamp', + metavar='TIMESTAMP', + help='Manually specify the archive creation date/time (UTC, yyyy-mm-ddThh:mm:ss format)', ) recreate_group.add_argument( '-h', '--help', action='help', help='Show this help message and exit' From 26fd41da92e262e68fc87e24fd1e828937984f50 Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Thu, 27 Mar 2025 22:18:34 +0530 Subject: [PATCH 14/18] add rest of flags --- borgmatic/actions/recreate.py | 4 ++-- borgmatic/borg/recreate.py | 30 +++++++++++++++--------------- borgmatic/commands/arguments.py | 21 +++++++++++++-------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/borgmatic/actions/recreate.py b/borgmatic/actions/recreate.py index 53ee7fce..3cef7762 100644 --- a/borgmatic/actions/recreate.py +++ b/borgmatic/actions/recreate.py @@ -23,9 +23,9 @@ def run_recreate( repository, recreate_arguments.repository ): if recreate_arguments.archive: - logger.info(f'Recreating archive {recreate_arguments.archive}') + logger.answer(f'Recreating archive {recreate_arguments.archive}') else: - logger.info('Recreating repository') + logger.answer('Recreating repository') # Collect and process patterns. processed_patterns = process_patterns( diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index 640bcd05..ea2a2081 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -1,4 +1,5 @@ import logging +import shlex from datetime import datetime import borgmatic.borg.environment @@ -10,15 +11,6 @@ from borgmatic.borg.create import make_exclude_flags, make_list_filter_flags, wr logger = logging.getLogger(__name__) -def is_valid_timestamp(time_str): - try: - # Attempt to parse the time string using the expected format - datetime.strptime(time_str, r"%Y-%m-%dT%H:%M:%S") - return True - except ValueError: - return False - - def recreate_archive( repository, archive, @@ -37,15 +29,11 @@ def recreate_archive( Executes the recreate command with the given arguments. ''' - if recreate_arguments.timestamp and not is_valid_timestamp(recreate_arguments.timestamp): - logger.error( - 'Please provide a valid timestamp of format: yyyy-mm-ddThh:mm:ss. Example: 2025-03-26T14:45:59' - ) - return lock_wait = config.get('lock_wait', None) exclude_flags = make_exclude_flags(config) compression = config.get('compression', None) + chunker_params = config.get('chunker_params', None) # Write patterns to a temporary file and use that file with --patterns-from. patterns_file = write_patterns_file( @@ -80,8 +68,20 @@ def recreate_archive( if recreate_arguments.target and recreate_arguments.archive else () ) - + (('--comment', f'"{recreate_arguments.comment}"') if recreate_arguments.comment else ()) + + ( + ('--comment', shlex.quote(recreate_arguments.comment)) + if recreate_arguments.comment + else () + ) + + (('--timestamp', recreate_arguments.timestamp) if recreate_arguments.timestamp else ()) + (('--compression', compression) if compression else ()) + + (('--chunker-params', chunker_params) if chunker_params else ()) + + flags.make_match_archives_flags( + recreate_arguments.match_archives or archive or config.get('match_archives'), + config.get('archive_name_format'), + local_borg_version, + ) + + (('--recompress', recreate_arguments.recompress) if recreate_arguments.recompress else ()) + exclude_flags + ( flags.make_repository_archive_flags(repository, archive, local_borg_version) diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index f9d0e7af..689959ce 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -1582,17 +1582,22 @@ def make_parsers(): metavar='COMMENT', help='Add a comment text to the archive, if archive not provided, consider all archives', ) - recreate_group.add_argument( - '--compression', - '-C', - dest='compression', - metavar='COMPRESSION', - help='Select the compression algorithm', - ) recreate_group.add_argument( '--timestamp', metavar='TIMESTAMP', - help='Manually specify the archive creation date/time (UTC, yyyy-mm-ddThh:mm:ss format)', + help='Manually specify the archive creation date/time (UTC)', + ) + recreate_group.add_argument( + '-a', + '--match-archives', + '--glob-archives', + metavar='PATTERN', + help='Only consider archive names, hashes, or series matching this pattern', + ) + recreate_group.add_argument( + '--recompress', + metavar='MODE', + help='Recompress data chunks according to MODE: [if-different (default), always, never]', ) recreate_group.add_argument( '-h', '--help', action='help', help='Show this help message and exit' From 7020f0530ad0d889fdf25e6484acb4e8b168bb17 Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Fri, 28 Mar 2025 22:22:19 +0530 Subject: [PATCH 15/18] update existing tests --- borgmatic/borg/recreate.py | 13 ++- borgmatic/commands/arguments.py | 1 + tests/unit/borg/test_recreate.py | 195 ++++++++++++++++--------------- 3 files changed, 111 insertions(+), 98 deletions(-) diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index ea2a2081..81d5a6ed 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -1,6 +1,5 @@ import logging import shlex -from datetime import datetime import borgmatic.borg.environment import borgmatic.config.paths @@ -76,10 +75,14 @@ def recreate_archive( + (('--timestamp', recreate_arguments.timestamp) if recreate_arguments.timestamp else ()) + (('--compression', compression) if compression else ()) + (('--chunker-params', chunker_params) if chunker_params else ()) - + flags.make_match_archives_flags( - recreate_arguments.match_archives or archive or config.get('match_archives'), - config.get('archive_name_format'), - local_borg_version, + + ( + flags.make_match_archives_flags( + recreate_arguments.match_archives or archive or config.get('match_archives'), + config.get('archive_name_format'), + local_borg_version, + ) + if recreate_arguments.match_archives + else () ) + (('--recompress', recreate_arguments.recompress) if recreate_arguments.recompress else ()) + exclude_flags diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 689959ce..8e468e8a 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -1591,6 +1591,7 @@ def make_parsers(): '-a', '--match-archives', '--glob-archives', + dest='match_archives', metavar='PATTERN', help='Only consider archive names, hashes, or series matching this pattern', ) diff --git a/tests/unit/borg/test_recreate.py b/tests/unit/borg/test_recreate.py index 4bab00d9..aaa3c3fd 100644 --- a/tests/unit/borg/test_recreate.py +++ b/tests/unit/borg/test_recreate.py @@ -28,7 +28,17 @@ def test_recreate_archive_dry_run_skips_execution(): ).and_return(('repo::archive',)) flexmock(module.borgmatic.execute).should_receive('execute_command').never() - recreate_arguments = flexmock(repository=flexmock(), list=None, path=None) + recreate_arguments = flexmock( + repository=flexmock(), + list=None, + target=None, + comment=None, + timestamp=None, + compression=None, + chunker_params=None, + match_archives=None, + recompress=None, + ) result = module.recreate_archive( repository='repo', @@ -44,11 +54,6 @@ def test_recreate_archive_dry_run_skips_execution(): def test_recreate_calls_borg_with_required_flags(): - flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( - ('repo::archive',) - ) - flexmock(module).should_receive('write_patterns_file').and_return(None) - flexmock(module).should_receive('make_list_filter_flags').and_return(None) insert_execute_command_mock(('borg', 'recreate', 'repo::archive')) module.recreate_archive( @@ -56,7 +61,16 @@ def test_recreate_calls_borg_with_required_flags(): archive='archive', config={}, local_borg_version='1.2.3', - recreate_arguments=flexmock(path=None, list=None), + recreate_arguments=flexmock( + list=None, + target=None, + comment=None, + timestamp=None, + compression=None, + chunker_params=None, + match_archives=None, + recompress=None, + ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', remote_path=None, @@ -64,30 +78,7 @@ def test_recreate_calls_borg_with_required_flags(): ) -def test_recreate_calls_borg_without_archive(): - logger_mock = flexmock(module.logger) - logger_mock.should_receive('error').with_args('Please provide a valid archive name.').once() - - flexmock(module.borgmatic.execute).should_receive('execute_command').never() - - module.recreate_archive( - repository='repo', - archive=None, - config={}, - local_borg_version='1.2.3', - recreate_arguments=flexmock(path=None, list=None), - global_arguments=flexmock(dry_run=False, log_json=False), - local_path='borg', - patterns=None, - ) - - def test_recreate_with_remote_path(): - flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( - ('repo::archive',) - ) - flexmock(module).should_receive('write_patterns_file').and_return(None) - flexmock(module).should_receive('make_list_filter_flags').and_return(None) insert_execute_command_mock(('borg', 'recreate', '--remote-path', 'borg1', 'repo::archive')) module.recreate_archive( @@ -95,7 +86,16 @@ def test_recreate_with_remote_path(): archive='archive', config={}, local_borg_version='1.2.3', - recreate_arguments=flexmock(path=None, list=None), + recreate_arguments=flexmock( + list=None, + target=None, + comment=None, + timestamp=None, + compression=None, + chunker_params=None, + match_archives=None, + recompress=None, + ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', remote_path='borg1', @@ -104,11 +104,6 @@ def test_recreate_with_remote_path(): def test_recreate_with_lock_wait(): - flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( - ('repo::archive',) - ) - flexmock(module).should_receive('write_patterns_file').and_return(None) - flexmock(module).should_receive('make_list_filter_flags').and_return(None) insert_execute_command_mock(('borg', 'recreate', '--lock-wait', '5', 'repo::archive')) module.recreate_archive( @@ -116,7 +111,16 @@ def test_recreate_with_lock_wait(): archive='archive', config={'lock_wait': '5'}, local_borg_version='1.2.3', - recreate_arguments=flexmock(path=None, list=None), + recreate_arguments=flexmock( + list=None, + target=None, + comment=None, + timestamp=None, + compression=None, + chunker_params=None, + match_archives=None, + recompress=None, + ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', patterns=None, @@ -124,11 +128,6 @@ def test_recreate_with_lock_wait(): def test_recreate_with_log_info(): - flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( - ('repo::archive',) - ) - flexmock(module).should_receive('write_patterns_file').and_return(None) - flexmock(module).should_receive('make_list_filter_flags').and_return(None) insert_execute_command_mock(('borg', 'recreate', '--info', 'repo::archive')) insert_logging_mock(logging.INFO) @@ -138,7 +137,16 @@ def test_recreate_with_log_info(): archive='archive', config={}, local_borg_version='1.2.3', - recreate_arguments=flexmock(path=None, list=None), + recreate_arguments=flexmock( + list=None, + target=None, + comment=None, + timestamp=None, + compression=None, + chunker_params=None, + match_archives=None, + recompress=None, + ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', patterns=None, @@ -146,11 +154,6 @@ def test_recreate_with_log_info(): def test_recreate_with_log_debug(): - flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( - ('repo::archive',) - ) - flexmock(module).should_receive('write_patterns_file').and_return(None) - flexmock(module).should_receive('make_list_filter_flags').and_return(None) insert_execute_command_mock(('borg', 'recreate', '--debug', '--show-rc', 'repo::archive')) insert_logging_mock(logging.DEBUG) @@ -159,7 +162,16 @@ def test_recreate_with_log_debug(): archive='archive', config={}, local_borg_version='1.2.3', - recreate_arguments=flexmock(path=None, list=None), + recreate_arguments=flexmock( + list=None, + target=None, + comment=None, + timestamp=None, + compression=None, + chunker_params=None, + match_archives=None, + recompress=None, + ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', patterns=None, @@ -167,11 +179,6 @@ def test_recreate_with_log_debug(): def test_recreate_with_log_json(): - flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( - ('repo::archive',) - ) - flexmock(module).should_receive('write_patterns_file').and_return(None) - flexmock(module).should_receive('make_list_filter_flags').and_return(None) insert_execute_command_mock(('borg', 'recreate', '--log-json', 'repo::archive')) module.recreate_archive( @@ -179,38 +186,23 @@ def test_recreate_with_log_json(): archive='archive', config={}, local_borg_version='1.2.3', - recreate_arguments=flexmock(path=None, list=None), + recreate_arguments=flexmock( + list=None, + target=None, + comment=None, + timestamp=None, + compression=None, + chunker_params=None, + match_archives=None, + recompress=None, + ), global_arguments=flexmock(dry_run=False, log_json=True), local_path='borg', patterns=None, ) -def test_recreate_with_path_flag(): - flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( - ('repo::archive',) - ) - flexmock(module).should_receive('write_patterns_file').and_return(None) - flexmock(module).should_receive('make_list_filter_flags').and_return(None) - insert_execute_command_mock(('borg', 'recreate', '--path', '/some/path', 'repo::archive')) - - module.recreate_archive( - repository='repo', - archive='archive', - config={}, - local_borg_version='1.2.3', - recreate_arguments=flexmock(path='/some/path', list=None), - global_arguments=flexmock(dry_run=False, log_json=False), - local_path='borg', - patterns=None, - ) - - def test_recreate_with_list_filter_flags(): - flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( - ('repo::archive',) - ) - flexmock(module).should_receive('write_patterns_file').and_return(None) flexmock(module).should_receive('make_list_filter_flags').and_return('AME+-') insert_execute_command_mock( ('borg', 'recreate', '--list', '--filter', 'AME+-', 'repo::archive') @@ -221,7 +213,16 @@ def test_recreate_with_list_filter_flags(): archive='archive', config={}, local_borg_version='1.2.3', - recreate_arguments=flexmock(path=None, list=True), + recreate_arguments=flexmock( + list=True, + target=None, + comment=None, + timestamp=None, + compression=None, + chunker_params=None, + match_archives=None, + recompress=None, + ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', patterns=None, @@ -229,12 +230,8 @@ def test_recreate_with_list_filter_flags(): def test_recreate_with_patterns_from_flag(): - flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return( - ('repo::archive',) - ) mock_patterns_file = flexmock(name='patterns_file') flexmock(module).should_receive('write_patterns_file').and_return(mock_patterns_file) - flexmock(module).should_receive('make_list_filter_flags').and_return(None) insert_execute_command_mock( ('borg', 'recreate', '--patterns-from', 'patterns_file', 'repo::archive') ) @@ -244,7 +241,16 @@ def test_recreate_with_patterns_from_flag(): archive='archive', config={}, local_borg_version='1.2.3', - recreate_arguments=flexmock(path=None, list=None), + recreate_arguments=flexmock( + list=None, + target=None, + comment=None, + timestamp=None, + compression=None, + chunker_params=None, + match_archives=None, + recompress=None, + ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', patterns=['pattern1', 'pattern2'], @@ -252,14 +258,7 @@ def test_recreate_with_patterns_from_flag(): def test_recreate_with_exclude_flags(): - flexmock(module.borgmatic.borg.flags).should_receive( - 'make_repository_archive_flags' - ).and_return(('repo::archive',)) - flexmock(module).should_receive('write_patterns_file').and_return(None) - flexmock(module).should_receive('make_list_filter_flags').and_return(None) - # Mock the make_exclude_flags to return a sample exclude flag flexmock(module).should_receive('make_exclude_flags').and_return(('--exclude', 'pattern')) - insert_execute_command_mock(('borg', 'recreate', '--exclude', 'pattern', 'repo::archive')) module.recreate_archive( @@ -267,8 +266,18 @@ def test_recreate_with_exclude_flags(): archive='archive', config={'exclude_patterns': ['pattern']}, local_borg_version='1.2.3', - recreate_arguments=flexmock(path=None, list=None), + recreate_arguments=flexmock( + list=None, + target=None, + comment=None, + timestamp=None, + compression=None, + chunker_params=None, + match_archives=None, + recompress=None, + ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', patterns=None, ) + From 8b3a682edfa833383f53ef8b2f1dd9b6b5e08baf Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Sat, 29 Mar 2025 01:26:20 +0530 Subject: [PATCH 16/18] add tests and minor fixes --- borgmatic/borg/recreate.py | 15 +- borgmatic/commands/arguments.py | 12 -- tests/unit/borg/test_recreate.py | 269 +++++++++++++++++++++++++++---- 3 files changed, 243 insertions(+), 53 deletions(-) diff --git a/borgmatic/borg/recreate.py b/borgmatic/borg/recreate.py index 81d5a6ed..bea11b58 100644 --- a/borgmatic/borg/recreate.py +++ b/borgmatic/borg/recreate.py @@ -33,6 +33,8 @@ def recreate_archive( exclude_flags = make_exclude_flags(config) compression = config.get('compression', None) chunker_params = config.get('chunker_params', None) + # Available recompress MODES: 'if-different' (default), 'always', 'never' + recompress = config.get('recompress', None) # Write patterns to a temporary file and use that file with --patterns-from. patterns_file = write_patterns_file( @@ -42,11 +44,6 @@ def recreate_archive( recreate_command = ( (local_path, 'recreate') + (('--remote-path', remote_path) if remote_path else ()) - # + ( - # ('--path', recreate_arguments.path) - # if recreate_arguments.path - # else () - # ) + (('--log-json',) if global_arguments.log_json else ()) + (('--lock-wait', str(lock_wait)) if lock_wait is not None else ()) + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) @@ -62,11 +59,7 @@ def recreate_archive( else () ) # Flag --target works only for a single archive - + ( - ('--target', recreate_arguments.target) - if recreate_arguments.target and recreate_arguments.archive - else () - ) + + (('--target', recreate_arguments.target) if recreate_arguments.target and archive else ()) + ( ('--comment', shlex.quote(recreate_arguments.comment)) if recreate_arguments.comment @@ -84,7 +77,7 @@ def recreate_archive( if recreate_arguments.match_archives else () ) - + (('--recompress', recreate_arguments.recompress) if recreate_arguments.recompress else ()) + + (('--recompress', recompress) if recompress else ()) + exclude_flags + ( flags.make_repository_archive_flags(repository, archive, local_borg_version) diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 8e468e8a..b0185348 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -1562,13 +1562,6 @@ def make_parsers(): '--archive', help='Archive name, hash, or series to recreate', ) - # recreate_group.add_argument( - # '--path', - # metavar='PATH', - # dest='path', - # help='Path to recreate the repository/archive', - # required=True, - # ) recreate_group.add_argument( '--list', dest='list', action='store_true', help='Show per-file details' ) @@ -1595,11 +1588,6 @@ def make_parsers(): metavar='PATTERN', help='Only consider archive names, hashes, or series matching this pattern', ) - recreate_group.add_argument( - '--recompress', - metavar='MODE', - help='Recompress data chunks according to MODE: [if-different (default), always, never]', - ) recreate_group.add_argument( '-h', '--help', action='help', help='Show this help message and exit' ) diff --git a/tests/unit/borg/test_recreate.py b/tests/unit/borg/test_recreate.py index aaa3c3fd..5ec0eeaf 100644 --- a/tests/unit/borg/test_recreate.py +++ b/tests/unit/borg/test_recreate.py @@ -1,4 +1,5 @@ import logging +import shlex from flexmock import flexmock @@ -34,10 +35,7 @@ def test_recreate_archive_dry_run_skips_execution(): target=None, comment=None, timestamp=None, - compression=None, - chunker_params=None, match_archives=None, - recompress=None, ) result = module.recreate_archive( @@ -66,10 +64,7 @@ def test_recreate_calls_borg_with_required_flags(): target=None, comment=None, timestamp=None, - compression=None, - chunker_params=None, match_archives=None, - recompress=None, ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', @@ -91,10 +86,7 @@ def test_recreate_with_remote_path(): target=None, comment=None, timestamp=None, - compression=None, - chunker_params=None, match_archives=None, - recompress=None, ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', @@ -116,10 +108,7 @@ def test_recreate_with_lock_wait(): target=None, comment=None, timestamp=None, - compression=None, - chunker_params=None, match_archives=None, - recompress=None, ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', @@ -142,10 +131,7 @@ def test_recreate_with_log_info(): target=None, comment=None, timestamp=None, - compression=None, - chunker_params=None, match_archives=None, - recompress=None, ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', @@ -167,10 +153,7 @@ def test_recreate_with_log_debug(): target=None, comment=None, timestamp=None, - compression=None, - chunker_params=None, match_archives=None, - recompress=None, ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', @@ -191,10 +174,7 @@ def test_recreate_with_log_json(): target=None, comment=None, timestamp=None, - compression=None, - chunker_params=None, match_archives=None, - recompress=None, ), global_arguments=flexmock(dry_run=False, log_json=True), local_path='borg', @@ -218,10 +198,7 @@ def test_recreate_with_list_filter_flags(): target=None, comment=None, timestamp=None, - compression=None, - chunker_params=None, match_archives=None, - recompress=None, ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', @@ -246,10 +223,7 @@ def test_recreate_with_patterns_from_flag(): target=None, comment=None, timestamp=None, - compression=None, - chunker_params=None, match_archives=None, - recompress=None, ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', @@ -271,13 +245,248 @@ def test_recreate_with_exclude_flags(): target=None, comment=None, timestamp=None, - compression=None, - chunker_params=None, match_archives=None, - recompress=None, ), global_arguments=flexmock(dry_run=False, log_json=False), local_path='borg', patterns=None, ) + +def test_recreate_with_target_flag(): + insert_execute_command_mock(('borg', 'recreate', '--target', 'new-archive', 'repo::archive')) + + module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='1.2.3', + recreate_arguments=flexmock( + list=None, + target='new-archive', + comment=None, + timestamp=None, + match_archives=None, + ), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_comment_flag(): + insert_execute_command_mock( + ('borg', 'recreate', '--comment', shlex.quote('This is a test comment'), 'repo::archive') + ) + + module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='1.2.3', + recreate_arguments=flexmock( + list=None, + target=None, + comment='This is a test comment', + timestamp=None, + match_archives=None, + ), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_timestamp_flag(): + insert_execute_command_mock( + ('borg', 'recreate', '--timestamp', '2023-10-01T12:00:00', 'repo::archive') + ) + + module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='1.2.3', + recreate_arguments=flexmock( + list=None, + target=None, + comment=None, + timestamp='2023-10-01T12:00:00', + match_archives=None, + ), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_compression_flag(): + insert_execute_command_mock(('borg', 'recreate', '--compression', 'lz4', 'repo::archive')) + + module.recreate_archive( + repository='repo', + archive='archive', + config={'compression': 'lz4'}, + 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=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_chunker_params_flag(): + insert_execute_command_mock( + ('borg', 'recreate', '--chunker-params', '19,23,21,4095', 'repo::archive') + ) + + module.recreate_archive( + repository='repo', + archive='archive', + config={'chunker_params': '19,23,21,4095'}, + 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=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_recompress_flag(): + insert_execute_command_mock(('borg', 'recreate', '--recompress', 'always', 'repo::archive')) + + module.recreate_archive( + repository='repo', + archive='archive', + config={'recompress': 'always'}, + 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=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_match_archives_star(): + insert_execute_command_mock(('borg', 'recreate', 'repo::archive')) + + 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='*', + ), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_match_archives_regex(): + insert_execute_command_mock(('borg', 'recreate', 'repo::archive')) + + 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='re:.*', + ), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_match_archives_shell(): + insert_execute_command_mock(('borg', 'recreate', 'repo::archive')) + + 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='sh:*', + ), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_glob_archives_flag(): + insert_execute_command_mock(('borg', 'recreate', '--glob-archives', 'foo-*', 'repo::archive')) + + 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='foo-*', + ), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) + + +def test_recreate_with_match_archives_flag(): + insert_execute_command_mock( + ('borg', 'recreate', '--match-archives', 'sh:foo-*', '--repo', 'repo', 'archive') + ) + + module.recreate_archive( + repository='repo', + archive='archive', + config={}, + local_borg_version='2.0.0b3', + recreate_arguments=flexmock( + list=None, + target=None, + comment=None, + timestamp=None, + match_archives='sh:foo-*', + ), + global_arguments=flexmock(dry_run=False, log_json=False), + local_path='borg', + patterns=None, + ) From 2716d9d0b0980af80a2840abff40573d703f1306 Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Sat, 29 Mar 2025 23:25:50 +0530 Subject: [PATCH 17/18] add to schema --- borgmatic/config/schema.yaml | 18 ++++ tests/unit/borg/test_recreate.py | 158 ++++++++++++++++++++++++++++++- 2 files changed, 173 insertions(+), 3 deletions(-) diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 9fb8f361..9d622b31 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -284,6 +284,24 @@ properties: http://borgbackup.readthedocs.io/en/stable/usage/create.html for details. Defaults to "lz4". example: lz4 + recompress: + type: string + enum: ['if-different', 'always', 'never'] + description: | + Mode for recompressing data chunks according to MODE. + Possible modes are: + - "if-different": Recompress if the current compression + is with a different compression algorithm. + - "always": Recompress even if the current compression + is with the same compression algorithm. Use this to change + the compression level. + - "never": Do not recompress. Use this option to explicitly + prevent recompression. + See https://borgbackup.readthedocs.io/en/stable/usage/recreate.html + for details. + Not passing --recompress is equivalent to "--recompress never". + Defaults to "if-different". + example: if-different upload_rate_limit: type: integer description: | diff --git a/tests/unit/borg/test_recreate.py b/tests/unit/borg/test_recreate.py index 5ec0eeaf..fe2739b7 100644 --- a/tests/unit/borg/test_recreate.py +++ b/tests/unit/borg/test_recreate.py @@ -7,9 +7,6 @@ from borgmatic.borg import recreate as module from ..test_verbosity import insert_logging_mock -# from borgmatic.borg.pattern import Pattern, Pattern_type, Pattern_style, Pattern_source -# from borgmatic.borg.create import make_exclude_flags, make_list_filter_flags - def insert_execute_command_mock(command, working_directory=None, borg_exit_codes=None): flexmock(module.borgmatic.borg.environment).should_receive('make_environment') @@ -23,7 +20,21 @@ def insert_execute_command_mock(command, working_directory=None, borg_exit_codes ).once() +def mock_dependencies(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) + + def test_recreate_archive_dry_run_skips_execution(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( 'make_repository_archive_flags' ).and_return(('repo::archive',)) @@ -52,6 +63,13 @@ def test_recreate_archive_dry_run_skips_execution(): def test_recreate_calls_borg_with_required_flags(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock(('borg', 'recreate', 'repo::archive')) module.recreate_archive( @@ -74,6 +92,13 @@ def test_recreate_calls_borg_with_required_flags(): def test_recreate_with_remote_path(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock(('borg', 'recreate', '--remote-path', 'borg1', 'repo::archive')) module.recreate_archive( @@ -96,6 +121,13 @@ def test_recreate_with_remote_path(): def test_recreate_with_lock_wait(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock(('borg', 'recreate', '--lock-wait', '5', 'repo::archive')) module.recreate_archive( @@ -117,6 +149,13 @@ def test_recreate_with_lock_wait(): def test_recreate_with_log_info(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock(('borg', 'recreate', '--info', 'repo::archive')) insert_logging_mock(logging.INFO) @@ -140,6 +179,13 @@ def test_recreate_with_log_info(): def test_recreate_with_log_debug(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock(('borg', 'recreate', '--debug', '--show-rc', 'repo::archive')) insert_logging_mock(logging.DEBUG) @@ -162,6 +208,13 @@ def test_recreate_with_log_debug(): def test_recreate_with_log_json(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock(('borg', 'recreate', '--log-json', 'repo::archive')) module.recreate_archive( @@ -183,6 +236,12 @@ def test_recreate_with_log_json(): def test_recreate_with_list_filter_flags(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(()) + flexmock(module.borgmatic.borg.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) flexmock(module).should_receive('make_list_filter_flags').and_return('AME+-') insert_execute_command_mock( ('borg', 'recreate', '--list', '--filter', 'AME+-', 'repo::archive') @@ -207,6 +266,12 @@ def test_recreate_with_list_filter_flags(): def test_recreate_with_patterns_from_flag(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) mock_patterns_file = flexmock(name='patterns_file') flexmock(module).should_receive('write_patterns_file').and_return(mock_patterns_file) insert_execute_command_mock( @@ -232,6 +297,12 @@ def test_recreate_with_patterns_from_flag(): def test_recreate_with_exclude_flags(): + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) flexmock(module).should_receive('make_exclude_flags').and_return(('--exclude', 'pattern')) insert_execute_command_mock(('borg', 'recreate', '--exclude', 'pattern', 'repo::archive')) @@ -254,6 +325,13 @@ def test_recreate_with_exclude_flags(): def test_recreate_with_target_flag(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock(('borg', 'recreate', '--target', 'new-archive', 'repo::archive')) module.recreate_archive( @@ -275,6 +353,13 @@ def test_recreate_with_target_flag(): def test_recreate_with_comment_flag(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock( ('borg', 'recreate', '--comment', shlex.quote('This is a test comment'), 'repo::archive') ) @@ -298,6 +383,13 @@ def test_recreate_with_comment_flag(): def test_recreate_with_timestamp_flag(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock( ('borg', 'recreate', '--timestamp', '2023-10-01T12:00:00', 'repo::archive') ) @@ -321,6 +413,13 @@ def test_recreate_with_timestamp_flag(): def test_recreate_with_compression_flag(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock(('borg', 'recreate', '--compression', 'lz4', 'repo::archive')) module.recreate_archive( @@ -342,6 +441,13 @@ def test_recreate_with_compression_flag(): def test_recreate_with_chunker_params_flag(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock( ('borg', 'recreate', '--chunker-params', '19,23,21,4095', 'repo::archive') ) @@ -365,6 +471,13 @@ def test_recreate_with_chunker_params_flag(): def test_recreate_with_recompress_flag(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock(('borg', 'recreate', '--recompress', 'always', 'repo::archive')) module.recreate_archive( @@ -386,6 +499,13 @@ def test_recreate_with_recompress_flag(): def test_recreate_with_match_archives_star(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock(('borg', 'recreate', 'repo::archive')) module.recreate_archive( @@ -407,6 +527,13 @@ def test_recreate_with_match_archives_star(): def test_recreate_with_match_archives_regex(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock(('borg', 'recreate', 'repo::archive')) module.recreate_archive( @@ -428,6 +555,13 @@ def test_recreate_with_match_archives_regex(): def test_recreate_with_match_archives_shell(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).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.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock(('borg', 'recreate', 'repo::archive')) module.recreate_archive( @@ -449,6 +583,15 @@ def test_recreate_with_match_archives_shell(): def test_recreate_with_glob_archives_flag(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('') + flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return( + ('--glob-archives', 'foo-*') + ) + flexmock(module.borgmatic.borg.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('repo::archive',)) insert_execute_command_mock(('borg', 'recreate', '--glob-archives', 'foo-*', 'repo::archive')) module.recreate_archive( @@ -470,6 +613,15 @@ def test_recreate_with_glob_archives_flag(): def test_recreate_with_match_archives_flag(): + flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(()) + flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None) + flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('') + flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return( + ('--match-archives', 'sh:foo-*') + ) + flexmock(module.borgmatic.borg.flags).should_receive( + 'make_repository_archive_flags' + ).and_return(('--repo', 'repo', 'archive')) insert_execute_command_mock( ('borg', 'recreate', '--match-archives', 'sh:foo-*', '--repo', 'repo', 'archive') ) From dbf2e78f6253757793f6639df711fec9aa6a95b2 Mon Sep 17 00:00:00 2001 From: Vandal <86826719+VandalByte@users.noreply.github.com> Date: Sun, 30 Mar 2025 03:05:46 +0530 Subject: [PATCH 18/18] help changes --- borgmatic/commands/arguments.py | 6 +++--- borgmatic/config/schema.yaml | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index b0185348..6269a4e0 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -1568,17 +1568,17 @@ def make_parsers(): recreate_group.add_argument( '--target', metavar='TARGET', - help='Name of new archive name', + help='Create a new archive from the specified archive (via --archive), without replacing it', ) recreate_group.add_argument( '--comment', metavar='COMMENT', - help='Add a comment text to the archive, if archive not provided, consider all archives', + help='Add a comment text to the archive or, if an archive is not provided, to all matching archives', ) recreate_group.add_argument( '--timestamp', metavar='TIMESTAMP', - help='Manually specify the archive creation date/time (UTC)', + help='Manually override the archive creation date/time (UTC)', ) recreate_group.add_argument( '-a', diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 9d622b31..f3b98f36 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -299,8 +299,7 @@ properties: prevent recompression. See https://borgbackup.readthedocs.io/en/stable/usage/recreate.html for details. - Not passing --recompress is equivalent to "--recompress never". - Defaults to "if-different". + Defaults to "never". example: if-different upload_rate_limit: type: integer