gabriel / muse public
test_commit_workdir_preservation.py python
136 lines 5.1 KB
Raw
sha256:2eaa5d95f9d9383498e76947410a26e5a3ba23d182f339910c424cf88fad412b fix: try fetch/presign before fetch/mpack to avoid Cloudfla… Sonnet 4.6 patch 6 days ago
1 """Tests that muse commit does NOT overwrite the working tree.
2
3 Regression suite for the apply_manifest-in-commit bug:
4 commit.py was calling apply_manifest() after writing the snapshot, which
5 restored every tracked file from the object store — silently destroying any
6 unstaged changes that existed on disk at commit time.
7
8 Expected (Git-compatible) behaviour: commit writes to the object store and
9 advances the branch ref; it never touches files in the working tree.
10 """
11
12 from __future__ import annotations
13
14 import pathlib
15
16 import pytest
17
18 from tests.cli_test_helper import CliRunner
19
20 runner = CliRunner()
21
22
23 def _run(repo: pathlib.Path, *args: str) -> None:
24 r = runner.invoke(None, list(args), cwd=repo)
25 assert r.exit_code == 0, f"muse {' '.join(args)} failed:\n{r.output}"
26
27
28 # ---------------------------------------------------------------------------
29 # Core regression: unstaged changes to a tracked file survive commit
30 # ---------------------------------------------------------------------------
31
32
33 class TestCommitPreservesWorkdir:
34 def test_unstaged_changes_survive_commit(self, muse_repo: pathlib.Path) -> None:
35 """Unstaged edits to a tracked file must not be overwritten by commit."""
36 f = muse_repo / "song.py"
37 f.write_text("v1\n")
38 _run(muse_repo, "code", "add", "song.py")
39 _run(muse_repo, "commit", "-m", "add song")
40
41 # Make a second change and stage it
42 f.write_text("v2\n")
43 _run(muse_repo, "code", "add", "song.py")
44
45 # Make a THIRD change — intentionally NOT staged
46 f.write_text("v3 — unstaged\n")
47
48 # Commit captures v2 (staged); v3 on disk must survive
49 _run(muse_repo, "commit", "-m", "commit v2")
50
51 assert f.read_text() == "v3 — unstaged\n", (
52 "commit overwrote the working tree with the staged version"
53 )
54
55 def test_unstaged_changes_to_multiple_files(self, muse_repo: pathlib.Path) -> None:
56 """Multiple files with unstaged edits all survive commit."""
57 for name in ("a.py", "b.py", "c.py"):
58 f = muse_repo / name
59 f.write_text("initial\n")
60
61 _run(muse_repo, "code", "add", ".")
62 _run(muse_repo, "commit", "-m", "initial")
63
64 # Stage updated versions
65 for name in ("a.py", "b.py", "c.py"):
66 (muse_repo / name).write_text("staged\n")
67 _run(muse_repo, "code", "add", ".")
68
69 # Write unstaged versions
70 for name in ("a.py", "b.py", "c.py"):
71 (muse_repo / name).write_text(f"unstaged {name}\n")
72
73 _run(muse_repo, "commit", "-m", "commit staged versions")
74
75 for name in ("a.py", "b.py", "c.py"):
76 assert (muse_repo / name).read_text() == f"unstaged {name}\n", (
77 f"{name} was overwritten by commit"
78 )
79
80 def test_untracked_files_survive_commit(self, muse_repo: pathlib.Path) -> None:
81 """Untracked files must never be touched by commit (was already true)."""
82 tracked = muse_repo / "tracked.py"
83 tracked.write_text("tracked\n")
84 _run(muse_repo, "code", "add", "tracked.py")
85 _run(muse_repo, "commit", "-m", "add tracked")
86
87 untracked = muse_repo / "notes.txt"
88 untracked.write_text("my notes\n")
89
90 tracked.write_text("tracked v2\n")
91 _run(muse_repo, "code", "add", "tracked.py")
92 _run(muse_repo, "commit", "-m", "update tracked")
93
94 assert untracked.read_text() == "my notes\n"
95
96 def test_new_untracked_file_not_deleted_by_commit(self, muse_repo: pathlib.Path) -> None:
97 """A new file created after staging must not be deleted by commit."""
98 f = muse_repo / "old.py"
99 f.write_text("old\n")
100 _run(muse_repo, "code", "add", "old.py")
101 _run(muse_repo, "commit", "-m", "add old")
102
103 # Stage another file
104 g = muse_repo / "new_staged.py"
105 g.write_text("staged\n")
106 _run(muse_repo, "code", "add", "new_staged.py")
107
108 # Create a brand-new file AFTER staging — should survive commit
109 extra = muse_repo / "created_after_stage.py"
110 extra.write_text("i exist\n")
111
112 _run(muse_repo, "commit", "-m", "commit new_staged")
113
114 assert extra.exists(), "commit deleted a file that was never staged"
115 assert extra.read_text() == "i exist\n"
116
117 def test_staged_deletion_still_removes_file(self, muse_repo: pathlib.Path) -> None:
118 """muse rm + commit must still delete the file from disk (regression guard)."""
119 f = muse_repo / "gone.py"
120 f.write_text("bye\n")
121 _run(muse_repo, "code", "add", "gone.py")
122 _run(muse_repo, "commit", "-m", "add gone")
123
124 _run(muse_repo, "rm", "gone.py")
125 _run(muse_repo, "commit", "-m", "delete gone")
126
127 assert not f.exists(), "staged deletion did not remove the file"
128
129 def test_working_tree_unchanged_after_first_commit(self, muse_repo: pathlib.Path) -> None:
130 """On the very first commit, files that were staged are still on disk."""
131 f = muse_repo / "hello.py"
132 f.write_text("hello\n")
133 _run(muse_repo, "code", "add", "hello.py")
134 _run(muse_repo, "commit", "-m", "first commit")
135
136 assert f.read_text() == "hello\n"
File History 1 commit
sha256:2eaa5d95f9d9383498e76947410a26e5a3ba23d182f339910c424cf88fad412b fix: try fetch/presign before fetch/mpack to avoid Cloudfla… Sonnet 4.6 patch 6 days ago