fix(update): warn and prompt before restoring autostash
Add a restore prompt for interactive updates, keep the stash when the user declines, and print a post-restore warning that local changes were reapplied on top of updated code.
This commit is contained in:
@@ -1962,7 +1962,25 @@ def _stash_local_changes_if_needed(git_cmd: list[str], cwd: Path) -> Optional[st
|
||||
|
||||
|
||||
|
||||
def _restore_stashed_changes(git_cmd: list[str], cwd: Path, stash_ref: str) -> None:
|
||||
def _restore_stashed_changes(
|
||||
git_cmd: list[str],
|
||||
cwd: Path,
|
||||
stash_ref: str,
|
||||
prompt_user: bool = False,
|
||||
) -> bool:
|
||||
if prompt_user:
|
||||
print()
|
||||
print("⚠ Local changes were stashed before updating.")
|
||||
print(" Restoring them may reapply local customizations onto the updated codebase.")
|
||||
print(" Review the result afterward if Hermes behaves unexpectedly.")
|
||||
print("Restore local changes now? [Y/n]")
|
||||
response = input().strip().lower()
|
||||
if response not in ("", "y", "yes"):
|
||||
print("Skipped restoring local changes.")
|
||||
print("Your changes are still preserved in git stash.")
|
||||
print(f"Restore manually with: git stash apply {stash_ref}")
|
||||
return False
|
||||
|
||||
print("→ Restoring local changes...")
|
||||
restore = subprocess.run(
|
||||
git_cmd + ["stash", "apply", stash_ref],
|
||||
@@ -1981,6 +1999,9 @@ def _restore_stashed_changes(git_cmd: list[str], cwd: Path, stash_ref: str) -> N
|
||||
sys.exit(1)
|
||||
|
||||
subprocess.run(git_cmd + ["stash", "drop", stash_ref], cwd=cwd, check=True)
|
||||
print("⚠ Local changes were restored on top of the updated codebase.")
|
||||
print(" Review `git diff` / `git status` if Hermes behaves unexpectedly.")
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -2053,13 +2074,19 @@ def cmd_update(args):
|
||||
print(f"→ Found {commit_count} new commit(s)")
|
||||
|
||||
auto_stash_ref = _stash_local_changes_if_needed(git_cmd, PROJECT_ROOT)
|
||||
prompt_for_restore = auto_stash_ref is not None and sys.stdin.isatty() and sys.stdout.isatty()
|
||||
|
||||
print("→ Pulling updates...")
|
||||
try:
|
||||
subprocess.run(git_cmd + ["pull", "origin", branch], cwd=PROJECT_ROOT, check=True)
|
||||
finally:
|
||||
if auto_stash_ref is not None:
|
||||
_restore_stashed_changes(git_cmd, PROJECT_ROOT, auto_stash_ref)
|
||||
_restore_stashed_changes(
|
||||
git_cmd,
|
||||
PROJECT_ROOT,
|
||||
auto_stash_ref,
|
||||
prompt_user=prompt_for_restore,
|
||||
)
|
||||
|
||||
# Reinstall Python dependencies (prefer uv for speed, fall back to pip)
|
||||
print("→ Updating Python dependencies...")
|
||||
|
||||
@@ -577,13 +577,34 @@ clone_repo() {
|
||||
git pull origin "$BRANCH"
|
||||
|
||||
if [ -n "$autostash_ref" ]; then
|
||||
log_info "Restoring local changes..."
|
||||
if git stash apply "$autostash_ref"; then
|
||||
git stash drop "$autostash_ref" >/dev/null
|
||||
local restore_now="yes"
|
||||
if [ -t 0 ] && [ -t 1 ]; then
|
||||
echo
|
||||
log_warn "Local changes were stashed before updating."
|
||||
log_warn "Restoring them may reapply local customizations onto the updated codebase."
|
||||
printf "Restore local changes now? [Y/n] "
|
||||
read -r restore_answer
|
||||
case "$restore_answer" in
|
||||
""|y|Y|yes|YES|Yes) restore_now="yes" ;;
|
||||
*) restore_now="no" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ "$restore_now" = "yes" ]; then
|
||||
log_info "Restoring local changes..."
|
||||
if git stash apply "$autostash_ref"; then
|
||||
git stash drop "$autostash_ref" >/dev/null
|
||||
log_warn "Local changes were restored on top of the updated codebase."
|
||||
log_warn "Review git diff / git status if Hermes behaves unexpectedly."
|
||||
else
|
||||
log_error "Update succeeded, but restoring local changes failed. Your changes are still preserved in git stash."
|
||||
log_info "Resolve manually with: git stash apply $autostash_ref"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_error "Update succeeded, but restoring local changes failed. Your changes are still preserved in git stash."
|
||||
log_info "Resolve manually with: git stash apply $autostash_ref"
|
||||
exit 1
|
||||
log_info "Skipped restoring local changes."
|
||||
log_info "Your changes are still preserved in git stash."
|
||||
log_info "Restore manually with: git stash apply $autostash_ref"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
|
||||
@@ -46,7 +46,53 @@ def test_stash_local_changes_if_needed_returns_specific_stash_commit(monkeypatch
|
||||
assert calls[2][0][-3:] == ["rev-parse", "--verify", "refs/stash"]
|
||||
|
||||
|
||||
def test_restore_stashed_changes_applies_specific_stash_and_drops_it(monkeypatch, tmp_path, capsys):
|
||||
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", "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", "drop", "abc123"]
|
||||
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):
|
||||
@@ -59,11 +105,11 @@ def test_restore_stashed_changes_applies_specific_stash_and_drops_it(monkeypatch
|
||||
|
||||
monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
|
||||
|
||||
hermes_main._restore_stashed_changes(["git"], tmp_path, "abc123")
|
||||
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", "drop", "abc123"]
|
||||
assert "Restoring local changes" in capsys.readouterr().out
|
||||
assert "Restore local changes now?" not in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_restore_stashed_changes_exits_cleanly_when_apply_fails(monkeypatch, tmp_path, capsys):
|
||||
@@ -76,9 +122,10 @@ def test_restore_stashed_changes_exits_cleanly_when_apply_fails(monkeypatch, tmp
|
||||
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")
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user