2
0
Эх сурвалжийг харах

Fix hang when database hooks are enabled and "patterns" contains a parent directory of "~/.borgmatic" (#582).

Dan Helfman 2 жил өмнө
parent
commit
679bb839d7

+ 2 - 0
NEWS

@@ -1,6 +1,8 @@
 1.7.2.dev0
  * #577: Fix regression in which "borgmatic info --archive ..." showed repository info instead of
    archive info with Borg 1.
+ * #582: Fix hang when database hooks are enabled and "patterns" contains a parent directory of
+   "~/.borgmatic".
 
 1.7.1
  * #542: Make the "source_directories" option optional. This is useful for "check"-only setups or

+ 33 - 7
borgmatic/borg/create.py

@@ -59,7 +59,7 @@ def map_directories_to_devices(directories):
     }
 
 
-def deduplicate_directories(directory_devices):
+def deduplicate_directories(directory_devices, additional_directory_devices):
     '''
     Given a map from directory to the identifier for the device on which that directory resides,
     return the directories as a sorted tuple with all duplicate child directories removed. For
@@ -74,22 +74,28 @@ def deduplicate_directories(directory_devices):
     there are cases where Borg coming across the same file twice will result in duplicate reads and
     even hangs, e.g. when a database hook is using a named pipe for streaming database dumps to
     Borg.
+
+    If any additional directory devices are given, also deduplicate against them, but don't include
+    them in the returned directories.
     '''
     deduplicated = set()
     directories = sorted(directory_devices.keys())
+    additional_directories = sorted(additional_directory_devices.keys())
+    all_devices = {**directory_devices, **additional_directory_devices}
 
     for directory in directories:
         deduplicated.add(directory)
         parents = pathlib.PurePath(directory).parents
 
-        # If another directory in the given list is a parent of current directory (even n levels
-        # up) and both are on the same filesystem, then the current directory is a duplicate.
-        for other_directory in directories:
+        # If another directory in the given list (or the additional list) is a parent of current
+        # directory (even n levels up) and both are on the same filesystem, then the current
+        # directory is a duplicate.
+        for other_directory in directories + additional_directories:
             for parent in parents:
                 if (
                     pathlib.PurePath(other_directory) == parent
-                    and directory_devices[directory] is not None
-                    and directory_devices[other_directory] == directory_devices[directory]
+                    and all_devices[directory] is not None
+                    and all_devices[other_directory] == all_devices[directory]
                 ):
                     if directory in deduplicated:
                         deduplicated.remove(directory)
@@ -195,6 +201,23 @@ def borgmatic_source_directories(borgmatic_source_directory):
     )
 
 
+ROOT_PATTERN_PREFIX = 'R '
+
+
+def pattern_root_directories(patterns=None):
+    '''
+    Given a sequence of patterns, parse out and return just the root directories.
+    '''
+    if not patterns:
+        return []
+
+    return [
+        pattern.split(ROOT_PATTERN_PREFIX, maxsplit=1)[1]
+        for pattern in patterns
+        if pattern.startswith(ROOT_PATTERN_PREFIX)
+    ]
+
+
 def create_archive(
     dry_run,
     repository,
@@ -222,7 +245,10 @@ def create_archive(
                 location_config.get('source_directories', [])
                 + borgmatic_source_directories(location_config.get('borgmatic_source_directory'))
             )
-        )
+        ),
+        additional_directory_devices=map_directories_to_devices(
+            expand_directories(pattern_root_directories(location_config.get('patterns')))
+        ),
     )
 
     try:

+ 76 - 18
tests/unit/borg/test_create.py

