Fix a regression in which running borgmatic with no arguments and no configuration files doesn't error as expected (#1286).
All checks were successful
build / test (push) Successful in 8m23s
build / docs (push) Successful in 1m51s

This commit is contained in:
Dan Helfman 2026-03-22 20:10:46 -07:00
commit 52234c47e6
5 changed files with 41 additions and 21 deletions

5
NEWS
View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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():