From 449896f6619053b462131cd20737f09a53d92ade Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Mon, 11 Oct 2021 10:40:10 -0700 Subject: [PATCH] Fix error when configured source directories are not present on the filesystem at the time of backup (#387). --- NEWS | 2 ++ borgmatic/borg/create.py | 14 ++++++++++---- tests/unit/borg/test_create.py | 25 +++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 48f39f1..708c9b6 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ 1.5.19.dev0 + * #387: Fix error when configured source directories are not present on the filesystem at the time + of backup. Now, Borg will complain, but the backup will still continue. * Update sample systemd service file with more granular read-only filesystem settings. * Move Gitea and GitHub hosting from a personal namespace to an organization for better collaboration with related projects. diff --git a/borgmatic/borg/create.py b/borgmatic/borg/create.py index a9aa222..6b02c87 100644 --- a/borgmatic/borg/create.py +++ b/borgmatic/borg/create.py @@ -44,13 +44,18 @@ def _expand_home_directories(directories): return tuple(os.path.expanduser(directory) for directory in directories) -def map_directories_to_devices(directories): # pragma: no cover +def map_directories_to_devices(directories): ''' Given a sequence of directories, return a map from directory to an identifier for the device on - which that directory resides. This is handy for determining whether two different directories - are on the same filesystem (have the same device identifier). + which that directory resides or None if the path doesn't exist. + + This is handy for determining whether two different directories are on the same filesystem (have + the same device identifier). ''' - return {directory: os.stat(directory).st_dev for directory in directories} + return { + directory: os.stat(directory).st_dev if os.path.exists(directory) else None + for directory in directories + } def deduplicate_directories(directory_devices): @@ -82,6 +87,7 @@ def deduplicate_directories(directory_devices): 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] ): if directory in deduplicated: diff --git a/tests/unit/borg/test_create.py b/tests/unit/borg/test_create.py index cc56881..99f2910 100644 --- a/tests/unit/borg/test_create.py +++ b/tests/unit/borg/test_create.py @@ -60,6 +60,30 @@ def test_expand_home_directories_considers_none_as_no_directories(): assert paths == () +def test_map_directories_to_devices_gives_device_id_per_path(): + flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55)) + flexmock(module.os).should_receive('stat').with_args('/bar').and_return(flexmock(st_dev=66)) + + device_map = module.map_directories_to_devices(('/foo', '/bar')) + + assert device_map == { + '/foo': 55, + '/bar': 66, + } + + +def test_map_directories_to_devices_with_missing_path_does_not_error(): + flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55)) + flexmock(module.os).should_receive('stat').with_args('/bar').and_raise(FileNotFoundError) + + device_map = module.map_directories_to_devices(('/foo', '/bar')) + + assert device_map == { + '/foo': 55, + '/bar': None, + } + + @pytest.mark.parametrize( 'directories,expected_directories', ( @@ -72,6 +96,7 @@ def test_expand_home_directories_considers_none_as_no_directories(): ({'/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',)),