fix(update-check): implement --apply to actually run the upgrade

Fixes #1353. `gstack-update-check --apply` previously exited 0 and
emitted `UPGRADE_AVAILABLE <old> <new>` without touching the working
tree. The flag was undocumented and the script's only flag handler was
`--force`; unknown flags fell through to the report-only path, so
`--apply` was indistinguishable from no flag.

Add an `--apply` branch that, when an upgrade is detected, runs the
minimal headless variant of `/gstack-upgrade` Step 4:

  git -C "$GSTACK_DIR" fetch origin main
  git -C "$GSTACK_DIR" reset --hard origin/main
  ( cd "$GSTACK_DIR" && ./setup )

On success the script emits `UPGRADED <old> <new>` (distinct from the
report-only `UPGRADE_AVAILABLE` token) and caches that line so a
subsequent `--apply` re-run, after the VERSION already moved, prints
the new `UP_TO_DATE` and exits cleanly.

Scope is intentionally minimal:

- Git installs only. Vendored installs need `mv` + clone-into-place
  and the local-vendored sync that `/gstack-upgrade` Step 4.5 handles
  — surface an explicit error pointing at `/gstack-upgrade` instead.
- Snooze is ignored on `--apply`. The caller asked for the upgrade
  explicitly; honoring snooze would mean a CI gate appears to succeed
  while not actually upgrading.
- Each git step's failure prints to stderr and exits non-zero so a
  scripted caller can react. A `git reset` that succeeded followed by
  a `./setup` that failed leaves the working tree at the new version
  and surfaces an explicit "re-run ./setup manually" hint.

Refactor flag parsing from the single-arg `if "${1:-}" = "--force"`
shape to a `for arg in "$@"; case` loop so the two flags compose
(e.g. `--force --apply` is well-defined: both fire) and an unknown
flag still falls through silently to preserve the existing zero-noise
contract of the periodic-check path.

Header doc grows three lines covering the new flag and the new output
token so the file is self-describing.
This commit is contained in:
genisis0x 2026-05-13 14:33:22 +05:30
parent dc6252d1df
commit af7ab9812e
1 changed files with 67 additions and 5 deletions

View File

@ -4,8 +4,18 @@
# Output (one line, or nothing):
# JUST_UPGRADED <old> <new> — marker found from recent upgrade
# UPGRADE_AVAILABLE <old> <new> — remote VERSION differs from local
# UPGRADED <old> <new> — `--apply` ran a successful upgrade
# (nothing) — up to date, snoozed, disabled, or check skipped
#
# Flags:
# --force Bust cache and snooze (used by standalone /gstack-upgrade).
# --apply When an upgrade is available, actually run it: `git fetch` +
# `git reset --hard origin/main` + `./setup` inside `$GSTACK_DIR`.
# For the full ceremony (stash, vendored-install handling,
# local-vendored copy sync, migrations), use /gstack-upgrade.
# `--apply` is the minimal headless variant for CI / scripted
# update gates.
#
# Env overrides (for testing):
# GSTACK_DIR — override auto-detected gstack root
# GSTACK_REMOTE_URL — override remote VERSION URL
@ -20,11 +30,25 @@ SNOOZE_FILE="$STATE_DIR/update-snoozed"
VERSION_FILE="$GSTACK_DIR/VERSION"
REMOTE_URL="${GSTACK_REMOTE_URL:-https://raw.githubusercontent.com/garrytan/gstack/main/VERSION}"
# ─── Force flag (busts cache + snooze for standalone /gstack-upgrade) ──
if [ "${1:-}" = "--force" ]; then
rm -f "$CACHE_FILE"
rm -f "$SNOOZE_FILE"
fi
# ─── Parse flags ──────────────────────────────────────────────
APPLY=0
for arg in "$@"; do
case "$arg" in
--force)
rm -f "$CACHE_FILE"
rm -f "$SNOOZE_FILE"
;;
--apply)
APPLY=1
# Force a fresh check so a stale cache doesn't suppress the upgrade.
rm -f "$CACHE_FILE"
;;
*)
# Unknown flag — fall through silently to preserve the historical
# zero-noise contract of the periodic-check path.
;;
esac
done
# ─── Step 0: Check if updates are disabled ────────────────────
_UC=$("$GSTACK_DIR/bin/gstack-config" get update_check 2>/dev/null || true)
@ -197,6 +221,44 @@ fi
# Versions differ — upgrade available
echo "UPGRADE_AVAILABLE $LOCAL $REMOTE" > "$CACHE_FILE"
# ─── Apply branch ────────────────────────────────────────────
# `--apply` is the headless / scripted variant of /gstack-upgrade Step 4
# for git installs. It does NOT cover vendored installs, local-vendored
# copy sync, or the migrations pipeline — point users at /gstack-upgrade
# for any of those. Snooze is intentionally ignored on --apply: the
# caller asked for the upgrade explicitly.
if [ "$APPLY" = "1" ]; then
if [ ! -d "$GSTACK_DIR/.git" ]; then
echo "ERROR: --apply requires a git install at $GSTACK_DIR" >&2
echo " Run /gstack-upgrade for vendored installs." >&2
exit 1
fi
if ! git -C "$GSTACK_DIR" fetch origin main >/dev/null 2>&1; then
echo "ERROR: --apply failed to fetch origin/main from $GSTACK_DIR" >&2
exit 1
fi
if ! git -C "$GSTACK_DIR" reset --hard origin/main >/dev/null 2>&1; then
echo "ERROR: --apply failed to reset $GSTACK_DIR to origin/main" >&2
exit 1
fi
if [ -x "$GSTACK_DIR/setup" ]; then
if ! ( cd "$GSTACK_DIR" && ./setup >/dev/null 2>&1 ); then
echo "ERROR: --apply ran git reset but ./setup failed in $GSTACK_DIR" >&2
echo " Working tree is at the new version; re-run ./setup manually." >&2
exit 1
fi
fi
# Confirm the version moved.
NEW_LOCAL=""
if [ -f "$VERSION_FILE" ]; then
NEW_LOCAL="$(cat "$VERSION_FILE" 2>/dev/null | tr -d '[:space:]')"
fi
echo "UPGRADED $LOCAL ${NEW_LOCAL:-$REMOTE}" > "$CACHE_FILE"
echo "UPGRADED $LOCAL ${NEW_LOCAL:-$REMOTE}"
exit 0
fi
if check_snooze "$REMOTE"; then
exit 0 # snoozed — stay quiet
fi