From 6bf6ac310b27bc84c9fae41a59bf83383aa126e1 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Wed, 9 Feb 2022 21:11:00 -0800 Subject: [PATCH] When using the "bsd_flags" option, tailor the flags passed to Borg depending on the Borg version (#394). --- NEWS | 3 +- borgmatic/borg/create.py | 10 ++++-- borgmatic/borg/feature.py | 6 ++-- borgmatic/config/schema.yaml | 8 ++--- scripts/find-unsupported-borg-options | 2 +- tests/unit/borg/test_create.py | 52 ++++++++++++++++++++++++--- 6 files changed, 65 insertions(+), 16 deletions(-) diff --git a/NEWS b/NEWS index 20ebd1bac..ecd7361f3 100644 --- a/NEWS +++ b/NEWS @@ -2,7 +2,8 @@ * #394: Compact repository segments and free space with new "borgmatic compact" action. Borg 1.2+ only. Also run "compact" by default when no actions are specified, as "prune" in Borg 1.2 no longer frees up space unless "compact" is run. - * #394: When the "atime" option is used, tailor the flags passed to Borg depending on version. + * #394: When using the "atime" or "bsd_flags" options, tailor the flags passed to Borg depending on + the Borg version. * #480, #482: Fix traceback when a YAML validation error occurs. 1.5.22 diff --git a/borgmatic/borg/create.py b/borgmatic/borg/create.py index 060c8503b..675a0671f 100644 --- a/borgmatic/borg/create.py +++ b/borgmatic/borg/create.py @@ -227,12 +227,16 @@ def create_archive( archive_name_format = storage_config.get('archive_name_format', DEFAULT_ARCHIVE_NAME_FORMAT) extra_borg_options = storage_config.get('extra_borg_options', {}).get('create', '') - atime_feature_available = feature.available(feature.Feature.ATIME, local_borg_version) - if atime_feature_available: + if feature.available(feature.Feature.ATIME, local_borg_version): atime_flags = ('--atime',) if location_config.get('atime') is True else () else: atime_flags = ('--noatime',) if location_config.get('atime') is False else () + if feature.available(feature.Feature.NOFLAGS, local_borg_version): + noflags_flags = ('--noflags',) if location_config.get('bsd_flags') is False else () + else: + noflags_flags = ('--nobsdflags',) if location_config.get('bsd_flags') is False else () + full_command = ( tuple(local_path.split(' ')) + ('create',) @@ -252,7 +256,7 @@ def create_archive( + (('--noctime',) if location_config.get('ctime') is False else ()) + (('--nobirthtime',) if location_config.get('birthtime') is False else ()) + (('--read-special',) if (location_config.get('read_special') or stream_processes) else ()) - + (('--nobsdflags',) if location_config.get('bsd_flags') is False else ()) + + noflags_flags + (('--files-cache', files_cache) if files_cache else ()) + (('--remote-path', remote_path) if remote_path else ()) + (('--umask', str(umask)) if umask else ()) diff --git a/borgmatic/borg/feature.py b/borgmatic/borg/feature.py index 125a19090..6849e1528 100644 --- a/borgmatic/borg/feature.py +++ b/borgmatic/borg/feature.py @@ -6,11 +6,13 @@ from pkg_resources import parse_version class Feature(Enum): COMPACT = 1 ATIME = 2 + NOFLAGS = 3 FEATURE_TO_MINIMUM_BORG_VERSION = { - Feature.COMPACT: parse_version('1.2.0a2'), - Feature.ATIME: parse_version('1.2.0a7'), + Feature.COMPACT: parse_version('1.2.0a2'), # borg compact + Feature.ATIME: parse_version('1.2.0a7'), # borg create --atime + Feature.NOFLAGS: parse_version('1.2.0a8'), # borg create --noflags } diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index d0c735993..c0999f1ef 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -111,10 +111,10 @@ properties: type: string description: | Any paths matching these patterns are included/excluded from - backups. Globs are expanded. (Tildes are not.) Note that - Borg considers this option experimental. See the output of - "borg help patterns" for more details. Quote any value if it - contains leading punctuation, so it parses correctly. + backups. Globs are expanded. (Tildes are not.) See the + output of "borg help patterns" for more details. Quote any + value if it contains leading punctuation, so it parses + correctly. example: - 'R /' - '- /home/*/.cache' diff --git a/scripts/find-unsupported-borg-options b/scripts/find-unsupported-borg-options index f0ba00ba8..9221e72da 100755 --- a/scripts/find-unsupported-borg-options +++ b/scripts/find-unsupported-borg-options @@ -38,7 +38,7 @@ for sub_command in prune create check list info; do | grep -v '^--json$' \ | grep -v '^--keep-last$' \ | grep -v '^--list$' \ - | grep -v '^--nobsdflags$' \ + | grep -v '^--bsdflags$' \ | grep -v '^--pattern$' \ | grep -v '^--progress$' \ | grep -v '^--stats$' \ diff --git a/tests/unit/borg/test_create.py b/tests/unit/borg/test_create.py index b3c5eb17d..e3895377b 100644 --- a/tests/unit/borg/test_create.py +++ b/tests/unit/borg/test_create.py @@ -768,11 +768,9 @@ def test_create_archive_with_read_special_calls_borg_with_read_special_parameter ('ctime', False), ('birthtime', True), ('birthtime', False), - ('bsd_flags', True), - ('bsd_flags', False), ), ) -def test_create_archive_with_option_calls_borg_without_corresponding_parameter( +def test_create_archive_with_basic_option_calls_borg_with_corresponding_parameter( option_name, option_value ): option_flag = '--no' + option_name.replace('_', '') if option_value is False else None @@ -815,7 +813,7 @@ def test_create_archive_with_option_calls_borg_without_corresponding_parameter( (False, False, '--noatime'), ), ) -def test_create_archive_with_atime_option_calls_borg_without_corresponding_parameter( +def test_create_archive_with_atime_option_calls_borg_with_corresponding_parameter( option_value, feature_available, option_flag ): flexmock(module).should_receive('borgmatic_source_directories').and_return([]) @@ -824,7 +822,8 @@ def test_create_archive_with_atime_option_calls_borg_without_corresponding_param flexmock(module).should_receive('_expand_directories').and_return(()) flexmock(module).should_receive('_expand_home_directories').and_return(()) flexmock(module).should_receive('_write_pattern_file').and_return(None) - flexmock(module.feature).should_receive('available').and_return(feature_available) + flexmock(module.feature).should_receive('available').with_args(module.feature.Feature.ATIME, '1.2.3').and_return(feature_available) + flexmock(module.feature).should_receive('available').with_args(module.feature.Feature.NOFLAGS, '1.2.3').and_return(True) flexmock(module).should_receive('_make_pattern_flags').and_return(()) flexmock(module).should_receive('_make_exclude_flags').and_return(()) flexmock(module).should_receive('execute_command').with_args( @@ -848,6 +847,49 @@ def test_create_archive_with_atime_option_calls_borg_without_corresponding_param ) +@pytest.mark.parametrize( + 'option_value,feature_available,option_flag', + ( + (True, True, None), + (True, False, None), + (False, True, '--noflags'), + (False, False, '--nobsdflags'), + ), +) +def test_create_archive_with_bsd_flags_option_calls_borg_with_corresponding_parameter( + option_value, feature_available, option_flag +): + flexmock(module).should_receive('borgmatic_source_directories').and_return([]) + flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar')) + flexmock(module).should_receive('map_directories_to_devices').and_return({}) + flexmock(module).should_receive('_expand_directories').and_return(()) + flexmock(module).should_receive('_expand_home_directories').and_return(()) + flexmock(module).should_receive('_write_pattern_file').and_return(None) + flexmock(module.feature).should_receive('available').with_args(module.feature.Feature.ATIME, '1.2.3').and_return(True) + flexmock(module.feature).should_receive('available').with_args(module.feature.Feature.NOFLAGS, '1.2.3').and_return(feature_available) + flexmock(module).should_receive('_make_pattern_flags').and_return(()) + flexmock(module).should_receive('_make_exclude_flags').and_return(()) + flexmock(module).should_receive('execute_command').with_args( + ('borg', 'create') + ((option_flag,) if option_flag else ()) + ARCHIVE_WITH_PATHS, + output_log_level=logging.INFO, + output_file=None, + borg_local_path='borg', + ) + + module.create_archive( + dry_run=False, + repository='repo', + location_config={ + 'source_directories': ['foo', 'bar'], + 'repositories': ['repo'], + 'bsd_flags': option_value, + 'exclude_patterns': None, + }, + storage_config={}, + local_borg_version='1.2.3', + ) + + def test_create_archive_with_files_cache_calls_borg_with_files_cache_parameters(): flexmock(module).should_receive('borgmatic_source_directories').and_return([]) flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))