recipe-upgrade: stop upgrade-PR branches drifting behind upstream

The extend path grafts HEAD^{tree} WHOLESALE onto the existing upgrade-* branch
(commit-tree -p <branch tip>). Reconcile force-syncs the MIRROR's main to upstream
but never brought the branch — or the local checkout — forward, so each week the
PR base drifted further back and upstream changes made since the branch was cut
were silently absent from the pushed tree. CI then verified a tree that would
never deploy.

Found on gitea PR #5 (2026-08-10): base 0ab323d predated upstream's
'BREAKING CHANGE: remove forgejo' (37ebd22), so the 1.27.1 bump fixing
CVE-2026-60004 + CVE-2026-59774 was !testme-GREEN against a forgejo-bearing tree.

Two changes:
1. Before pushing, if the local work does not contain the freshly-synced upstream
   main, merge upstream in — and FAIL LOUDLY (exit 1, naming the checkout) if that
   cannot auto-merge, rather than pushing a tree that omits upstream changes.
2. The extend commit now also parents on upstream main when the branch predated it,
   so the recorded history matches the pushed tree. Without it the merge-base stays
   stale and a later merge can REVERT upstream's changes. Still no force-push.

Verified against the real gitea drift: detection fires, merge is clean, resulting
tree keeps forgejo removed AND the 1.27.1 pin, history contains upstream.
This commit is contained in:
autonomic-bot
2026-08-10 16:24:37 +00:00
parent 8f85a238cc
commit 02dbd71b49
@@ -102,6 +102,27 @@ if [ "${MODE}" != "--reconcile-only" ]; then
DIVERGED=$(git log --oneline origin/main..HEAD 2>/dev/null || true)
[ -n "${DIVERGED}" ] || { echo "ERROR: HEAD has no commits beyond origin/main. Nothing to PR."; exit 1; }
LATEST_MSG=$(git log -1 --pretty=%s HEAD)
# --- Keep the LOCAL work current with the freshly-synced upstream main (anti-drift) ---
# The push path below grafts `HEAD^{tree}` WHOLESALE onto the PR branch. If this checkout is not
# based on the upstream main we just synced, every upstream change made since the branch was cut
# is silently ABSENT from the pushed tree — the PR (and the CI that verifies it) then describes a
# tree that will never deploy. Observed on gitea PR #5 (2026-08-10): its base predated upstream's
# "BREAKING CHANGE: remove forgejo", so `!testme` verified a forgejo-bearing tree while main had
# dropped it. Merge upstream in FIRST, and fail loudly rather than paper over a conflict.
if ! git merge-base --is-ancestor "${NEW_MAIN_SHA}" HEAD; then
echo "→ Local work predates upstream main (${NEW_MAIN_SHA:0:8}) — merging upstream in first..."
if ! GIT_AUTHOR_NAME="${GITEA_USERNAME}" GIT_AUTHOR_EMAIL="${GITEA_USERNAME}@git.autonomic.zone" \
GIT_COMMITTER_NAME="${GITEA_USERNAME}" GIT_COMMITTER_EMAIL="${GITEA_USERNAME}@git.autonomic.zone" \
git merge --no-edit "${NEW_MAIN_SHA}" >/dev/null 2>&1; then
git merge --abort 2>/dev/null || true
echo "ERROR: cannot auto-merge upstream main (${NEW_MAIN_SHA:0:8}) into the local ${RECIPE} work."
echo " Upstream changed files this upgrade also touches. Resolve by hand in"
echo " ${RECIPE_DIR}, then re-run. Refusing to push a tree that omits upstream changes."
exit 1
fi
echo " ✓ upstream merged into the local work"
fi
fi
# --- Reconcile open PRs against the freshly-synced upstream main ---
@@ -165,9 +186,19 @@ if git rev-parse --verify --quiet "refs/remotes/gitea/${BRANCH}" >/dev/null; the
if [ "$(git rev-parse 'HEAD^{tree}')" = "$(git rev-parse "${EXIST_TIP}^{tree}")" ]; then
echo "→ '${BRANCH}' already has this exact tree — nothing new to push (will still re-test)."
else
# Parent the new commit on the branch tip AND (when the branch predates it) on upstream main, so
# the recorded HISTORY matches the tree we are pushing. Without the second parent the merge-base
# stays stale: git would later treat upstream's post-branch changes as "removed by this PR" and a
# merge could revert them (the gitea #5 / forgejo-removal drift, 2026-08-10). No force-push: this
# is still a fast-forward from the branch tip.
EXTRA_PARENT=()
if ! git merge-base --is-ancestor "${NEW_MAIN_SHA}" "${EXIST_TIP}"; then
EXTRA_PARENT=(-p "${NEW_MAIN_SHA}")
echo " (also parenting on upstream main ${NEW_MAIN_SHA:0:8} — branch predated it)"
fi
ONTOP=$(GIT_AUTHOR_NAME="${GITEA_USERNAME}" GIT_AUTHOR_EMAIL="${GITEA_USERNAME}@git.autonomic.zone" \
GIT_COMMITTER_NAME="${GITEA_USERNAME}" GIT_COMMITTER_EMAIL="${GITEA_USERNAME}@git.autonomic.zone" \
git commit-tree "$(git rev-parse 'HEAD^{tree}')" -p "${EXIST_TIP}" -m "${LATEST_MSG}")
git commit-tree "$(git rev-parse 'HEAD^{tree}')" -p "${EXIST_TIP}" "${EXTRA_PARENT[@]}" -m "${LATEST_MSG}")
echo "→ Adding the new work on top of '${BRANCH}' (fast-forward, no force-push)..."
git push gitea "${ONTOP}:refs/heads/${BRANCH}"
fi