fix(install): a diverged checkout cannot keep its old tree

`stage_repository` treated a failed `pull --ff-only` as "keep local state"
and carried on. Every later stage reads files only the new tree has (`pm/`),
so an install whose checkout could not fast-forward failed further down with
`ModuleNotFoundError: No module named 'pm'` instead of updating.

A release cut off the main line diverges for every user who installed from it:
v2026.5.29.2 is not an ancestor of main, so its checkout can never fast-forward
to a later main. Match the remote the way `hermes update` already does, after
parking the old tip behind refs/hermes-install-backup/<stamp>-<sha> and
stashing any local work so neither is destroyed silently.

Verified against a real diverged repository: before, the checkout stayed on
the old line (HEAD=6a2e091, remote=43eea63) with no backup; after, HEAD equals
the new main, the backup ref and an autostash exist, and both are named in the
installer's output.
This commit is contained in:
ethernet
2026-09-18 02:57:30 -04:00
parent 626b3a80a0
commit 5cb0a5073b

View File

@@ -288,7 +288,27 @@ stage_repository() {
log "updating $INSTALL_DIR"
git -C "$INSTALL_DIR" fetch origin "$BRANCH" || fail "git fetch failed"
git -C "$INSTALL_DIR" checkout "$BRANCH" || fail "git checkout failed"
git -C "$INSTALL_DIR" pull --ff-only origin "$BRANCH" || log "not fast-forwardable; keeping local state"
if ! git -C "$INSTALL_DIR" merge --ff-only "origin/$BRANCH"; then
# A release cut off the main line, a force-pushed remote, or the
# user's own commits cannot fast-forward. Every stage below reads
# files only the new tree has (pm/), so an install left on the old
# tree cannot finish -- match the remote the way `hermes update`
# does, after parking the old tip and any local work.
local stamp prior rescue
stamp="$(date -u +%Y%m%d-%H%M%S)"
prior="$(git -C "$INSTALL_DIR" rev-parse --short HEAD 2>/dev/null || echo unknown)"
rescue="refs/hermes-install-backup/$stamp-$prior"
if [ -n "$(git -C "$INSTALL_DIR" status --porcelain)" ]; then
git -C "$INSTALL_DIR" stash push --include-untracked -m "hermes-install-autostash-$stamp" \
&& log "local changes stashed as hermes-install-autostash-$stamp" \
|| log "could not stash local changes; they are overwritten below"
fi
git -C "$INSTALL_DIR" update-ref "$rescue" HEAD 2>/dev/null \
&& log "previous HEAD backed up to $rescue" \
|| log "could not back up the previous HEAD"
git -C "$INSTALL_DIR" reset --hard "origin/$BRANCH" || fail "git reset failed"
log "not fast-forwardable; reset to origin/$BRANCH"
fi
else
log "cloning $REPO_URL ($BRANCH) into $INSTALL_DIR"
mkdir -p "$(dirname "$INSTALL_DIR")"