Browse Source

Fix for archives storing relative source directory paths such that they contain the working directory (#960).

Dan Helfman 5 months ago
parent
commit
a4954cc7a3
5 changed files with 38 additions and 8 deletions
  1. 3 0
      NEWS
  2. 18 2
      borgmatic/actions/create.py
  3. 1 1
      pyproject.toml
  4. 15 4
      tests/unit/actions/test_create.py
  5. 1 1
      tox.ini

+ 3 - 0
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,

+ 18 - 2
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.
+
+    Take into account the given working directory so that relative paths are supported.
     '''
-    expanded_directory = os.path.join(working_directory or '', os.path.expanduser(directory))
+    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]
 
-    return glob.glob(expanded_directory) or [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):

+ 1 - 1
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",

+ 15 - 4
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():

+ 1 - 1
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