diff --git a/NEWS b/NEWS index 84e656e3..7274ebde 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ 1.9.6.dev0 * #959: Fix an error in the Btrfs hook when a "/" subvolume is configured in borgmatic's source directories. + * #960: Fix for archives storing relative source directory paths such that they contain the working + directory. + * Drop support for Python 3.8, which has been end-of-lifed. 1.9.5 * #418: Backup and restore databases that have the same name but with different ports, hostnames, diff --git a/borgmatic/actions/create.py b/borgmatic/actions/create.py index ef455cc4..b74e8cfb 100644 --- a/borgmatic/actions/create.py +++ b/borgmatic/actions/create.py @@ -18,10 +18,26 @@ def expand_directory(directory, working_directory): ''' Given a directory path, expand any tilde (representing a user's home directory) and any globs therein. Return a list of one or more resulting paths. - ''' - expanded_directory = os.path.join(working_directory or '', os.path.expanduser(directory)) - return glob.glob(expanded_directory) or [expanded_directory] + Take into account the given working directory so that relative paths are supported. + ''' + expanded_directory = os.path.expanduser(directory) + + # This would be a lot easier to do with glob(..., root_dir=working_directory), but root_dir is + # only available in Python 3.10+. + glob_paths = glob.glob(os.path.join(working_directory or '', os.path.expanduser(directory))) + + if not glob_paths: + return [expanded_directory] + + working_directory_prefix = os.path.join(working_directory or '', '') + + # Remove the working directory prefix that we added above in order to make glob() work. + return [ + # os.path.relpath() won't work here because it collapses any usage of Borg's slashdot hack. + glob_path.removeprefix(working_directory_prefix) + for glob_path in glob_paths + ] def expand_directories(directories, working_directory=None): diff --git a/pyproject.toml b/pyproject.toml index 56713835..5896fef9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = [ ] description = "Simple, configuration-driven backup software for servers and workstations" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", diff --git a/tests/unit/actions/test_create.py b/tests/unit/actions/test_create.py index 5e164bb7..47a09e9c 100644 --- a/tests/unit/actions/test_create.py +++ b/tests/unit/actions/test_create.py @@ -22,16 +22,16 @@ def test_expand_directory_with_glob_expands(): assert paths == ['foo', 'food'] -def test_expand_directory_with_working_directory_passes_it_through(): +def test_expand_directory_strips_off_working_directory(): flexmock(module.os.path).should_receive('expanduser').and_return('foo') flexmock(module.glob).should_receive('glob').with_args('/working/dir/foo').and_return([]).once() paths = module.expand_directory('foo', working_directory='/working/dir') - assert paths == ['/working/dir/foo'] + assert paths == ['foo'] -def test_expand_directory_with_glob_passes_through_working_directory(): +def test_expand_directory_globs_working_directory_and_strips_it_off(): flexmock(module.os.path).should_receive('expanduser').and_return('foo*') flexmock(module.glob).should_receive('glob').with_args('/working/dir/foo*').and_return( ['/working/dir/foo', '/working/dir/food'] @@ -39,7 +39,18 @@ def test_expand_directory_with_glob_passes_through_working_directory(): paths = module.expand_directory('foo*', working_directory='/working/dir') - assert paths == ['/working/dir/foo', '/working/dir/food'] + assert paths == ['foo', 'food'] + + +def test_expand_directory_with_slashdot_hack_globs_working_directory_and_strips_it_off(): + flexmock(module.os.path).should_receive('expanduser').and_return('./foo*') + flexmock(module.glob).should_receive('glob').with_args('/working/dir/./foo*').and_return( + ['/working/dir/./foo', '/working/dir/./food'] + ).once() + + paths = module.expand_directory('./foo*', working_directory='/working/dir') + + assert paths == ['./foo', './food'] def test_expand_directories_flattens_expanded_directories(): diff --git a/tox.ini b/tox.ini index 087fdfaf..b2f0d484 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -env_list = py38,py39,py310,py311,py312 +env_list = py39,py310,py311,py312 skip_missing_interpreters = True package = editable min_version = 4.0