From 203856eaa6d396a599e1a3191884d46f23634b8f Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Fri, 25 Sep 2026 22:56:00 +0200 Subject: [PATCH] posixfs: move replaces an existing destination also on Windows Path.rename() replaces an existing destination on POSIX, but raises FileExistsError on Windows. Use Path.replace() so move() behaves the same everywhere and like the other backends, which all overwrite the target. This broke e.g. soft-deleting an item while a soft-deleted item of the same name already existed (borgbackup/borg#10432 CI). Co-Authored-By: Claude Opus 5.5 --- src/borgstore/backends/posixfs.py | 3 ++- tests/test_backends.py | 9 +++++++++ tests/test_store.py | 6 ++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/borgstore/backends/posixfs.py b/src/borgstore/backends/posixfs.py index 52dd5af..b2b1ee1 100644 --- a/src/borgstore/backends/posixfs.py +++ b/src/borgstore/backends/posixfs.py @@ -295,7 +295,8 @@ def delete(self, name): def move(self, curr_name, new_name): def _rename_to_new_name(): - curr_path.rename(new_path) + # replace() (not rename()) so an existing destination also gets replaced on Windows. + curr_path.replace(new_path) if not self.opened: raise BackendMustBeOpen() diff --git a/tests/test_backends.py b/tests/test_backends.py index bae530b..2c945f7 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -895,6 +895,15 @@ def test_missing_nesting_dir_move(tested_backends, request): backend.move("namespace2/nest2/key2", "namespace2a/nest2a/key2a") +def test_move_replaces_existing(tested_backends, request): + with get_backend_from_fixture(tested_backends, request) as backend: + backend.store("key1", b"value1") + backend.store("key2", b"value2") + backend.move("key1", "key2") + assert not backend.info("key1").exists + assert backend.load("key2") == b"value1" + + def test_parent_is_an_object(tested_backends, request): # a name below an object (not below a directory) does not exist, like on Windows, where the OS reports # such a path as not found (on POSIX, it is ENOTDIR). diff --git a/tests/test_store.py b/tests/test_store.py index 9310299..085a902 100644 --- a/tests/test_store.py +++ b/tests/test_store.py @@ -361,6 +361,12 @@ def test_move_delete_undelete(posixfs_store_created): store.move(nsk0, undelete=True) # undelete a previously soft-deleted item assert list_store_names(store, ns, deleted=False) == [k0, k1] assert list_store_names(store, ns, deleted=True) == [] + # Delete again, while a soft-deleted item of the same name exists + store.move(nsk0, delete=True) # soft-delete + store.store(nsk0, v0) # same name again + store.move(nsk0, delete=True) # soft-delete, replacing the already soft-deleted item + assert list_store_names(store, ns, deleted=False) == [k1] + assert list_store_names(store, ns, deleted=True) == [k0] def test_move_change_level(posixfs_store_created):