fix(recipe-upgrade): reconcile mirror from TRUE coopcloud upstream, not from the mirror itself

The reconcile that's supposed to make the mirror main == upstream main was fetching origin/main —
but origin is the cc-ci MIRROR, so it synced the mirror to itself (a no-op) and never pulled real
upstream. Fix: fetch coopcloud explicitly (git.coopcloud.tech/coop-cloud/<recipe>, default branch
main OR master) via an 'upstream' remote and force-sync the mirror main + tags from it. Every recipe
has a coopcloud correspondent; none are forked. Also reorder the skill so the reconcile runs BEFORE
the upgrade check, so the check sees the real current recipe. Verified by divergence test (diverged a
mirror, reconcile snapped it back to coopcloud HEAD).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
autonomic-bot
2026-06-05 01:44:45 +00:00
parent 167ce2d881
commit 65a96453fc
2 changed files with 49 additions and 28 deletions

View File

@ -51,31 +51,37 @@ test** to make a PR green.
> 2. **Dirty worktree** is usually just the untracked cc-ci overlay (`compose.ccci.yml`) — don't skip on
> that. `git stash -u` before the checks and `stash pop` after.
On cc-ci, refresh the recipe and see what's available (creds + stash first; each abra call pty-wrapped):
```
ssh cc-ci 'export PATH=/run/current-system/sw/bin:$PATH; R=<recipe>; \
url=$(git -C ~/.abra/recipes/$R config --get remote.origin.url); \
case "$url" in https://git.autonomic.zone/*) \
git -C ~/.abra/recipes/$R remote set-url origin "https://'"$GITEA_USERNAME"':'"$GITEA_PASSWORD"'@${url#https://}";; esac; \
git -C ~/.abra/recipes/$R stash -u >/dev/null 2>&1 || true; \
script -qec "abra recipe fetch $R --force -n" /dev/null; \
git -C ~/.abra/recipes/$R fetch origin main; \
script -qec "abra recipe versions $R -m -n" /dev/null; \
script -qec "abra recipe upgrade $R -m -n" /dev/null; \
git -C ~/.abra/recipes/$R stash pop >/dev/null 2>&1 || true'
```
- Skip a recipe as `SKIPPED — dirty-worktree` **only if it is still dirty with TRACKED changes after the
stash** (real local edits), not for the untracked overlay.
- **Reconcile the mirror first (always, even if up to date).** Run:
**Sync the mirror from TRUE upstream FIRST, then check for upgrades against that** — so the check sees
the real current recipe, not a stale mirror.
- **(a) Reconcile the mirror from coopcloud (always — do this BEFORE the check).** Run:
```
set -a; . /srv/cc-ci/.testenv; set +a
ssh cc-ci "GITEA_USERNAME='$GITEA_USERNAME' GITEA_PASSWORD='$GITEA_PASSWORD' GITEA_URL='$GITEA_URL' bash -s <recipe> --reconcile-only" \
< /srv/cc-ci/.claude/skills/recipe-upgrade/open-recipe-pr.sh
```
This force-syncs the `recipe-maintainers/<recipe>` mirror `main` to be **identical to true upstream
main**, and **closes any open mirror PR whose changes are already in upstream main** (merged
upstream — the mirror just never reflected it). Do this before the up-to-date check so a stale
already-merged PR gets cleaned up even when there's nothing new to upgrade.
This fetches **true upstream from coopcloud** (`git.coopcloud.tech/coop-cloud/<recipe>`, default branch
main OR master — every recipe has a coopcloud correspondent and none are forked) and force-syncs the
`recipe-maintainers/<recipe>` mirror's `main` + tags to be **identical to it**. (The checkout's
`origin` is the mirror, so the reconcile fetches coopcloud *explicitly* via an `upstream` remote — it
does NOT sync the mirror to itself.) It also **closes any open mirror PR already in upstream main**.
- **(b) Refresh the local checkout + check for upgrades** (creds + stash per the box; each abra call
pty-wrapped) — the checkout pulls from the now-coopcloud-synced mirror:
```
ssh cc-ci 'export PATH=/run/current-system/sw/bin:$PATH; R=<recipe>; \
url=$(git -C ~/.abra/recipes/$R config --get remote.origin.url); \
case "$url" in https://git.autonomic.zone/*) \
git -C ~/.abra/recipes/$R remote set-url origin "https://'"$GITEA_USERNAME"':'"$GITEA_PASSWORD"'@${url#https://}";; esac; \
git -C ~/.abra/recipes/$R stash -u >/dev/null 2>&1 || true; \
script -qec "abra recipe fetch $R --force -n" /dev/null; \
git -C ~/.abra/recipes/$R fetch origin main; \
script -qec "abra recipe versions $R -m -n" /dev/null; \
script -qec "abra recipe upgrade $R -m -n" /dev/null; \
git -C ~/.abra/recipes/$R stash pop >/dev/null 2>&1 || true'
```
Skip a recipe as `SKIPPED — dirty-worktree` **only if it is still dirty with TRACKED changes after the
stash** (real local edits), not for the untracked overlay.
- **No upgrades available → stop** (status `SKIPPED — up-to-date`) — after the reconcile above.
- Check `git log HEAD..origin/main` and upstream PRs (`git.coopcloud.tech/coop-cloud/<recipe>/pulls`)
— if someone already started the bump, **re-plan from the tip of `origin/main`**, not from scratch.

