From 776bf3fcd45e3244050eeb5cccf9389ce6c53a86 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Thu, 8 Jan 2026 22:26:15 -0800 Subject: [PATCH 1/7] Track "last lines" per-process instead of per-buffer, now that each process can have multiple active buffers (#485). --- borgmatic/execute.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/borgmatic/execute.py b/borgmatic/execute.py index 629ad946..c5d50e48 100644 --- a/borgmatic/execute.py +++ b/borgmatic/execute.py @@ -189,7 +189,7 @@ def log_outputs(processes, exclude_stdouts, output_log_level, borg_local_path, b captured, in which case it won't be logged. ''' # Map from output buffer to sequence of last lines. - buffer_last_lines = collections.defaultdict(list) + process_last_lines = collections.defaultdict(list) process_for_output_buffer = { buffer: process for process in processes @@ -242,7 +242,7 @@ def log_outputs(processes, exclude_stdouts, output_log_level, borg_local_path, b borg_local_path=borg_local_path, command=command, ), - last_lines=buffer_last_lines[ready_buffer], + last_lines=process_last_lines[ready_process], captured_output=captured_outputs[ready_process], ) @@ -263,11 +263,11 @@ def log_outputs(processes, exclude_stdouts, output_log_level, borg_local_path, b exit_status = interpret_exit_code(command, exit_code, borg_local_path, borg_exit_codes) if exit_status in {Exit_status.ERROR, Exit_status.WARNING}: + last_lines = process_last_lines[process] + # If an error occurs, include its output in the raised exception so that we don't # inadvertently hide error output. for output_buffer in output_buffers_for_process(process, exclude_stdouts): - last_lines = buffer_last_lines[output_buffer] if output_buffer else [] - # Collect any straggling output lines that came in since we last gathered output. while output_buffer: # pragma: no cover line = output_buffer.readline().rstrip().decode() From 4d266508849f18ec00d31edbbd2f173bc7bde1bc Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Thu, 8 Jan 2026 22:54:37 -0800 Subject: [PATCH 2/7] Fix incorrect use of "--log-json" in the "key change-passphrase" action (#485). --- borgmatic/borg/change_passphrase.py | 3 +-- tests/unit/borg/test_change_passphrase.py | 24 +++++++++++------------ 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/borgmatic/borg/change_passphrase.py b/borgmatic/borg/change_passphrase.py index 626e278a..99ad9d65 100644 --- a/borgmatic/borg/change_passphrase.py +++ b/borgmatic/borg/change_passphrase.py @@ -32,7 +32,6 @@ def change_passphrase( (local_path, 'key', 'change-passphrase') + (('--remote-path', remote_path) if remote_path else ()) + (('--umask', str(umask)) if umask else ()) - + ('--log-json',) + (('--lock-wait', str(lock_wait)) if lock_wait else ()) + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) + (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ()) @@ -66,5 +65,5 @@ def change_passphrase( ) logger.answer( - f"{repository_path}: Don't forget to update your encryption_passphrase option (if needed)", + "Don't forget to update your encryption_passphrase option (if needed)", ) diff --git a/tests/unit/borg/test_change_passphrase.py b/tests/unit/borg/test_change_passphrase.py index 63a3e0c1..b8fa01af 100644 --- a/tests/unit/borg/test_change_passphrase.py +++ b/tests/unit/borg/test_change_passphrase.py @@ -34,7 +34,7 @@ def insert_execute_command_mock( def test_change_passphrase_calls_borg_with_required_flags(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) - insert_execute_command_mock(('borg', 'key', 'change-passphrase', '--log-json', 'repo')) + insert_execute_command_mock(('borg', 'key', 'change-passphrase', 'repo')) module.change_passphrase( repository_path='repo', @@ -47,7 +47,7 @@ def test_change_passphrase_calls_borg_with_required_flags(): def test_change_passphrase_calls_borg_with_local_path(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) - insert_execute_command_mock(('borg1', 'key', 'change-passphrase', '--log-json', 'repo')) + insert_execute_command_mock(('borg1', 'key', 'change-passphrase', 'repo')) module.change_passphrase( repository_path='repo', @@ -64,7 +64,7 @@ def test_change_passphrase_calls_borg_using_exit_codes(): borg_exit_codes = flexmock() config = {'borg_exit_codes': borg_exit_codes} insert_execute_command_mock( - ('borg', 'key', 'change-passphrase', '--log-json', 'repo'), + ('borg', 'key', 'change-passphrase', 'repo'), config=config, borg_exit_codes=borg_exit_codes, ) @@ -81,7 +81,7 @@ def test_change_passphrase_calls_borg_using_exit_codes(): def test_change_passphrase_calls_borg_with_remote_path_flags(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) insert_execute_command_mock( - ('borg', 'key', 'change-passphrase', '--remote-path', 'borg1', '--log-json', 'repo'), + ('borg', 'key', 'change-passphrase', '--remote-path', 'borg1', 'repo'), ) module.change_passphrase( @@ -98,7 +98,7 @@ def test_change_passphrase_calls_borg_with_umask_flags(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) config = {'umask': '0770'} insert_execute_command_mock( - ('borg', 'key', 'change-passphrase', '--umask', '0770', '--log-json', 'repo'), + ('borg', 'key', 'change-passphrase', '--umask', '0770', 'repo'), config=config, ) @@ -115,7 +115,7 @@ def test_change_passphrase_calls_borg_with_lock_wait_flags(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) config = {'lock_wait': '5'} insert_execute_command_mock( - ('borg', 'key', 'change-passphrase', '--log-json', '--lock-wait', '5', 'repo'), + ('borg', 'key', 'change-passphrase', '--lock-wait', '5', 'repo'), config=config, ) @@ -132,7 +132,7 @@ def test_change_passphrase_calls_borg_with_extra_borg_options(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) config = {'extra_borg_options': {'key_change_passphrase': '--extra "value with space"'}} insert_execute_command_mock( - ('borg', 'key', 'change-passphrase', '--log-json', '--extra', 'value with space', 'repo'), + ('borg', 'key', 'change-passphrase', '--extra', 'value with space', 'repo'), config=config, ) @@ -147,9 +147,7 @@ def test_change_passphrase_calls_borg_with_extra_borg_options(): def test_change_passphrase_with_log_info_calls_borg_with_info_parameter(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) - insert_execute_command_mock( - ('borg', 'key', 'change-passphrase', '--log-json', '--info', 'repo') - ) + insert_execute_command_mock(('borg', 'key', 'change-passphrase', '--info', 'repo')) insert_logging_mock(logging.INFO) module.change_passphrase( @@ -164,7 +162,7 @@ def test_change_passphrase_with_log_info_calls_borg_with_info_parameter(): def test_change_passphrase_with_log_debug_calls_borg_with_debug_flags(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) insert_execute_command_mock( - ('borg', 'key', 'change-passphrase', '--log-json', '--debug', '--show-rc', 'repo'), + ('borg', 'key', 'change-passphrase', '--debug', '--show-rc', 'repo'), ) insert_logging_mock(logging.DEBUG) @@ -194,7 +192,7 @@ def test_change_passphrase_with_dry_run_skips_borg_call(): def test_change_passphrase_calls_borg_without_passphrase(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) insert_execute_command_mock( - ('borg', 'key', 'change-passphrase', '--log-json', 'repo'), + ('borg', 'key', 'change-passphrase', 'repo'), config={'option': 'foo'}, ) @@ -215,7 +213,7 @@ def test_change_passphrase_calls_borg_with_working_directory(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) config = {'working_directory': '/working/dir'} insert_execute_command_mock( - ('borg', 'key', 'change-passphrase', '--log-json', 'repo'), + ('borg', 'key', 'change-passphrase', 'repo'), config=config, working_directory='/working/dir', ) From fd373cfefcf4a54d6d6ba0ab59aa359975c42184 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Fri, 9 Jan 2026 11:51:14 -0800 Subject: [PATCH 3/7] Fix for an error in the "key import" action when importing a key from stdin. --- NEWS | 1 + borgmatic/borg/import_key.py | 18 ++++++++-------- tests/unit/borg/test_import_key.py | 33 +++++++++++++++--------------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/NEWS b/NEWS index 10b8e1a4..1cf99426 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,7 @@ https://torsion.org/borgmatic/reference/command-line/logging/#systemd-journal * SECURITY: Prevent shell injection attacks via constant interpolation in command hooks. (This was already implemented for deprecated "before_*"/"after_*" command hooks.) + * Fix for an error in the "key import" action when importing a key from stdin. * Promote the ZFS, LVM, and Btrfs hooks from beta features to stable. 2.0.13 diff --git a/borgmatic/borg/import_key.py b/borgmatic/borg/import_key.py index 1d0945bf..3a9595c7 100644 --- a/borgmatic/borg/import_key.py +++ b/borgmatic/borg/import_key.py @@ -4,7 +4,7 @@ import shlex import borgmatic.config.paths from borgmatic.borg import environment, flags -from borgmatic.execute import DO_NOT_CAPTURE, execute_command +from borgmatic.execute import execute_command logger = logging.getLogger(__name__) @@ -32,13 +32,12 @@ def import_key( working_directory = borgmatic.config.paths.get_working_directory(config) extra_borg_options = config.get('extra_borg_options', {}).get('key_import', '') - if import_arguments.path and import_arguments.path != '-': - if not os.path.exists(os.path.join(working_directory or '', import_arguments.path)): - raise ValueError(f'Path {import_arguments.path} does not exist. Aborting.') - - input_file = None - else: - input_file = DO_NOT_CAPTURE + if ( + import_arguments.path + and import_arguments.path != '-' + and not os.path.exists(os.path.join(working_directory or '', import_arguments.path)) + ): + raise ValueError(f'Path {import_arguments.path} does not exist. Aborting.') full_command = ( (local_path, 'key', 'import') @@ -54,7 +53,7 @@ def import_key( repository_path, local_borg_version, ) - + ((import_arguments.path,) if input_file is None else ()) + + (import_arguments.path or '-',) ) if global_arguments.dry_run: @@ -63,7 +62,6 @@ def import_key( execute_command( full_command, - input_file=input_file, output_log_level=logging.INFO, environment=environment.make_environment(config), working_directory=working_directory, diff --git a/tests/unit/borg/test_import_key.py b/tests/unit/borg/test_import_key.py index d7d11ae7..3bd9fc58 100644 --- a/tests/unit/borg/test_import_key.py +++ b/tests/unit/borg/test_import_key.py @@ -10,7 +10,6 @@ from ..test_verbosity import insert_logging_mock def insert_execute_command_mock( command, - input_file=module.DO_NOT_CAPTURE, working_directory=None, borg_exit_codes=None, ): @@ -20,7 +19,6 @@ def insert_execute_command_mock( ) flexmock(module).should_receive('execute_command').with_args( command, - input_file=input_file, output_log_level=module.logging.INFO, environment=None, working_directory=working_directory, @@ -33,7 +31,7 @@ def test_import_key_calls_borg_with_required_flags(): flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'import', '--log-json', 'repo')) + insert_execute_command_mock(('borg', 'key', 'import', '--log-json', 'repo', '-')) module.import_key( repository_path='repo', @@ -48,7 +46,7 @@ def test_import_key_calls_borg_with_local_path(): flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg1', 'key', 'import', '--log-json', 'repo')) + insert_execute_command_mock(('borg1', 'key', 'import', '--log-json', 'repo', '-')) module.import_key( repository_path='repo', @@ -66,7 +64,7 @@ def test_import_key_calls_borg_using_exit_codes(): flexmock(module.os.path).should_receive('exists').never() borg_exit_codes = flexmock() insert_execute_command_mock( - ('borg', 'key', 'import', '--log-json', 'repo'), borg_exit_codes=borg_exit_codes + ('borg', 'key', 'import', '--log-json', 'repo', '-'), borg_exit_codes=borg_exit_codes ) module.import_key( @@ -83,7 +81,7 @@ def test_import_key_calls_borg_with_remote_path_flags(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() insert_execute_command_mock( - ('borg', 'key', 'import', '--remote-path', 'borg1', '--log-json', 'repo') + ('borg', 'key', 'import', '--remote-path', 'borg1', '--log-json', 'repo', '-') ) module.import_key( @@ -100,7 +98,9 @@ def test_import_key_calls_borg_with_umask_flags(): flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'import', '--umask', '0770', '--log-json', 'repo')) + insert_execute_command_mock( + ('borg', 'key', 'import', '--umask', '0770', '--log-json', 'repo', '-') + ) module.import_key( repository_path='repo', @@ -115,7 +115,9 @@ def test_import_key_calls_borg_with_lock_wait_flags(): flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'import', '--log-json', '--lock-wait', '5', 'repo')) + insert_execute_command_mock( + ('borg', 'key', 'import', '--log-json', '--lock-wait', '5', 'repo', '-') + ) module.import_key( repository_path='repo', @@ -131,7 +133,7 @@ def test_import_key_calls_borg_with_extra_borg_options(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() insert_execute_command_mock( - ('borg', 'key', 'import', '--log-json', '--extra', 'value with space', 'repo') + ('borg', 'key', 'import', '--log-json', '--extra', 'value with space', 'repo', '-') ) module.import_key( @@ -147,7 +149,7 @@ def test_import_key_with_log_info_calls_borg_with_info_parameter(): flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'import', '--log-json', '--info', 'repo')) + insert_execute_command_mock(('borg', 'key', 'import', '--log-json', '--info', 'repo', '-')) insert_logging_mock(logging.INFO) module.import_key( @@ -164,7 +166,7 @@ def test_import_key_with_log_debug_calls_borg_with_debug_flags(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() insert_execute_command_mock( - ('borg', 'key', 'import', '--log-json', '--debug', '--show-rc', 'repo') + ('borg', 'key', 'import', '--log-json', '--debug', '--show-rc', 'repo', '-') ) insert_logging_mock(logging.DEBUG) @@ -181,7 +183,7 @@ def test_import_key_calls_borg_with_paper_flags(): flexmock(module.flags).should_receive('make_flags').and_return(('--paper',)) flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'import', '--log-json', '--paper', 'repo')) + insert_execute_command_mock(('borg', 'key', 'import', '--log-json', '--paper', 'repo', '-')) module.import_key( repository_path='repo', @@ -197,7 +199,7 @@ def test_import_key_calls_borg_with_path_argument(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').with_args('source').and_return(True) insert_execute_command_mock( - ('borg', 'key', 'import', '--log-json', 'repo', 'source'), input_file=None + ('borg', 'key', 'import', '--log-json', 'repo', 'source'), ) module.import_key( @@ -229,7 +231,7 @@ def test_import_key_with_stdin_path_calls_borg_without_path_argument(): flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'import', '--log-json', 'repo')) + insert_execute_command_mock(('borg', 'key', 'import', '--log-json', 'repo', '-')) module.import_key( repository_path='repo', @@ -260,7 +262,7 @@ def test_import_key_calls_borg_with_working_directory(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() insert_execute_command_mock( - ('borg', 'key', 'import', '--log-json', 'repo'), working_directory='/working/dir' + ('borg', 'key', 'import', '--log-json', 'repo', '-'), working_directory='/working/dir' ) module.import_key( @@ -280,7 +282,6 @@ def test_import_key_calls_borg_with_path_argument_and_working_directory(): ).once() insert_execute_command_mock( ('borg', 'key', 'import', '--log-json', 'repo', 'source'), - input_file=None, working_directory='/working/dir', ) From 6f4f1fa16ea15cd21248c56313e6f3d26ed49bef Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Fri, 9 Jan 2026 11:57:27 -0800 Subject: [PATCH 4/7] For the "key export" action, fix use of "--log-json" when exporting to stdout (#485). --- borgmatic/borg/export_key.py | 2 +- tests/unit/borg/test_export_key.py | 38 +++++++++++------------------- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/borgmatic/borg/export_key.py b/borgmatic/borg/export_key.py index f818b016..ee210718 100644 --- a/borgmatic/borg/export_key.py +++ b/borgmatic/borg/export_key.py @@ -48,7 +48,7 @@ def export_key( (local_path, 'key', 'export') + (('--remote-path', remote_path) if remote_path else ()) + (('--umask', str(umask)) if umask else ()) - + ('--log-json',) + + (('--log-json',) if output_file is None else ()) + (('--lock-wait', str(lock_wait)) if lock_wait else ()) + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) + (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ()) diff --git a/tests/unit/borg/test_export_key.py b/tests/unit/borg/test_export_key.py index df0d81e7..9995840f 100644 --- a/tests/unit/borg/test_export_key.py +++ b/tests/unit/borg/test_export_key.py @@ -35,7 +35,7 @@ def insert_execute_command_mock( def test_export_key_calls_borg_with_required_flags(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'export', '--log-json', 'repo')) + insert_execute_command_mock(('borg', 'key', 'export', 'repo')) module.export_key( repository_path='repo', @@ -49,7 +49,7 @@ def test_export_key_calls_borg_with_required_flags(): def test_export_key_calls_borg_with_local_path(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg1', 'key', 'export', '--log-json', 'repo')) + insert_execute_command_mock(('borg1', 'key', 'export', 'repo')) module.export_key( repository_path='repo', @@ -65,9 +65,7 @@ def test_export_key_calls_borg_using_exit_codes(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() borg_exit_codes = flexmock() - insert_execute_command_mock( - ('borg', 'key', 'export', '--log-json', 'repo'), borg_exit_codes=borg_exit_codes - ) + insert_execute_command_mock(('borg', 'key', 'export', 'repo'), borg_exit_codes=borg_exit_codes) module.export_key( repository_path='repo', @@ -81,9 +79,7 @@ def test_export_key_calls_borg_using_exit_codes(): def test_export_key_calls_borg_with_remote_path_flags(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock( - ('borg', 'key', 'export', '--remote-path', 'borg1', '--log-json', 'repo') - ) + insert_execute_command_mock(('borg', 'key', 'export', '--remote-path', 'borg1', 'repo')) module.export_key( repository_path='repo', @@ -98,7 +94,7 @@ def test_export_key_calls_borg_with_remote_path_flags(): def test_export_key_calls_borg_with_umask_flags(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'export', '--umask', '0770', '--log-json', 'repo')) + insert_execute_command_mock(('borg', 'key', 'export', '--umask', '0770', 'repo')) module.export_key( repository_path='repo', @@ -112,7 +108,7 @@ def test_export_key_calls_borg_with_umask_flags(): def test_export_key_calls_borg_with_lock_wait_flags(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'export', '--log-json', '--lock-wait', '5', 'repo')) + insert_execute_command_mock(('borg', 'key', 'export', '--lock-wait', '5', 'repo')) module.export_key( repository_path='repo', @@ -126,9 +122,7 @@ def test_export_key_calls_borg_with_lock_wait_flags(): def test_export_key_calls_borg_with_extra_borg_options(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock( - ('borg', 'key', 'export', '--log-json', '--extra', 'value with space', 'repo') - ) + insert_execute_command_mock(('borg', 'key', 'export', '--extra', 'value with space', 'repo')) module.export_key( repository_path='repo', @@ -142,7 +136,7 @@ def test_export_key_calls_borg_with_extra_borg_options(): def test_export_key_with_log_info_calls_borg_with_info_parameter(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'export', '--log-json', '--info', 'repo')) + insert_execute_command_mock(('borg', 'key', 'export', '--info', 'repo')) insert_logging_mock(logging.INFO) module.export_key( @@ -157,9 +151,7 @@ def test_export_key_with_log_info_calls_borg_with_info_parameter(): def test_export_key_with_log_debug_calls_borg_with_debug_flags(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock( - ('borg', 'key', 'export', '--log-json', '--debug', '--show-rc', 'repo') - ) + insert_execute_command_mock(('borg', 'key', 'export', '--debug', '--show-rc', 'repo')) insert_logging_mock(logging.DEBUG) module.export_key( @@ -174,7 +166,7 @@ def test_export_key_with_log_debug_calls_borg_with_debug_flags(): def test_export_key_calls_borg_with_paper_flags(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'export', '--log-json', '--paper', 'repo')) + insert_execute_command_mock(('borg', 'key', 'export', '--paper', 'repo')) module.export_key( repository_path='repo', @@ -188,7 +180,7 @@ def test_export_key_calls_borg_with_paper_flags(): def test_export_key_calls_borg_with_paper_flag(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'export', '--log-json', '--paper', 'repo')) + insert_execute_command_mock(('borg', 'key', 'export', '--paper', 'repo')) module.export_key( repository_path='repo', @@ -202,7 +194,7 @@ def test_export_key_calls_borg_with_paper_flag(): def test_export_key_calls_borg_with_qr_html_flag(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'export', '--log-json', '--qr-html', 'repo')) + insert_execute_command_mock(('borg', 'key', 'export', '--qr-html', 'repo')) module.export_key( repository_path='repo', @@ -247,7 +239,7 @@ def test_export_key_with_already_existent_path_raises(): def test_export_key_with_stdout_path_calls_borg_without_path_argument(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock(('borg', 'key', 'export', '--log-json', 'repo')) + insert_execute_command_mock(('borg', 'key', 'export', 'repo')) module.export_key( repository_path='repo', @@ -275,9 +267,7 @@ def test_export_key_with_dry_run_skips_borg_call(): def test_export_key_calls_borg_with_working_directory(): flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) flexmock(module.os.path).should_receive('exists').never() - insert_execute_command_mock( - ('borg', 'key', 'export', '--log-json', 'repo'), working_directory='/working/dir' - ) + insert_execute_command_mock(('borg', 'key', 'export', 'repo'), working_directory='/working/dir') module.export_key( repository_path='repo', From 528dc6fe878a419e73550b2b40a15eb437f19956 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Fri, 9 Jan 2026 12:01:21 -0800 Subject: [PATCH 5/7] For the "mount" action, fix use of "--log-json" when "--foreground" is set (#485). --- borgmatic/borg/mount.py | 2 +- tests/unit/borg/test_mount.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/borgmatic/borg/mount.py b/borgmatic/borg/mount.py index 041c1e72..9561bbe1 100644 --- a/borgmatic/borg/mount.py +++ b/borgmatic/borg/mount.py @@ -32,7 +32,7 @@ def mount_archive( (local_path, 'mount') + (('--remote-path', remote_path) if remote_path else ()) + (('--umask', str(umask)) if umask else ()) - + ('--log-json',) + + (('--log-json',) if not mount_arguments.foreground else ()) + (('--lock-wait', str(lock_wait)) if lock_wait else ()) + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) + (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ()) diff --git a/tests/unit/borg/test_mount.py b/tests/unit/borg/test_mount.py index 00fed9b6..4d3fe57a 100644 --- a/tests/unit/borg/test_mount.py +++ b/tests/unit/borg/test_mount.py @@ -273,7 +273,7 @@ def test_mount_archive_calls_borg_with_foreground_parameter(): flexmock(module.environment).should_receive('make_environment') flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None) flexmock(module).should_receive('execute_command').with_args( - ('borg', 'mount', '--log-json', '--foreground', 'repo::archive', '/mnt'), + ('borg', 'mount', '--foreground', 'repo::archive', '/mnt'), output_file=module.DO_NOT_CAPTURE, environment=None, working_directory=None, From 4e6319f5d3b0b7c69a84294cbc0904404ab10e40 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Fri, 9 Jan 2026 12:12:42 -0800 Subject: [PATCH 6/7] In the "export-tar" action, fix the use of "--log-json" when exporting to stdout (#485). --- borgmatic/borg/export_tar.py | 2 +- tests/unit/borg/test_export_tar.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/borgmatic/borg/export_tar.py b/borgmatic/borg/export_tar.py index 51a1d0f2..5028ce8e 100644 --- a/borgmatic/borg/export_tar.py +++ b/borgmatic/borg/export_tar.py @@ -41,7 +41,7 @@ def export_tar_archive( (local_path, 'export-tar') + (('--remote-path', remote_path) if remote_path else ()) + (('--umask', str(umask)) if umask else ()) - + ('--log-json',) + + (('--log-json',) if destination_path != '-' else ()) + (('--lock-wait', str(lock_wait)) if lock_wait else ()) + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) + (('--list',) if config.get('list_details') else ()) diff --git a/tests/unit/borg/test_export_tar.py b/tests/unit/borg/test_export_tar.py index e2c7f84b..58573789 100644 --- a/tests/unit/borg/test_export_tar.py +++ b/tests/unit/borg/test_export_tar.py @@ -368,9 +368,7 @@ def test_export_tar_archive_calls_borg_with_stdout_destination_path(): flexmock(module.flags).should_receive('make_repository_archive_flags').and_return( ('repo::archive',), ) - insert_execute_command_mock( - ('borg', 'export-tar', '--log-json', 'repo::archive', '-'), capture=False - ) + insert_execute_command_mock(('borg', 'export-tar', 'repo::archive', '-'), capture=False) module.export_tar_archive( dry_run=False, From bb149e4ee85bc1735acf34478321b5371dde2d33 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Fri, 9 Jan 2026 12:14:31 -0800 Subject: [PATCH 7/7] Upgrade urllib3 in test requirements for security fix. --- test_requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requirements.txt b/test_requirements.txt index 5b5f2d66..3dbf9d07 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -24,4 +24,4 @@ requests-oauthlib==2.0.0 rpds-py==0.28.0 ruamel-yaml>0.15.0 typing-extensions==4.15.0 -urllib3==2.6.1 +urllib3==2.6.3