@@ -84,29 +84,34 @@ def test_map_directories_to_devices_with_missing_path_does_not_error():
 
 
 @pytest.mark.parametrize(
-    'directories,expected_directories',
+    'directories,additional_directories,expected_directories',
     (
-        ({'/': 1, '/root': 1}, ('/',)),
-        ({'/': 1, '/root/': 1}, ('/',)),
-        ({'/': 1, '/root': 2}, ('/', '/root')),
-        ({'/root': 1, '/': 1}, ('/',)),
-        ({'/root': 1, '/root/foo': 1}, ('/root',)),
-        ({'/root/': 1, '/root/foo': 1}, ('/root/',)),
-        ({'/root': 1, '/root/foo/': 1}, ('/root',)),
-        ({'/root': 1, '/root/foo': 2}, ('/root', '/root/foo')),
-        ({'/root/foo': 1, '/root': 1}, ('/root',)),
-        ({'/root': None, '/root/foo': None}, ('/root', '/root/foo')),
-        ({'/root': 1, '/etc': 1, '/root/foo/bar': 1}, ('/etc', '/root')),
-        ({'/root': 1, '/root/foo': 1, '/root/foo/bar': 1}, ('/root',)),
-        ({'/dup': 1, '/dup': 1}, ('/dup',)),
-        ({'/foo': 1, '/bar': 1}, ('/bar', '/foo')),
-        ({'/foo': 1, '/bar': 2}, ('/bar', '/foo')),
+        ({'/': 1, '/root': 1}, {}, ('/',)),
+        ({'/': 1, '/root/': 1}, {}, ('/',)),
+        ({'/': 1, '/root': 2}, {}, ('/', '/root')),
+        ({'/root': 1, '/': 1}, {}, ('/',)),
+        ({'/root': 1, '/root/foo': 1}, {}, ('/root',)),
+        ({'/root/': 1, '/root/foo': 1}, {}, ('/root/',)),
+        ({'/root': 1, '/root/foo/': 1}, {}, ('/root',)),
+        ({'/root': 1, '/root/foo': 2}, {}, ('/root', '/root/foo')),
+        ({'/root/foo': 1, '/root': 1}, {}, ('/root',)),
+        ({'/root': None, '/root/foo': None}, {}, ('/root', '/root/foo')),
+        ({'/root': 1, '/etc': 1, '/root/foo/bar': 1}, {}, ('/etc', '/root')),
+        ({'/root': 1, '/root/foo': 1, '/root/foo/bar': 1}, {}, ('/root',)),
+        ({'/dup': 1, '/dup': 1}, {}, ('/dup',)),
+        ({'/foo': 1, '/bar': 1}, {}, ('/bar', '/foo')),
+        ({'/foo': 1, '/bar': 2}, {}, ('/bar', '/foo')),
+        ({'/root/foo': 1}, {'/root': 1}, ()),
+        ({'/root/foo': 1}, {'/root': 2}, ('/root/foo',)),
+        ({'/root/foo': 1}, {}, ('/root/foo',)),
     ),
 )
 def test_deduplicate_directories_removes_child_paths_on_the_same_filesystem(
-    directories, expected_directories
+    directories, additional_directories, expected_directories
 ):
-    assert module.deduplicate_directories(directories) == expected_directories
+    assert (
+        module.deduplicate_directories(directories, additional_directories) == expected_directories
+    )
 
 
 def test_write_pattern_file_writes_pattern_lines():
@@ -285,6 +290,16 @@ def test_borgmatic_source_directories_defaults_when_directory_not_given():
     ]
 
 
+def test_pattern_root_directories_deals_with_none_patterns():
+    assert module.pattern_root_directories(patterns=None) == []
+
+
+def test_pattern_root_directories_parses_roots_and_ignores_others():
+    assert module.pattern_root_directories(
+        ['R /root', '+ /root/foo', '- /root/foo/bar', 'R /baz']
+    ) == ['/root', '/baz']
+
+
 DEFAULT_ARCHIVE_NAME = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}'
 REPO_ARCHIVE_WITH_PATHS = (f'repo::{DEFAULT_ARCHIVE_NAME}', 'foo', 'bar')
 
