gabriel / muse public
test_cli_reset_revert.py python
236 lines 7.6 KB
Raw
1 """Tests for muse reset and muse revert."""
2
3 import pathlib
4
5 import pytest
6 from tests.cli_test_helper import CliRunner
7
8 cli = None # argparse migration — CliRunner ignores this arg
9 from muse.core.refs import get_head_commit_id
10
11 runner = CliRunner()
12
13
14 @pytest.fixture
15 def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
16 monkeypatch.chdir(tmp_path)
17 monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path))
18 result = runner.invoke(cli, ["init"])
19 assert result.exit_code == 0, result.output
20 return tmp_path
21
22
23 def _write(repo: pathlib.Path, filename: str, content: str = "data") -> None:
24 (repo / filename).write_text(content)
25
26
27 def _commit(msg: str = "initial") -> None:
28 runner.invoke(cli, ["code", "add", "."])
29 result = runner.invoke(cli, ["commit", "-m", msg])
30 assert result.exit_code == 0, result.output
31
32
33 def _head_id(repo: pathlib.Path) -> str | None:
34 return get_head_commit_id(repo, "main")
35
36
37 # ---------------------------------------------------------------------------
38 # reset
39 # ---------------------------------------------------------------------------
40
41
42 class TestResetSoft:
43 def test_moves_branch_pointer(self, repo: pathlib.Path) -> None:
44 _write(repo, "beat.py", "v1")
45 _commit("first")
46 first_id = _head_id(repo)
47
48 _write(repo, "beat.py", "v2")
49 _commit("second")
50 assert _head_id(repo) != first_id
51
52 result = runner.invoke(cli, ["reset", first_id])
53 assert result.exit_code == 0, result.output
54 assert _head_id(repo) == first_id
55
56 def test_soft_preserves_workdir(self, repo: pathlib.Path) -> None:
57 _write(repo, "beat.py", "v1")
58 _commit("first")
59 first_id = _head_id(repo)
60
61 _write(repo, "lead.py", "new")
62 _commit("second")
63
64 runner.invoke(cli, ["reset", first_id])
65 # workdir still has lead.mid from second commit (soft = no restore)
66 assert (repo / "lead.py").exists()
67
68 def test_soft_output_message(self, repo: pathlib.Path) -> None:
69 _write(repo, "beat.py")
70 _commit("first")
71 first_id = _head_id(repo)
72 _write(repo, "lead.py")
73 _commit("second")
74
75 result = runner.invoke(cli, ["reset", first_id])
76 assert "Moved" in result.output or first_id[:8] in result.output
77
78 def test_reset_unknown_ref_errors(self, repo: pathlib.Path) -> None:
79 _write(repo, "beat.py")
80 _commit("only")
81 result = runner.invoke(cli, ["reset", "deadbeef"])
82 assert result.exit_code != 0
83 assert "not found" in result.stderr.lower() or "deadbeef" in result.stderr
84
85
86 class TestResetHard:
87 def test_moves_branch_pointer(self, repo: pathlib.Path) -> None:
88 _write(repo, "beat.py", "v1")
89 _commit("first")
90 first_id = _head_id(repo)
91
92 _write(repo, "beat.py", "v2")
93 _commit("second")
94
95 result = runner.invoke(cli, ["reset", "--hard", first_id])
96 assert result.exit_code == 0, result.output
97 assert _head_id(repo) == first_id
98
99 def test_restores_workdir(self, repo: pathlib.Path) -> None:
100 _write(repo, "beat.py", "v1")
101 _commit("first")
102 first_id = _head_id(repo)
103
104 _write(repo, "lead.py", "new")
105 _commit("second")
106
107 runner.invoke(cli, ["reset", "--hard", first_id])
108 # After hard reset, workdir should reflect first commit (no lead.mid)
109 assert not (repo / "lead.py").exists()
110 assert (repo / "beat.py").exists()
111
112 def test_restores_file_content(self, repo: pathlib.Path) -> None:
113 _write(repo, "beat.py", "original")
114 _commit("first")
115 first_id = _head_id(repo)
116
117 _write(repo, "beat.py", "modified")
118 _commit("second")
119
120 runner.invoke(cli, ["reset", "--hard", first_id])
121 assert (repo / "beat.py").read_text() == "original"
122
123 def test_hard_output_shows_commit(self, repo: pathlib.Path) -> None:
124 _write(repo, "beat.py")
125 _commit("the target")
126 first_id = _head_id(repo)
127 _write(repo, "lead.py")
128 _commit("second")
129
130 result = runner.invoke(cli, ["reset", "--hard", first_id])
131 assert result.exit_code == 0
132 assert "HEAD is now at" in result.output
133
134
135 # ---------------------------------------------------------------------------
136 # revert
137 # ---------------------------------------------------------------------------
138
139
140 class TestRevert:
141 def test_creates_new_commit(self, repo: pathlib.Path) -> None:
142 _write(repo, "beat.py")
143 _commit("add beat")
144 before_id = _head_id(repo)
145
146 _write(repo, "lead.py")
147 _commit("add lead")
148 after_id = _head_id(repo)
149
150 result = runner.invoke(cli, ["revert", after_id])
151 assert result.exit_code == 0, result.output
152 new_id = _head_id(repo)
153 assert new_id not in (before_id, after_id)
154
155 def test_revert_restores_parent_state(self, repo: pathlib.Path) -> None:
156 _write(repo, "beat.py", "original")
157 _commit("first")
158
159 _write(repo, "beat.py", "changed")
160 _commit("second")
161 second_id = _head_id(repo)
162
163 runner.invoke(cli, ["revert", second_id])
164 assert (repo / "beat.py").read_text() == "original"
165
166 def test_revert_default_message_includes_original(self, repo: pathlib.Path) -> None:
167 # Need a base commit first so "my change" is not the root
168 _write(repo, "base.py", "base")
169 _commit("base")
170
171 _write(repo, "beat.py")
172 _commit("my change")
173 commit_id = _head_id(repo)
174
175 _write(repo, "lead.py")
176 _commit("third")
177
178 result = runner.invoke(cli, ["revert", commit_id])
179 assert result.exit_code == 0
180 assert "my change" in result.output or "Revert" in result.output
181
182 def test_revert_custom_message(self, repo: pathlib.Path) -> None:
183 _write(repo, "base.py", "base")
184 _commit("base")
185 _write(repo, "beat.py")
186 _commit("to revert")
187 commit_id = _head_id(repo)
188 _write(repo, "lead.py")
189 _commit("third")
190
191 result = runner.invoke(cli, ["revert", "-m", "undo that change", commit_id])
192 assert result.exit_code == 0, result.output
193 assert "undo that change" in result.output
194
195 def test_revert_no_commit_flag(self, repo: pathlib.Path) -> None:
196 _write(repo, "base.py", "base")
197 _commit("base")
198
199 _write(repo, "beat.py")
200 _commit("second")
201 second_id = _head_id(repo)
202
203 _write(repo, "lead.py")
204 _commit("third")
205
206 result = runner.invoke(cli, ["revert", "--no-commit", second_id])
207 assert result.exit_code == 0, result.output
208 assert "working tree" in result.output or "applied" in result.output.lower()
209 # HEAD should not have advanced
210 assert _head_id(repo) != second_id # third is still HEAD
211
212 def test_revert_unknown_ref_errors(self, repo: pathlib.Path) -> None:
213 _write(repo, "beat.py")
214 _commit("only")
215 result = runner.invoke(cli, ["revert", "deadbeef"])
216 assert result.exit_code != 0
217
218 def test_revert_root_commit_errors(self, repo: pathlib.Path) -> None:
219 _write(repo, "beat.py")
220 _commit("root")
221 root_id = _head_id(repo)
222
223 result = runner.invoke(cli, ["revert", root_id])
224 assert result.exit_code != 0
225 assert "root" in result.stderr.lower() or "parent" in result.stderr.lower()
226
227 def test_revert_removes_added_file(self, repo: pathlib.Path) -> None:
228 _write(repo, "beat.py", "base")
229 _commit("base")
230
231 _write(repo, "lead.py", "added")
232 _commit("add lead")
233 lead_commit = _head_id(repo)
234
235 runner.invoke(cli, ["revert", lead_commit])
236 assert not (repo / "lead.py").exists()
File History 1 commit