Fix for archives storing relative source directory paths such that they contain the working directory (#960).
All checks were successful
build / test (push) Successful in 5m53s
build / docs (push) Successful in 2m1s

This commit is contained in:
Dan Helfman 2024-12-29 20:09:13 -08:00
parent a6b6dd32c1
commit a4954cc7a3
5 changed files with 39 additions and 9 deletions

3
NEWS
View File

@ -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,

View File

@ -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):

View File

@ -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",

View File

@ -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():

View File

@ -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