From cb2fd7c5e842d7c7b5801b13bce18b2112dc3a1e Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Wed, 17 Apr 2024 16:50:09 -0700 Subject: [PATCH] Fix lack of file extraction when using "extract --strip-components all" on a path with a leading slash (#851). --- NEWS | 4 ++++ borgmatic/borg/extract.py | 9 +++++++-- setup.py | 2 +- tests/unit/borg/test_extract.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index c883ecb..5459f1c 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,7 @@ +1.8.11.dev0 + * #851: Fix lack of file extraction when using "extract --strip-components all" on a path with a + leading slash. + 1.8.10 * #656 (beta): Add a "spot" consistency check that compares file counts and contents between your source files and the latest archive, ensuring they fall within configured tolerances. This can diff --git a/borgmatic/borg/extract.py b/borgmatic/borg/extract.py index 966e297..e50a487 100644 --- a/borgmatic/borg/extract.py +++ b/borgmatic/borg/extract.py @@ -104,8 +104,13 @@ def extract_archive( if not paths: raise ValueError('The --strip-components flag with "all" requires at least one --path') - # Calculate the maximum number of leading path components of the given paths. - strip_components = max(0, *(len(path.split(os.path.sep)) - 1 for path in paths)) + # Calculate the maximum number of leading path components of the given paths. "if piece" + # ignores empty path components, e.g. those resulting from a leading slash. And the "- 1" + # is so this doesn't count the final path component, e.g. the filename itself. + strip_components = max( + 0, + *(len(tuple(piece for piece in path.split(os.path.sep) if piece)) - 1 for path in paths) + ) full_command = ( (local_path, 'extract') diff --git a/setup.py b/setup.py index a896074..2985f06 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -VERSION = '1.8.10' +VERSION = '1.8.11.dev0' setup( diff --git a/tests/unit/borg/test_extract.py b/tests/unit/borg/test_extract.py index 1914632..d5c660b 100644 --- a/tests/unit/borg/test_extract.py +++ b/tests/unit/borg/test_extract.py @@ -507,6 +507,39 @@ def test_extract_archive_calls_borg_with_strip_components_calculated_from_all(): ) +def test_extract_archive_calls_borg_with_strip_components_calculated_from_all_with_leading_slash(): + flexmock(module.os.path).should_receive('abspath').and_return('repo') + insert_execute_command_mock( + ( + 'borg', + 'extract', + '--strip-components', + '2', + 'repo::archive', + '/foo/bar/baz.txt', + '/foo/bar.txt', + ) + ) + flexmock(module.feature).should_receive('available').and_return(True) + flexmock(module.flags).should_receive('make_repository_archive_flags').and_return( + ('repo::archive',) + ) + flexmock(module.borgmatic.config.validate).should_receive( + 'normalize_repository_path' + ).and_return('repo') + + module.extract_archive( + dry_run=False, + repository='repo', + archive='archive', + paths=['/foo/bar/baz.txt', '/foo/bar.txt'], + config={}, + local_borg_version='1.2.3', + global_arguments=flexmock(log_json=False), + strip_components='all', + ) + + def test_extract_archive_with_strip_components_all_and_no_paths_raises(): flexmock(module.os.path).should_receive('abspath').and_return('repo') flexmock(module.feature).should_receive('available').and_return(True)