fix: squash weekly recipe PRs before CI
This commit is contained in:
@@ -264,11 +264,13 @@ ssh cc-ci "GITEA_USERNAME='$GITEA_USERNAME' GITEA_PASSWORD='$GITEA_PASSWORD' GIT
|
|||||||
```
|
```
|
||||||
This (a) re-syncs mirror `main` to true upstream main, (b) **closes any open PR already merged
|
This (a) re-syncs mirror `main` to true upstream main, (b) **closes any open PR already merged
|
||||||
upstream** (merging it would be a no-op), then handles the upgrade PR: **if an open upgrade PR already
|
upstream** (merging it would be a no-op), then handles the upgrade PR: **if an open upgrade PR already
|
||||||
exists for this recipe (branch `upgrade-*`), the new work is pushed onto THAT PR's branch and the PR is
|
exists for this recipe (branch `upgrade-*`), its branch is replaced with one commit directly on the
|
||||||
updated + re-tested** — one evolving upgrade PR per recipe, not a second parallel one. Only if none
|
current upstream tip using `--force-with-lease`, then the PR is updated + re-tested** — one evolving
|
||||||
|
upgrade PR per recipe with one commit, not a second parallel PR. Only if none
|
||||||
exists is a fresh `upgrade-<version>` PR opened. Unrelated open PRs (e.g. a backup fix) are **NOT**
|
exists is a fresh `upgrade-<version>` PR opened. Unrelated open PRs (e.g. a backup fix) are **NOT**
|
||||||
closed or touched — the operator decides on those. The PR's diff is exactly this upgrade against the
|
closed or touched — the operator decides on those. The PR's diff is exactly this upgrade against the
|
||||||
real current upstream main. Capture the `PR_URL`. Export `RECIPE_PR_BODY` first (image-tag table + the
|
real current upstream main. This squash happens **before `!testme`**, so CI tests the one-commit PR
|
||||||
|
head. Capture the `PR_URL`. Export `RECIPE_PR_BODY` first (image-tag table + the
|
||||||
planned operator-action notes) — and **the PR body MUST link the upstream release notes**: for each
|
planned operator-action notes) — and **the PR body MUST link the upstream release notes**: for each
|
||||||
upgraded image/service, pull its release-notes URL from the registry `cc-ci-plan/upstream/<recipe>.md`
|
upgraded image/service, pull its release-notes URL from the registry `cc-ci-plan/upstream/<recipe>.md`
|
||||||
(between the current → new version) and include one explicit line per service in the body, e.g.
|
(between the current → new version) and include one explicit line per service in the body, e.g.
|
||||||
@@ -297,7 +299,9 @@ POST=0 /srv/cc-ci/.claude/skills/recipe-upgrade/testme-on-pr.sh <recipe> <pr-ind
|
|||||||
Read the failing run (linked from the PR / the harness log on cc-ci) and classify:
|
Read the failing run (linked from the PR / the harness log on cc-ci) and classify:
|
||||||
|
|
||||||
**5a. The UPGRADE itself is broken** (real regression: bad tag, missing migration, config gap) — fix
|
**5a. The UPGRADE itself is broken** (real regression: bad tag, missing migration, config gap) — fix
|
||||||
it on the recipe branch, push, and re-run `!testme` (within the 3-run budget). The recipe PR is only
|
it on the local recipe branch, then re-run `open-recipe-pr.sh` from step 3 to squash the corrected
|
||||||
|
tree into one commit on the PR branch **before** re-running `!testme` (within the 3-run budget).
|
||||||
|
Do not push a fix commit directly to the PR branch. The recipe PR is only
|
||||||
"working" once `!testme` is GREEN. If still red after 3 runs, leave the PR open (its red `!testme`
|
"working" once `!testme` is GREEN. If still red after 3 runs, leave the PR open (its red `!testme`
|
||||||
results stand as the evidence) and report `FAILED — upgrade not green after 3 !testme runs`.
|
results stand as the evidence) and report `FAILED — upgrade not green after 3 !testme runs`.
|
||||||
|
|
||||||
@@ -359,8 +363,8 @@ Always state explicitly that **nothing was merged** — the PR(s) await operator
|
|||||||
— never touch the gate. (Interim safeguard while the upgrader runs on the cc-ci host; see `IDEAS.md`.)
|
— never touch the gate. (Interim safeguard while the upgrader runs on the cc-ci host; see `IDEAS.md`.)
|
||||||
- **Mirror reflects reality.** Each run force-syncs the `recipe-maintainers/<recipe>` mirror `main` to
|
- **Mirror reflects reality.** Each run force-syncs the `recipe-maintainers/<recipe>` mirror `main` to
|
||||||
true upstream main and closes open PRs already merged upstream. It does NOT close superseded/unrelated
|
true upstream main and closes open PRs already merged upstream. It does NOT close superseded/unrelated
|
||||||
open PRs. **An existing open upgrade PR is extended (new work pushed onto its branch + re-tested)
|
open PRs. **An existing open upgrade PR is rewritten with one commit on the current upstream tip
|
||||||
rather than spawning a second upgrade PR** — one evolving upgrade PR per recipe.
|
using `--force-with-lease`, then re-tested** — one evolving upgrade PR per recipe.
|
||||||
- **Prefer a recipe-only PR.** Only open a cc-ci test PR (under `--with-tests`) when the upgrade is
|
- **Prefer a recipe-only PR.** Only open a cc-ci test PR (under `--with-tests`) when the upgrade is
|
||||||
correct but a test is genuinely stale — never to paper over a real upgrade regression.
|
correct but a test is genuinely stale — never to paper over a real upgrade regression.
|
||||||
- **Never weaken a test**; **bounded** changes (the upgrade + minimal test update, not rewrites).
|
- **Never weaken a test**; **bounded** changes (the upgrade + minimal test update, not rewrites).
|
||||||
|
|||||||
@@ -20,11 +20,9 @@
|
|||||||
# never reflected it.
|
# never reflected it.
|
||||||
#
|
#
|
||||||
# Default mode (open a PR — preconditions: HEAD has the upgrade commit(s)):
|
# Default mode (open a PR — preconditions: HEAD has the upgrade commit(s)):
|
||||||
# - If an open UPGRADE PR already exists for this recipe (branch 'upgrade-*'), the new work is added
|
# - Publish the final recipe tree as ONE commit directly on current upstream main before !testme.
|
||||||
# as a commit ON TOP of THAT PR's branch (a fast-forward — NO force-push) and the PR is updated +
|
# If an open upgrade PR exists, replace its branch with this single commit using
|
||||||
# re-tested — ONE evolving upgrade PR per recipe, NOT a second parallel PR. Otherwise a fresh
|
# --force-with-lease, then re-test the same PR. Otherwise open a fresh upgrade PR.
|
||||||
# 'upgrade-<version>' branch + PR is opened. (The only force-push here is the mirror-main sync
|
|
||||||
# above, which intentionally mirrors upstream main; PR branches are never force-pushed.)
|
|
||||||
# - Unrelated open PRs (e.g. a backup fix) are NEVER closed or touched — left for the operator.
|
# - Unrelated open PRs (e.g. a backup fix) are NEVER closed or touched — left for the operator.
|
||||||
# - Only PRs already merged into upstream main are closed (no-op merges) — see above.
|
# - Only PRs already merged into upstream main are closed (no-op merges) — see above.
|
||||||
#
|
#
|
||||||
@@ -165,9 +163,8 @@ if [ "${MODE}" = "--reconcile-only" ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- DEFAULT: if an open upgrade PR exists, push the new work onto ITS branch and re-test it, so
|
# --- DEFAULT: publish one commit with the final tree on current upstream main. This collapses
|
||||||
# there's ONE evolving upgrade PR per recipe rather than parallel ones. Only if none exists do we
|
# old local commits and merge commits while preserving the exact content to be tested. ---
|
||||||
# open a fresh upgrade-<version> PR. Unrelated open PRs are never touched. ---
|
|
||||||
if [ -n "${EXTEND_PR}" ]; then
|
if [ -n "${EXTEND_PR}" ]; then
|
||||||
BRANCH="${EXTEND_BRANCH}"
|
BRANCH="${EXTEND_BRANCH}"
|
||||||
echo "→ Extending existing open upgrade PR #${EXTEND_PR} (branch ${BRANCH}) with the new work — it will be re-tested; NOT opening a separate PR."
|
echo "→ Extending existing open upgrade PR #${EXTEND_PR} (branch ${BRANCH}) with the new work — it will be re-tested; NOT opening a separate PR."
|
||||||
@@ -178,39 +175,38 @@ else
|
|||||||
BRANCH="upgrade-$(git rev-parse --short HEAD)"
|
BRANCH="upgrade-$(git rev-parse --short HEAD)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Land the work on the PR branch WITHOUT force-pushing. If the branch already exists (extend case, or
|
# Do not create an empty PR when the proposed tree equals current upstream main.
|
||||||
# a stale same-named branch), add a commit ON TOP of its tip — a fast-forward, so the PR's history is
|
UPSTREAM_TREE=$(git rev-parse "${NEW_MAIN_SHA}^{tree}")
|
||||||
# preserved and it re-tests. Otherwise push a fresh branch.
|
WORK_TREE=$(git rev-parse 'HEAD^{tree}')
|
||||||
|
[ "${WORK_TREE}" != "${UPSTREAM_TREE}" ] || {
|
||||||
|
echo "ERROR: proposed recipe tree is identical to upstream main; nothing to PR."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# The PR branch is a single commit on the current upstream tip. Reusing an existing PR requires a
|
||||||
|
# guarded rewrite; the lease rejects concurrent updates instead of overwriting another writer.
|
||||||
|
SQUASHED=$(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 "${WORK_TREE}" -p "${NEW_MAIN_SHA}" -m "${LATEST_MSG}")
|
||||||
if git rev-parse --verify --quiet "refs/remotes/gitea/${BRANCH}" >/dev/null; then
|
if git rev-parse --verify --quiet "refs/remotes/gitea/${BRANCH}" >/dev/null; then
|
||||||
EXIST_TIP=$(git rev-parse "refs/remotes/gitea/${BRANCH}")
|
EXIST_TIP=$(git rev-parse "refs/remotes/gitea/${BRANCH}")
|
||||||
if [ "$(git rev-parse 'HEAD^{tree}')" = "$(git rev-parse "${EXIST_TIP}^{tree}")" ]; then
|
if [ "$(git rev-parse "${EXIST_TIP}^{tree}")" = "${WORK_TREE}" ] &&
|
||||||
echo "→ '${BRANCH}' already has this exact tree — nothing new to push (will still re-test)."
|
[ "$(git rev-parse "${EXIST_TIP}^" 2>/dev/null || true)" = "${NEW_MAIN_SHA}" ]; then
|
||||||
|
echo "→ '${BRANCH}' already has this one-commit diff — nothing new to push (will still re-test)."
|
||||||
else
|
else
|
||||||
# Parent the new commit on the branch tip AND (when the branch predates it) on upstream main, so
|
echo "→ Replacing '${BRANCH}' with one commit on upstream ${NEW_MAIN_SHA:0:8} (force-with-lease)..."
|
||||||
# the recorded HISTORY matches the tree we are pushing. Without the second parent the merge-base
|
git push --force-with-lease="refs/heads/${BRANCH}:${EXIST_TIP}" gitea \
|
||||||
# stays stale: git would later treat upstream's post-branch changes as "removed by this PR" and a
|
"${SQUASHED}:refs/heads/${BRANCH}"
|
||||||
# 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}" "${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
|
fi
|
||||||
else
|
else
|
||||||
echo "→ Pushing new branch '${BRANCH}'..."
|
echo "→ Pushing one-commit branch '${BRANCH}' on upstream ${NEW_MAIN_SHA:0:8}..."
|
||||||
git push gitea "HEAD:refs/heads/${BRANCH}"
|
git push gitea "${SQUASHED}:refs/heads/${BRANCH}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PR_BODY="${RECIPE_PR_BODY:-$(printf 'Recipe upgrade.\n\nCommits on top of upstream main:\n\n%s\n' "$(git log origin/main..HEAD --pretty='- %h %s')")}"
|
PR_BODY="${RECIPE_PR_BODY:-$(printf 'Recipe upgrade as one commit on upstream %s.\n\n%s\n' "${NEW_MAIN_SHA:0:8}" "$(git diff --stat "${NEW_MAIN_SHA}" "${SQUASHED}")")}"
|
||||||
PR_BODY="${PR_BODY}
|
PR_BODY="${PR_BODY}
|
||||||
|
|
||||||
Tested green on the cc-ci recipe CI server (full suite, cold, against this PR head). NOT merged — for operator review.
|
CI verification is run against this one-commit PR head. NOT merged — for operator review.
|
||||||
|
|
||||||
cc @trav @notplants"
|
cc @trav @notplants"
|
||||||
|
|
||||||
|
|||||||
@@ -197,7 +197,8 @@ description `"Upgrade <recipe> on cc-ci"`) with a prompt like:
|
|||||||
|
|
||||||
> Run the `/recipe-upgrade <recipe>` skill end-to-end — **DEFAULT mode, do NOT pass `--with-tests`**
|
> Run the `/recipe-upgrade <recipe>` skill end-to-end — **DEFAULT mode, do NOT pass `--with-tests`**
|
||||||
> (`/srv/cc-ci/.claude/skills/recipe-upgrade/SKILL.md`): plan, implement, **directly deploy + inspect
|
> (`/srv/cc-ci/.claude/skills/recipe-upgrade/SKILL.md`): plan, implement, **directly deploy + inspect
|
||||||
> the WIP on cc-ci for live feedback (step 2b, `--chaos`, tear it down after)**, open a recipe PR, and
|
> the WIP on cc-ci for live feedback (step 2b, `--chaos`, tear it down after)**, open a recipe PR as
|
||||||
|
> **one commit based on current upstream main**, and
|
||||||
> verify it by posting `!testme` on the PR (results visible in the PR; iterate ≤3×). Use the **existing
|
> verify it by posting `!testme` on the PR (results visible in the PR; iterate ≤3×). Use the **existing
|
||||||
> tests** — if a test fails because it is genuinely stale, **leave an explanatory comment on the PR**
|
> tests** — if a test fails because it is genuinely stale, **leave an explanatory comment on the PR**
|
||||||
> for the operator and do NOT modify any test. Drive cc-ci over `ssh cc-ci`. Do NOT prompt. Do NOT push
|
> for the operator and do NOT modify any test. Drive cc-ci over `ssh cc-ci`. Do NOT prompt. Do NOT push
|
||||||
|
|||||||
Reference in New Issue
Block a user