View File

@ -11,9 +11,10 @@
# < /srv/cc-ci/.claude/skills/recipe-upgrade/open-recipe-pr.sh
#
# Always (both modes):
# - Force-syncs the mirror's `main` to be IDENTICAL to true upstream `main`
# (origin/main of the abra checkout = git.coopcloud.tech). This guarantees
# every open PR is measured against the real current upstream.
# - Force-syncs the mirror's `main` to be IDENTICAL to true upstream `main`, fetched EXPLICITLY from
# coopcloud (`git.coopcloud.tech/coop-cloud/<recipe>`, default branch main OR master) via an
# `upstream` remote — NOT from the checkout's `origin`, which is the mirror itself. This guarantees
# the mirror really mirrors upstream and every open PR is measured against the real current upstream.
# - Closes any open mirror PR whose changes are ALREADY IN upstream main
# (i.e. merging it is a no-op) — it was merged upstream; the mirror just
# never reflected it.
@ -49,11 +50,24 @@ AUTH=(-u "${GITEA_USERNAME}:${GITEA_PASSWORD}")
[ -d "${RECIPE_DIR}/.git" ] || { echo "ERROR: ${RECIPE_DIR} is not a git repo (run 'abra recipe fetch ${RECIPE}')"; exit 1; }
cd "${RECIPE_DIR}"
# --- The true current upstream main ---
echo "→ Fetching upstream main..."
git fetch --quiet origin main
NEW_MAIN_SHA=$(git rev-parse refs/remotes/origin/main)
# --- The true current upstream main = COOPCLOUD ---
# Every recipe has a coop-cloud correspondent and NONE are intentionally forked, so upstream is always
# git.coopcloud.tech/coop-cloud/<recipe>. NOTE: the abra checkout's `origin` is the cc-ci MIRROR
# (recipe-maintainers/<recipe>), so we must fetch coopcloud EXPLICITLY via an `upstream` remote — using
# `origin` here would sync the mirror to itself (a no-op) and never pick up real upstream changes.
# coopcloud is public, so no creds are needed for this fetch.
UPSTREAM_URL="${UPSTREAM_URL:-https://git.coopcloud.tech/coop-cloud/${RECIPE}.git}"
echo "→ Fetching true upstream ${UPSTREAM_URL} ..."
git remote | grep -qx upstream && git remote set-url upstream "${UPSTREAM_URL}" || git remote add upstream "${UPSTREAM_URL}"
git fetch --quiet --prune --tags upstream 2>/dev/null || {
echo "ERROR: cannot fetch coopcloud upstream for ${RECIPE} (${UPSTREAM_URL}). Every recipe must have a coop-cloud correspondent — fix the URL/mirror."; exit 1; }
# coopcloud's default branch (main OR master) is what the mirror's `main` must mirror exactly.
UP_REF=$(git ls-remote --symref upstream HEAD 2>/dev/null | sed -n 's#^ref:[[:space:]]*refs/heads/\([^[:space:]]*\).*#upstream/\1#p' | head -1)
[ -n "${UP_REF}" ] || for b in main master; do git rev-parse --verify -q "upstream/$b" >/dev/null 2>&1 && { UP_REF="upstream/$b"; break; }; done
[ -n "${UP_REF}" ] || { echo "ERROR: no default branch found on coopcloud ${RECIPE}"; exit 1; }
NEW_MAIN_SHA=$(git rev-parse "${UP_REF}")
MAIN_TREE=$(git rev-parse "${NEW_MAIN_SHA}^{tree}")
echo " upstream ${UP_REF} = ${NEW_MAIN_SHA:0:8}"
# --- Ensure the mirror repo exists ---
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${AUTH[@]}" "${API}/repos/${NAMESPACE}/${RECIPE}")
@ -77,9 +91,10 @@ fi
REMOTE_URL="https://${GITEA_USERNAME}:${PASS_ENC}@${GITEA_URL}/${NAMESPACE}/${RECIPE}.git"
git remote | grep -qx gitea && git remote set-url gitea "${REMOTE_URL}" || git remote add gitea "${REMOTE_URL}"
# --- Mirror main := true upstream main (identical) ---
# --- Mirror main := true upstream main (identical); mirror also gets upstream's published tags ---
echo "→ Force-syncing gitea/main to upstream main (${NEW_MAIN_SHA:0:8})..."
git push --force gitea "${NEW_MAIN_SHA}:refs/heads/main"
git push --quiet gitea "refs/tags/*:refs/tags/*" 2>/dev/null || true # coopcloud published versions
git fetch --quiet gitea '+refs/heads/*:refs/remotes/gitea/*' || true
# --- Preconditions for open mode ---