238 lines
9.5 KiB
Python
238 lines
9.5 KiB
Python
from pathlib import Path
|
|
from subprocess import CalledProcessError
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from hermes_cli import main as hermes_main
|
|
|
|
|
|
def test_stash_local_changes_if_needed_returns_none_when_tree_clean(monkeypatch, tmp_path):
|
|
calls = []
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
calls.append((cmd, kwargs))
|
|
if cmd[-2:] == ["status", "--porcelain"]:
|
|
return SimpleNamespace(stdout="", returncode=0)
|
|
raise AssertionError(f"unexpected command: {cmd}")
|
|
|
|
monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
|
|
|
|
stash_ref = hermes_main._stash_local_changes_if_needed(["git"], tmp_path)
|
|
|
|
assert stash_ref is None
|
|
assert [cmd[-2:] for cmd, _ in calls] == [["status", "--porcelain"]]
|
|
|
|
|
|
def test_stash_local_changes_if_needed_returns_specific_stash_commit(monkeypatch, tmp_path):
|
|
calls = []
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
calls.append((cmd, kwargs))
|
|
if cmd[-2:] == ["status", "--porcelain"]:
|
|
return SimpleNamespace(stdout=" M hermes_cli/main.py\n?? notes.txt\n", returncode=0)
|
|
if cmd[1:4] == ["stash", "push", "--include-untracked"]:
|
|
return SimpleNamespace(stdout="Saved working directory\n", returncode=0)
|
|
if cmd[-3:] == ["rev-parse", "--verify", "refs/stash"]:
|
|
return SimpleNamespace(stdout="abc123\n", returncode=0)
|
|
raise AssertionError(f"unexpected command: {cmd}")
|
|
|
|
monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
|
|
|
|
stash_ref = hermes_main._stash_local_changes_if_needed(["git"], tmp_path)
|
|
|
|
assert stash_ref == "abc123"
|
|
assert calls[1][0][1:4] == ["stash", "push", "--include-untracked"]
|
|
assert calls[2][0][-3:] == ["rev-parse", "--verify", "refs/stash"]
|
|
|
|
|
|
def test_resolve_stash_selector_returns_matching_entry(monkeypatch, tmp_path):
|
|
def fake_run(cmd, **kwargs):
|
|
assert cmd == ["git", "stash", "list", "--format=%gd %H"]
|
|
return SimpleNamespace(
|
|
stdout="stash@{0} def456\nstash@{1} abc123\n",
|
|
returncode=0,
|
|
)
|
|
|
|
monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
|
|
|
|
assert hermes_main._resolve_stash_selector(["git"], tmp_path, "abc123") == "stash@{1}"
|
|
|
|
|
|
|
|
def test_restore_stashed_changes_prompts_before_applying(monkeypatch, tmp_path, capsys):
|
|
calls = []
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
calls.append((cmd, kwargs))
|
|
if cmd[1:3] == ["stash", "apply"]:
|
|
return SimpleNamespace(stdout="applied\n", stderr="", returncode=0)
|
|
if cmd[1:3] == ["stash", "list"]:
|
|
return SimpleNamespace(stdout="stash@{1} abc123\n", stderr="", returncode=0)
|
|
if cmd[1:3] == ["stash", "drop"]:
|
|
return SimpleNamespace(stdout="dropped\n", stderr="", returncode=0)
|
|
raise AssertionError(f"unexpected command: {cmd}")
|
|
|
|
monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
|
|
monkeypatch.setattr("builtins.input", lambda: "")
|
|
|
|
restored = hermes_main._restore_stashed_changes(["git"], tmp_path, "abc123", prompt_user=True)
|
|
|
|
assert restored is True
|
|
assert calls[0][0] == ["git", "stash", "apply", "abc123"]
|
|
assert calls[1][0] == ["git", "stash", "list", "--format=%gd %H"]
|
|
assert calls[2][0] == ["git", "stash", "drop", "stash@{1}"]
|
|
out = capsys.readouterr().out
|
|
assert "Restore local changes now? [Y/n]" in out
|
|
assert "restored on top of the updated codebase" in out
|
|
assert "git diff" in out
|
|
assert "git status" in out
|
|
|
|
|
|
def test_restore_stashed_changes_can_skip_restore_and_keep_stash(monkeypatch, tmp_path, capsys):
|
|
calls = []
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
calls.append((cmd, kwargs))
|
|
raise AssertionError(f"unexpected command: {cmd}")
|
|
|
|
monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
|
|
monkeypatch.setattr("builtins.input", lambda: "n")
|
|
|
|
restored = hermes_main._restore_stashed_changes(["git"], tmp_path, "abc123", prompt_user=True)
|
|
|
|
assert restored is False
|
|
assert calls == []
|
|
out = capsys.readouterr().out
|
|
assert "Restore local changes now? [Y/n]" in out
|
|
assert "Your changes are still preserved in git stash." in out
|
|
assert "git stash apply abc123" in out
|
|
|
|
|
|
def test_restore_stashed_changes_applies_without_prompt_when_disabled(monkeypatch, tmp_path, capsys):
|
|
calls = []
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
calls.append((cmd, kwargs))
|
|
if cmd[1:3] == ["stash", "apply"]:
|
|
return SimpleNamespace(stdout="applied\n", stderr="", returncode=0)
|
|
if cmd[1:3] == ["stash", "list"]:
|
|
return SimpleNamespace(stdout="stash@{0} abc123\n", stderr="", returncode=0)
|
|
if cmd[1:3] == ["stash", "drop"]:
|
|
return SimpleNamespace(stdout="dropped\n", stderr="", returncode=0)
|
|
raise AssertionError(f"unexpected command: {cmd}")
|
|
|
|
monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
|
|
|
|
restored = hermes_main._restore_stashed_changes(["git"], tmp_path, "abc123", prompt_user=False)
|
|
|
|
assert restored is True
|
|
assert calls[0][0] == ["git", "stash", "apply", "abc123"]
|
|
assert calls[1][0] == ["git", "stash", "list", "--format=%gd %H"]
|
|
assert calls[2][0] == ["git", "stash", "drop", "stash@{0}"]
|
|
assert "Restore local changes now?" not in capsys.readouterr().out
|
|
|
|
|
|
|
|
def test_print_stash_cleanup_guidance_with_selector(capsys):
|
|
hermes_main._print_stash_cleanup_guidance("abc123", "stash@{2}")
|
|
|
|
out = capsys.readouterr().out
|
|
assert "Check `git status` first" in out
|
|
assert "git stash list --format='%gd %H %s'" in out
|
|
assert "git stash drop stash@{2}" in out
|
|
|
|
|
|
|
|
def test_restore_stashed_changes_keeps_going_when_stash_entry_cannot_be_resolved(monkeypatch, tmp_path, capsys):
|
|
calls = []
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
calls.append((cmd, kwargs))
|
|
if cmd[1:3] == ["stash", "apply"]:
|
|
return SimpleNamespace(stdout="applied\n", stderr="", returncode=0)
|
|
if cmd[1:3] == ["stash", "list"]:
|
|
return SimpleNamespace(stdout="stash@{0} def456\n", stderr="", returncode=0)
|
|
raise AssertionError(f"unexpected command: {cmd}")
|
|
|
|
monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
|
|
|
|
restored = hermes_main._restore_stashed_changes(["git"], tmp_path, "abc123", prompt_user=False)
|
|
|
|
assert restored is True
|
|
assert calls == [
|
|
(["git", "stash", "apply", "abc123"], {"cwd": tmp_path, "capture_output": True, "text": True}),
|
|
(["git", "stash", "list", "--format=%gd %H"], {"cwd": tmp_path, "capture_output": True, "text": True, "check": True}),
|
|
]
|
|
out = capsys.readouterr().out
|
|
assert "couldn't find the stash entry to drop" in out
|
|
assert "stash was left in place" in out
|
|
assert "Check `git status` first" in out
|
|
assert "git stash list --format='%gd %H %s'" in out
|
|
assert "Look for commit abc123" in out
|
|
|
|
|
|
|
|
def test_restore_stashed_changes_keeps_going_when_drop_fails(monkeypatch, tmp_path, capsys):
|
|
calls = []
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
calls.append((cmd, kwargs))
|
|
if cmd[1:3] == ["stash", "apply"]:
|
|
return SimpleNamespace(stdout="applied\n", stderr="", returncode=0)
|
|
if cmd[1:3] == ["stash", "list"]:
|
|
return SimpleNamespace(stdout="stash@{0} abc123\n", stderr="", returncode=0)
|
|
if cmd[1:3] == ["stash", "drop"]:
|
|
return SimpleNamespace(stdout="", stderr="drop failed\n", returncode=1)
|
|
raise AssertionError(f"unexpected command: {cmd}")
|
|
|
|
monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
|
|
|
|
restored = hermes_main._restore_stashed_changes(["git"], tmp_path, "abc123", prompt_user=False)
|
|
|
|
assert restored is True
|
|
assert calls[2][0] == ["git", "stash", "drop", "stash@{0}"]
|
|
out = capsys.readouterr().out
|
|
assert "couldn't drop the saved stash entry" in out
|
|
assert "drop failed" in out
|
|
assert "Check `git status` first" in out
|
|
assert "git stash list --format='%gd %H %s'" in out
|
|
assert "git stash drop stash@{0}" in out
|
|
|
|
|
|
def test_restore_stashed_changes_exits_cleanly_when_apply_fails(monkeypatch, tmp_path, capsys):
|
|
calls = []
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
calls.append((cmd, kwargs))
|
|
if cmd[1:3] == ["stash", "apply"]:
|
|
return SimpleNamespace(stdout="conflict output\n", stderr="conflict stderr\n", returncode=1)
|
|
raise AssertionError(f"unexpected command: {cmd}")
|
|
|
|
monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
|
|
monkeypatch.setattr("builtins.input", lambda: "y")
|
|
|
|
with pytest.raises(SystemExit, match="1"):
|
|
hermes_main._restore_stashed_changes(["git"], tmp_path, "abc123", prompt_user=True)
|
|
|
|
out = capsys.readouterr().out
|
|
assert "Your changes are still preserved in git stash." in out
|
|
assert "git stash apply abc123" in out
|
|
assert calls == [(["git", "stash", "apply", "abc123"], {"cwd": tmp_path, "capture_output": True, "text": True})]
|
|
|
|
|
|
def test_stash_local_changes_if_needed_raises_when_stash_ref_missing(monkeypatch, tmp_path):
|
|
def fake_run(cmd, **kwargs):
|
|
if cmd[-2:] == ["status", "--porcelain"]:
|
|
return SimpleNamespace(stdout=" M hermes_cli/main.py\n", returncode=0)
|
|
if cmd[1:4] == ["stash", "push", "--include-untracked"]:
|
|
return SimpleNamespace(stdout="Saved working directory\n", returncode=0)
|
|
if cmd[-3:] == ["rev-parse", "--verify", "refs/stash"]:
|
|
raise CalledProcessError(returncode=128, cmd=cmd)
|
|
raise AssertionError(f"unexpected command: {cmd}")
|
|
|
|
monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
|
|
|
|
with pytest.raises(CalledProcessError):
|
|
hermes_main._stash_local_changes_if_needed(["git"], Path(tmp_path))
|