Fix "borgmatic create --progress" output so that it updates on the console in real-time (#221).
This commit is contained in:
parent
a472735616
commit
a897ffd514
10 changed files with 90 additions and 24 deletions
3
NEWS
3
NEWS
|
@ -1,6 +1,7 @@
|
||||||
1.3.20.dev0
|
1.3.20
|
||||||
* #205: More robust sample systemd service: boot delay, network dependency, lowered CPU/IO
|
* #205: More robust sample systemd service: boot delay, network dependency, lowered CPU/IO
|
||||||
priority, etc.
|
priority, etc.
|
||||||
|
* #221: Fix "borgmatic create --progress" output so that it updates on the console in real-time.
|
||||||
|
|
||||||
1.3.19
|
1.3.19
|
||||||
* #219: Fix visibility of "borgmatic prune --stats" output.
|
* #219: Fix visibility of "borgmatic prune --stats" output.
|
||||||
|
|
|
@ -4,7 +4,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from borgmatic.execute import execute_command
|
from borgmatic.execute import execute_command, execute_command_without_capture
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -163,6 +163,12 @@ def create_archive(
|
||||||
+ sources
|
+ sources
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# The progress output isn't compatible with captured and logged output, as progress messes with
|
||||||
|
# the terminal directly.
|
||||||
|
if progress:
|
||||||
|
execute_command_without_capture(full_command)
|
||||||
|
return
|
||||||
|
|
||||||
if json:
|
if json:
|
||||||
output_log_level = None
|
output_log_level = None
|
||||||
elif stats:
|
elif stats:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from borgmatic.execute import execute_command
|
from borgmatic.execute import execute_command, execute_command_without_capture
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -83,4 +83,10 @@ def extract_archive(
|
||||||
+ (tuple(restore_paths) if restore_paths else ())
|
+ (tuple(restore_paths) if restore_paths else ())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# The progress output isn't compatible with captured and logged output, as progress messes with
|
||||||
|
# the terminal directly.
|
||||||
|
if progress:
|
||||||
|
execute_command_without_capture(full_command)
|
||||||
|
return
|
||||||
|
|
||||||
execute_command(full_command)
|
execute_command(full_command)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from borgmatic.execute import BORG_ERROR_EXIT_CODE, execute_command
|
from borgmatic.execute import execute_command, execute_command_without_capture
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -45,8 +45,4 @@ def initialize_repository(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Don't use execute_command() here because it doesn't support interactive prompts.
|
# Don't use execute_command() here because it doesn't support interactive prompts.
|
||||||
try:
|
execute_command_without_capture(init_command)
|
||||||
subprocess.check_call(init_command)
|
|
||||||
except subprocess.CalledProcessError as error:
|
|
||||||
if error.returncode >= BORG_ERROR_EXIT_CODE:
|
|
||||||
raise
|
|
||||||
|
|
|
@ -61,3 +61,18 @@ def execute_command(full_command, output_log_level=logging.INFO, shell=False):
|
||||||
return output.decode() if output is not None else None
|
return output.decode() if output is not None else None
|
||||||
else:
|
else:
|
||||||
execute_and_log_output(full_command, output_log_level, shell=shell)
|
execute_and_log_output(full_command, output_log_level, shell=shell)
|
||||||
|
|
||||||
|
|
||||||
|
def execute_command_without_capture(full_command):
|
||||||
|
'''
|
||||||
|
Execute the given command (a sequence of command/argument strings), but don't capture or log its
|
||||||
|
output in any way. This is necessary for commands that monkey with the terminal (e.g. progress
|
||||||
|
display) or provide interactive prompts.
|
||||||
|
'''
|
||||||
|
logger.debug(' '.join(full_command))
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.check_call(full_command)
|
||||||
|
except subprocess.CalledProcessError as error:
|
||||||
|
if error.returncode >= BORG_ERROR_EXIT_CODE:
|
||||||
|
raise
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -1,6 +1,6 @@
|
||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
VERSION = '1.3.20.dev0'
|
VERSION = '1.3.20'
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
|
|
@ -758,6 +758,29 @@ def test_create_archive_with_stats_calls_borg_with_stats_parameter():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_archive_with_progress_calls_borg_with_progress_parameter():
|
||||||
|
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||||
|
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||||
|
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||||
|
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||||
|
flexmock(module).should_receive('_make_exclude_flags').and_return(())
|
||||||
|
flexmock(module).should_receive('execute_command_without_capture').with_args(
|
||||||
|
('borg', 'create', '--progress') + ARCHIVE_WITH_PATHS
|
||||||
|
)
|
||||||
|
|
||||||
|
module.create_archive(
|
||||||
|
dry_run=False,
|
||||||
|
repository='repo',
|
||||||
|
location_config={
|
||||||
|
'source_directories': ['foo', 'bar'],
|
||||||
|
'repositories': ['repo'],
|
||||||
|
'exclude_patterns': None,
|
||||||
|
},
|
||||||
|
storage_config={},
|
||||||
|
progress=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_create_archive_with_json_calls_borg_with_json_parameter():
|
def test_create_archive_with_json_calls_borg_with_json_parameter():
|
||||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||||
|
|
|
@ -195,7 +195,9 @@ def test_extract_archive_calls_borg_with_dry_run_parameter():
|
||||||
|
|
||||||
|
|
||||||
def test_extract_archive_calls_borg_with_progress_parameter():
|
def test_extract_archive_calls_borg_with_progress_parameter():
|
||||||
insert_execute_command_mock(('borg', 'extract', '--progress', 'repo::archive'))
|
flexmock(module).should_receive('execute_command_without_capture').with_args(
|
||||||
|
('borg', 'extract', '--progress', 'repo::archive')
|
||||||
|
).once()
|
||||||
|
|
||||||
module.extract_archive(
|
module.extract_archive(
|
||||||
dry_run=False,
|
dry_run=False,
|
||||||
|
|
|
@ -23,8 +23,8 @@ def insert_info_command_not_found_mock():
|
||||||
|
|
||||||
|
|
||||||
def insert_init_command_mock(init_command, **kwargs):
|
def insert_init_command_mock(init_command, **kwargs):
|
||||||
flexmock(module.subprocess).should_receive('check_call').with_args(
|
flexmock(module).should_receive('execute_command_without_capture').with_args(
|
||||||
init_command, **kwargs
|
init_command
|
||||||
).once()
|
).once()
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,18 +35,9 @@ def test_initialize_repository_calls_borg_with_parameters():
|
||||||
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
||||||
|
|
||||||
|
|
||||||
def test_initialize_repository_does_not_raise_for_borg_init_warning():
|
|
||||||
insert_info_command_not_found_mock()
|
|
||||||
flexmock(module.subprocess).should_receive('check_call').and_raise(
|
|
||||||
module.subprocess.CalledProcessError(1, 'borg init')
|
|
||||||
)
|
|
||||||
|
|
||||||
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
|
||||||
|
|
||||||
|
|
||||||
def test_initialize_repository_raises_for_borg_init_error():
|
def test_initialize_repository_raises_for_borg_init_error():
|
||||||
insert_info_command_not_found_mock()
|
insert_info_command_not_found_mock()
|
||||||
flexmock(module.subprocess).should_receive('check_call').and_raise(
|
flexmock(module).should_receive('execute_command_without_capture').and_raise(
|
||||||
module.subprocess.CalledProcessError(2, 'borg init')
|
module.subprocess.CalledProcessError(2, 'borg init')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -56,7 +47,7 @@ def test_initialize_repository_raises_for_borg_init_error():
|
||||||
|
|
||||||
def test_initialize_repository_skips_initialization_when_repository_already_exists():
|
def test_initialize_repository_skips_initialization_when_repository_already_exists():
|
||||||
insert_info_command_found_mock()
|
insert_info_command_found_mock()
|
||||||
flexmock(module.subprocess).should_receive('check_call').never()
|
flexmock(module).should_receive('execute_command_without_capture').never()
|
||||||
|
|
||||||
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import pytest
|
||||||
from flexmock import flexmock
|
from flexmock import flexmock
|
||||||
|
|
||||||
from borgmatic import execute as module
|
from borgmatic import execute as module
|
||||||
|
@ -49,3 +50,28 @@ def test_execute_command_captures_output_with_shell():
|
||||||
output = module.execute_command(full_command, output_log_level=None, shell=True)
|
output = module.execute_command(full_command, output_log_level=None, shell=True)
|
||||||
|
|
||||||
assert output == expected_output
|
assert output == expected_output
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_command_without_capture_does_not_raise_on_success():
|
||||||
|
flexmock(module.subprocess).should_receive('check_call').and_raise(
|
||||||
|
module.subprocess.CalledProcessError(0, 'borg init')
|
||||||
|
)
|
||||||
|
|
||||||
|
module.execute_command_without_capture(('borg', 'init'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_command_without_capture_does_not_raise_on_warning():
|
||||||
|
flexmock(module.subprocess).should_receive('check_call').and_raise(
|
||||||
|
module.subprocess.CalledProcessError(1, 'borg init')
|
||||||
|
)
|
||||||
|
|
||||||
|
module.execute_command_without_capture(('borg', 'init'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_command_without_capture_raises_on_error():
|
||||||
|
flexmock(module.subprocess).should_receive('check_call').and_raise(
|
||||||
|
module.subprocess.CalledProcessError(2, 'borg init')
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(module.subprocess.CalledProcessError):
|
||||||
|
module.execute_command_without_capture(('borg', 'init'))
|
||||||
|
|
Loading…
Reference in a new issue