Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions desloppify/app/commands/move/apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
from desloppify.base.output.terminal import colorize


def _ensure_move_destination_absent(dest_abs: str) -> None:
if Path(dest_abs).exists():
raise FileExistsError(f"Destination already exists: {dest_abs}")


def _rollback_written_files(written_files: dict[str, str]) -> None:
failed = restore_files_best_effort(written_files, safe_write_text)
for filepath in failed:
Expand Down Expand Up @@ -55,6 +60,7 @@ def apply_file_move(
Path(dest_abs).parent.mkdir(parents=True, exist_ok=True)
written_files: dict[str, str] = {}
try:
_ensure_move_destination_absent(dest_abs)
shutil.move(source_abs, dest_abs)

if dest_abs in new_contents:
Expand Down Expand Up @@ -85,6 +91,7 @@ def apply_directory_move(
Path(dest_abs).parent.mkdir(parents=True, exist_ok=True)
written_files: dict[str, str] = {}
try:
_ensure_move_destination_absent(dest_abs)
shutil.move(source_abs, dest_abs)

for src_file, changes in internal_changes.items():
Expand Down
29 changes: 29 additions & 0 deletions desloppify/tests/commands/test_transitive_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,19 @@ def test_file_move_with_importer_changes(self, tmp_path):
)
assert importer.read_text() == "from b import thing"

def test_file_move_fails_if_destination_already_exists(self, tmp_path):
src = tmp_path / "a.py"
dest = tmp_path / "b.py"
src.write_text("source content")
dest.write_text("existing content")

with pytest.raises(FileExistsError, match="Destination already exists"):
move_apply_mod.apply_file_move(str(src), str(dest), {}, [])

assert src.exists()
assert src.read_text() == "source content"
assert dest.read_text() == "existing content"

def test_file_move_rollback_on_write_error(self, tmp_path):
"""If writing an importer fails, the move is rolled back."""
src = tmp_path / "a.py"
Expand Down Expand Up @@ -802,6 +815,22 @@ def test_directory_move_with_internal_changes(self, tmp_path):
)
assert (dest / "a.py").read_text() == "from new_pkg.b import f"

def test_directory_move_fails_if_destination_already_exists(self, tmp_path):
src = tmp_path / "pkg"
src.mkdir()
(src / "mod.py").write_text("source content")

dest = tmp_path / "new_pkg"
dest.mkdir()
(dest / "mod.py").write_text("existing content")

with pytest.raises(FileExistsError, match="Destination already exists"):
move_apply_mod.apply_directory_move(str(src), str(dest), src, {}, {})

assert src.exists()
assert (src / "mod.py").read_text() == "source content"
assert (dest / "mod.py").read_text() == "existing content"


# =====================================================================
# Module 5: shared_phases.py
Expand Down