diff --git a/scripts/install.sh b/scripts/install.sh index 166d984fa..109b819a9 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -752,7 +752,12 @@ clone_repo() { if [ "$restore_now" = "yes" ]; then log_info "Restoring local changes..." if git stash apply "$autostash_ref"; then - git stash drop "$autostash_ref" >/dev/null + # Apply succeeded; dropping the captured stash ref can still fail + # after an interrupted prior install (stale hash / not a stash ref). + # Never abort the installer here — user changes are already applied (#14735). + if ! git stash drop "$autostash_ref" >/dev/null 2>&1; then + log_warn "Could not drop installer autostash (already removed or not a stash reference). Continuing. (#14735)" + fi log_warn "Local changes were restored on top of the updated codebase." log_warn "Review git diff / git status if Hermes behaves unexpectedly." else diff --git a/tests/test_install_sh_issue_14735.py b/tests/test_install_sh_issue_14735.py new file mode 100644 index 000000000..77d9d716e --- /dev/null +++ b/tests/test_install_sh_issue_14735.py @@ -0,0 +1,102 @@ +"""Regression tests for scripts/install.sh autostash cleanup (issue #14735). + +The installer uses ``set -e``. A stale ``git stash drop `` after a prior +interrupted run must not abort the whole script. These tests validate the +guard pattern and that install.sh still contains it. +""" + +from __future__ import annotations + +import re +import subprocess +import textwrap +from pathlib import Path + + +def _repo_root() -> Path: + return Path(__file__).resolve().parent.parent + + +def test_install_sh_stash_drop_after_apply_is_non_fatal() -> None: + """Reproduce #14735: drop of a non-stash ref must not exit under set -e.""" + script = textwrap.dedent( + r""" + set -euo pipefail + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + cd "$tmp" + git init -q + git config user.email "test@example.com" + git config user.name "Hermes Install Test" + echo v1 >tracked.txt + git add tracked.txt + git commit -q -m "base" + echo dirty >>tracked.txt + git stash push -u -q -m "hermes-install-autostash-test" + stash_ref=$(git rev-parse refs/stash) + stale_ref=$(git rev-parse HEAD) + git stash apply "$stash_ref" + # Same pattern as scripts/install.sh after fix: drop must not abort. + if ! git stash drop "$stale_ref" >/dev/null 2>&1; then + echo "drop_failed_as_expected" + fi + echo "survived" + """ + ) + proc = subprocess.run( + ["bash", "-c", script], + check=False, + capture_output=True, + text=True, + timeout=60, + ) + assert proc.returncode == 0, (proc.stdout, proc.stderr, proc.returncode) + assert "survived" in proc.stdout + assert "drop_failed_as_expected" in proc.stdout + + +def test_install_sh_documents_guarded_autostash_drop() -> None: + """install.sh must keep the non-fatal autostash drop guard (issue #14735).""" + install_sh = _repo_root() / "scripts" / "install.sh" + text = install_sh.read_text(encoding="utf-8") + # After successful apply: guarded drop + user-facing warning. + assert re.search( + r'git stash apply "\$autostash_ref"[\s\S]{0,800}?' + r'if ! git stash drop "\$autostash_ref"\s*>\s*/dev/null\s*2>&1', + text, + ), "expected guarded 'git stash drop' after successful apply" + assert "Could not drop installer autostash" in text + assert "#14735" in text + + +def test_stash_pop_round_trip_succeeds() -> None: + """Sanity: stash / pop succeeds in a fresh repo (install path uses apply+drop).""" + script = textwrap.dedent( + r""" + set -euo pipefail + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + cd "$tmp" + git init -q + git config user.email "test@example.com" + git config user.name "Hermes Install Test" + echo v1 >f + git add f && git commit -q -m one + echo v2 >>f + git stash push -u -q -m stash1 + if ! git stash pop -q; then + echo "stash_pop_failed" >&2 + exit 2 + fi + echo ok + """ + ) + proc = subprocess.run( + ["bash", "-c", script], + check=False, + capture_output=True, + text=True, + timeout=60, + ) + assert proc.returncode == 0, (proc.stdout, proc.stderr) + assert "ok" in proc.stdout