From 52234c47e640559308f000a96b92bd25ceafb447 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Sun, 22 Mar 2026 20:10:46 -0700 Subject: [PATCH] Fix a regression in which running borgmatic with no arguments and no configuration files doesn't error as expected (#1286). --- NEWS | 5 ++++- borgmatic/commands/borgmatic.py | 18 ++++++++-------- borgmatic/config/schema.yaml | 6 +++--- scripts/release | 2 +- tests/unit/commands/test_borgmatic.py | 31 +++++++++++++++++++++------ 5 files changed, 41 insertions(+), 21 deletions(-) diff --git a/NEWS b/NEWS index 2c890c28..faf5a4b7 100644 --- a/NEWS +++ b/NEWS @@ -1,13 +1,16 @@ 2.1.4.dev0 * #1266: Add a stand-alone borgmatic Linux binary to the release downloads to serve as another way to install borgmatic. Consider this binary a beta feature. + * #1286: Fix a regression in which running borgmatic with no arguments and no configuration files + doesn't error as expected. * When Borg exits with a warning exit code, show a description of it, so you don't have to lookup the code. * Split out borgmatic installation documentation to its own page, so it's easier to find. * Switch the default borgmatic installation method from pipx to uv, as uv is faster and used for borgmatic development. If you'd like to switch, see the documentation for more information: https://torsion.org/borgmatic/how-to/upgrade/ - * Move project tracker from Gitea to Forgejo. + * Move the project tracker from Gitea to Forgejo. + * Fix a regression in which borgmatic didn't show an error message when run with no configuration. 2.1.3 * #1175: Add a "files_changed" option for customizing Borg's file modification detection. diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index f2cf8b7f..3f48891a 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -1044,16 +1044,17 @@ def exit_with_help_link(): # pragma: no cover sys.exit(1) -def check_and_show_help_on_no_args(configs): +def check_and_show_help_on_no_args(configs, schema): ''' - Given a dict of configuration filename to corresponding parsed configuration, check if the - borgmatic command is run without any arguments. If the configuration option "default_actions" is - set to False, show the help message. Otherwise, trigger the default backup behavior. + Given a dict of configuration filename to corresponding parsed configuration and the + configuration schema as a dict, check if the borgmatic command was run without any arguments. If + the configuration option "default_actions" is set to False, then show the help message an exit. ''' - if len(sys.argv) == 1: # No arguments provided + if len(sys.argv) == 1: # No arguments provided. default_actions = any(config.get('default_actions', True) for config in configs.values()) - if not default_actions: - parse_arguments('--help') + + if configs and not default_actions: + parse_arguments(schema, '--help') sys.exit(0) @@ -1150,8 +1151,7 @@ def main(extra_summary_logs=()): # pragma: no cover resolve_env=global_arguments.resolve_env and not arguments.get('validate'), ) - # Use the helper function to check and show help on no arguments, passing the preloaded configs - check_and_show_help_on_no_args(configs) + check_and_show_help_on_no_args(configs, schema) configuration_parse_errors = ( (max(log.levelno for log in parse_logs) >= logging.CRITICAL) if parse_logs else False diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index c381444c..76106d89 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1098,9 +1098,9 @@ properties: default_actions: type: boolean description: | - Whether to apply default actions (create, prune, compact and check) - when no arguments are supplied to the borgmatic command. If set to - false, borgmatic displays the help message instead. + Whether to run default actions (create, prune, compact, and check) + when no arguments are given on the command line. If set to false, + borgmatic displays the help message instead. example: true skip_actions: type: array diff --git a/scripts/release b/scripts/release index 0334b7e4..c00b6ca5 100755 --- a/scripts/release +++ b/scripts/release @@ -49,7 +49,7 @@ source binary/bin/activate uv pip install -r binary_requirements.txt nuitka --mode=onefile --enable-plugin=upx --include-package-data=borgmatic --include-data-dir=borgmatic.egg-info=borgmatic.egg-info --include-package=borgmatic.hooks --include-package=apprise --no-deployment-flag=self-execution borgmatic/commands/borgmatic.py deactivate -rm -fr binary +rm -fr binary borgmatic.build borgmatic.dist standalone_binary_path="dist/borgmatic-${version}-binary-linux-glibc-x86_64" mv borgmatic.bin "$standalone_binary_path" diff --git a/tests/unit/commands/test_borgmatic.py b/tests/unit/commands/test_borgmatic.py index 5c076118..73ae1a09 100644 --- a/tests/unit/commands/test_borgmatic.py +++ b/tests/unit/commands/test_borgmatic.py @@ -2582,23 +2582,40 @@ def test_collect_configuration_run_summary_logs_outputs_merged_json_results(): def test_check_and_show_help_on_no_args_shows_help_when_no_args_and_default_actions_false(): flexmock(module.sys).should_receive('argv').and_return(['borgmatic']) - flexmock(module).should_receive('parse_arguments').with_args('--help').once() + flexmock(module).should_receive('parse_arguments').with_args({}, '--help').once() flexmock(module.sys).should_receive('exit').with_args(0).once() - module.check_and_show_help_on_no_args({'test.yaml': {'default_actions': False}}) + + module.check_and_show_help_on_no_args( + configs={'test.yaml': {'default_actions': False}}, schema={} + ) + + +def test_check_and_show_help_on_no_args_does_not_show_help_when_no_args_and_no_configurations(): + flexmock(module.sys).should_receive('argv').and_return(['borgmatic']) + flexmock(module).should_receive('parse_arguments').never() + flexmock(module.sys).should_receive('exit').never() + + module.check_and_show_help_on_no_args(configs={}, schema={}) def test_check_and_show_help_on_no_args_does_not_show_help_when_no_args_and_default_actions_true(): flexmock(module.sys).should_receive('argv').and_return(['borgmatic']) flexmock(module).should_receive('parse_arguments').never() flexmock(module.sys).should_receive('exit').never() - module.check_and_show_help_on_no_args({'test.yaml': {'default_actions': True}}) + + module.check_and_show_help_on_no_args( + configs={'test.yaml': {'default_actions': True}}, schema={} + ) def test_check_and_show_help_on_no_args_does_not_show_help_when_args_provided(): flexmock(module.sys).should_receive('argv').and_return(['borgmatic', '--create']) flexmock(module).should_receive('parse_arguments').never() flexmock(module.sys).should_receive('exit').never() - module.check_and_show_help_on_no_args({'test.yaml': {'default_actions': False}}) + + module.check_and_show_help_on_no_args( + configs={'test.yaml': {'default_actions': False}}, schema={} + ) def test_check_and_show_help_on_no_args_with_no_default_actions_in_all_configs(): @@ -2611,10 +2628,10 @@ def test_check_and_show_help_on_no_args_with_no_default_actions_in_all_configs() } # Expect help to be shown - flexmock(module).should_receive('parse_arguments').with_args('--help').once() + flexmock(module).should_receive('parse_arguments').with_args({}, '--help').once() flexmock(module.sys).should_receive('exit').with_args(0).once() - module.check_and_show_help_on_no_args(configs) + module.check_and_show_help_on_no_args(configs=configs, schema={}) def test_check_and_show_help_on_no_args_with_conflicting_configs(): @@ -2630,7 +2647,7 @@ def test_check_and_show_help_on_no_args_with_conflicting_configs(): flexmock(module).should_receive('parse_arguments').never() flexmock(module.sys).should_receive('exit').never() - module.check_and_show_help_on_no_args(configs) + module.check_and_show_help_on_no_args(configs=configs, schema={}) def test_get_singular_option_value_with_conflicting_values_exits():