diff --git a/NEWS b/NEWS index b46f8fbc..84e656e3 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,7 @@ +1.9.6.dev0 + * #959: Fix an error in the Btrfs hook when a "/" subvolume is configured in borgmatic's source + directories. + 1.9.5 * #418: Backup and restore databases that have the same name but with different ports, hostnames, or hooks. diff --git a/borgmatic/hooks/data_source/btrfs.py b/borgmatic/hooks/data_source/btrfs.py index 25295460..7872c734 100644 --- a/borgmatic/hooks/data_source/btrfs.py +++ b/borgmatic/hooks/data_source/btrfs.py @@ -115,17 +115,15 @@ def get_subvolumes(btrfs_command, findmnt_command, source_directories=None): BORGMATIC_SNAPSHOT_PREFIX = '.borgmatic-snapshot-' -def make_snapshot_path(subvolume_path): # pragma: no cover +def make_snapshot_path(subvolume_path): ''' Given the path to a subvolume, make a corresponding snapshot path for it. ''' return os.path.join( subvolume_path, f'{BORGMATIC_SNAPSHOT_PREFIX}{os.getpid()}', - # Included so that the snapshot ends up in the Borg archive at the "original" subvolume - # path. - subvolume_path.lstrip(os.path.sep), - ) + # Included so that the snapshot ends up in the Borg archive at the "original" subvolume path. + ) + subvolume_path.rstrip(os.path.sep) def make_snapshot_exclude_path(subvolume_path): # pragma: no cover @@ -155,7 +153,7 @@ def make_snapshot_exclude_path(subvolume_path): # pragma: no cover ) -def make_borg_source_directory_path(subvolume_path, source_directory): # pragma: no cover +def make_borg_source_directory_path(subvolume_path, source_directory): ''' Given the path to a subvolume and a source directory inside it, make a corresponding path for the source directory within a snapshot path intended for giving to Borg. @@ -181,7 +179,7 @@ def snapshot_subvolume(btrfs_command, subvolume_path, snapshot_path): # pragma: + ( 'subvolume', 'snapshot', - '-r', # Read-only, + '-r', # Read-only. subvolume_path, snapshot_path, ), diff --git a/pyproject.toml b/pyproject.toml index a7c63e5b..56713835 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "borgmatic" -version = "1.9.5" +version = "1.9.6.dev0" authors = [ { name="Dan Helfman", email="witten@torsion.org" }, ] diff --git a/tests/unit/hooks/data_source/test_btrfs.py b/tests/unit/hooks/data_source/test_btrfs.py index 35d78b2c..5c21f239 100644 --- a/tests/unit/hooks/data_source/test_btrfs.py +++ b/tests/unit/hooks/data_source/test_btrfs.py @@ -128,6 +128,41 @@ def test_get_subvolumes_without_source_directories_collects_all_subvolumes_from_ ) +@pytest.mark.parametrize( + 'subvolume_path,expected_snapshot_path', + ( + ('/foo/bar', '/foo/bar/.borgmatic-snapshot-1234/foo/bar'), + ('/', '/.borgmatic-snapshot-1234'), + ), +) +def test_make_snapshot_path_includes_stripped_subvolume_path( + subvolume_path, expected_snapshot_path +): + flexmock(module.os).should_receive('getpid').and_return(1234) + + assert module.make_snapshot_path(subvolume_path) == expected_snapshot_path + + +@pytest.mark.parametrize( + 'subvolume_path,source_directory_path,expected_path', + ( + ('/foo/bar', '/foo/bar/baz', '/foo/bar/.borgmatic-snapshot-1234/./foo/bar/baz'), + ('/foo/bar', '/foo/bar', '/foo/bar/.borgmatic-snapshot-1234/./foo/bar'), + ('/', '/foo', '/.borgmatic-snapshot-1234/./foo'), + ('/', '/', '/.borgmatic-snapshot-1234/./'), + ), +) +def test_make_borg_source_directory_path_includes_slashdot_hack_and_stripped_source_directory_path( + subvolume_path, source_directory_path, expected_path +): + flexmock(module.os).should_receive('getpid').and_return(1234) + + assert ( + module.make_borg_source_directory_path(subvolume_path, source_directory_path) + == expected_path + ) + + def test_dump_data_sources_snapshots_each_subvolume_and_updates_source_directories(): source_directories = ['/foo', '/mnt/subvol1'] config = {'btrfs': {}}