@@ -294,6 +309,7 @@ def test_create_archive_calls_borg_with_parameters():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -332,6 +348,7 @@ def test_create_archive_calls_borg_with_environment():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -372,6 +389,7 @@ def test_create_archive_with_patterns_calls_borg_with_patterns_including_convert
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(
@@ -413,6 +431,7 @@ def test_create_archive_with_exclude_patterns_calls_borg_with_excludes():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(('exclude',))
     flexmock(module).should_receive('write_pattern_file').and_return(None).and_return(
@@ -453,6 +472,7 @@ def test_create_archive_with_log_info_calls_borg_with_info_parameter():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -492,6 +512,7 @@ def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -532,6 +553,7 @@ def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -571,6 +593,7 @@ def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -611,6 +634,7 @@ def test_create_archive_with_dry_run_calls_borg_with_dry_run_parameter():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -651,6 +675,7 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats_paramete
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -691,6 +716,7 @@ def test_create_archive_with_checkpoint_interval_calls_borg_with_checkpoint_inte
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -729,6 +755,7 @@ def test_create_archive_with_chunker_params_calls_borg_with_chunker_params_param
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -767,6 +794,7 @@ def test_create_archive_with_compression_calls_borg_with_compression_parameters(
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -810,6 +838,7 @@ def test_create_archive_with_upload_rate_limit_calls_borg_with_upload_ratelimit_
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -848,6 +877,7 @@ def test_create_archive_with_working_directory_calls_borg_with_working_directory
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').with_args('/working/dir').and_return(
         '/working/dir'
     )
@@ -889,6 +919,7 @@ def test_create_archive_with_one_file_system_calls_borg_with_one_file_system_par
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -933,6 +964,7 @@ def test_create_archive_with_numeric_ids_calls_borg_with_numeric_ids_parameter(
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -972,6 +1004,7 @@ def test_create_archive_with_read_special_calls_borg_with_read_special_parameter
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1018,6 +1051,7 @@ def test_create_archive_with_basic_option_calls_borg_with_corresponding_paramete
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1068,6 +1102,7 @@ def test_create_archive_with_atime_option_calls_borg_with_corresponding_paramete
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1118,6 +1153,7 @@ def test_create_archive_with_flags_option_calls_borg_with_corresponding_paramete
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1157,6 +1193,7 @@ def test_create_archive_with_files_cache_calls_borg_with_files_cache_parameters(
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1196,6 +1233,7 @@ def test_create_archive_with_local_path_calls_borg_via_local_path():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1235,6 +1273,7 @@ def test_create_archive_with_remote_path_calls_borg_with_remote_path_parameters(
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1274,6 +1313,7 @@ def test_create_archive_with_umask_calls_borg_with_umask_parameters():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1312,6 +1352,7 @@ def test_create_archive_with_lock_wait_calls_borg_with_lock_wait_parameters():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1350,6 +1391,7 @@ def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_warning_o
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1389,6 +1431,7 @@ def test_create_archive_with_stats_and_log_info_calls_borg_with_stats_parameter_
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1429,6 +1472,7 @@ def test_create_archive_with_files_calls_borg_with_list_parameter_and_warning_ou
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1468,6 +1512,7 @@ def test_create_archive_with_files_and_log_info_calls_borg_with_list_parameter_a
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1508,6 +1553,7 @@ def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_para
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1548,6 +1594,7 @@ def test_create_archive_with_progress_calls_borg_with_progress_parameter():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1588,6 +1635,7 @@ def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progr
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1630,6 +1678,7 @@ def test_create_archive_with_json_calls_borg_with_json_parameter():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1671,6 +1720,7 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_parameter()
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1713,6 +1763,7 @@ def test_create_archive_with_source_directories_glob_expands():
     flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'food'))
     flexmock(module).should_receive('map_directories_to_devices').and_return({})
     flexmock(module).should_receive('expand_directories').and_return(())
+    flexmock(module).should_receive('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1752,6 +1803,7 @@ def test_create_archive_with_non_matching_source_directories_glob_passes_through
     flexmock(module).should_receive('deduplicate_directories').and_return(('foo*',))
     flexmock(module).should_receive('map_directories_to_devices').and_return({})
     flexmock(module).should_receive('expand_directories').and_return(())
+    flexmock(module).should_receive('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1791,6 +1843,7 @@ def test_create_archive_with_glob_calls_borg_with_expanded_directories():
     flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'food'))
     flexmock(module).should_receive('map_directories_to_devices').and_return({})
     flexmock(module).should_receive('expand_directories').and_return(())
+    flexmock(module).should_receive('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1829,6 +1882,7 @@ def test_create_archive_with_archive_name_format_calls_borg_with_archive_name():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1868,6 +1922,7 @@ def test_create_archive_with_archive_name_format_accepts_borg_placeholders():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1907,6 +1962,7 @@ def test_create_archive_with_repository_accepts_borg_placeholders():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1945,6 +2001,7 @@ def test_create_archive_with_extra_borg_options_calls_borg_with_extra_options():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)
@@ -1984,6 +2041,7 @@ def test_create_archive_with_stream_processes_calls_borg_with_processes():
     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('pattern_root_directories').and_return([])
     flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
     flexmock(module).should_receive('expand_home_directories').and_return(())
     flexmock(module).should_receive('write_pattern_file').and_return(None)