Make the CI gate execute the checks it reports on (closes #119)
All checks were successful
check / check (push) Successful in 3m0s

The check workflow ran `script/cibuild`, a plain `docker build .`. With a
warm layer cache the lint and builder stages replayed instead of running,
so a commit could report "Successful in 4s" without being formatted,
linted, tested, or built. Squash-merging an already-built branch onto
`next` hits exactly that path, so no merge was actually validated.

The workflow now writes `.ci-fingerprint` into the build context: the
hash of the last commit that touched the context. Any commit that
changes code gets a new value, invalidates the `COPY . .` layer of both
check stages, and really runs `make fmt-check`, `make lint`, `make test`
and `make build`. A docs-only commit leaves it unchanged and still
replays from cache in seconds, as `.dockerignore` already intends. The
module download layer sits above `COPY . .` and stays cached either way,
so this is cheaper than a scoped `--no-cache-filter`. `script/cibuild`
is a model script shared across repos and is left untouched.

The second half of the problem was that Gitea cancels the in-flight run
when a newer commit lands on the same branch and records the
cancellation as `failure`, marking commits red that were never tested.
That cancellation is unconditional server-side for push events, so the
superseding run now rewrites the exact `Has been cancelled` status to
`skipped`. Genuine failures are never touched.
This commit is contained in:
2026-08-12 09:49:51 +00:00
parent 543005c0c2
commit 5ac43e6b96
5 changed files with 90 additions and 3 deletions

View File

@@ -11,5 +11,53 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 2024-10-23
with:
# The fingerprint step below needs history to find the last commit
# that touched the Docker build context.
fetch-depth: 0
- name: Neutralize superseded run statuses
# Gitea cancels the in-flight run when another commit is pushed to the
# same branch and records the cancellation as `failure`, so a commit
# that was never tested reads red. The cancellation is unconditional
# server-side for push events and cannot be disabled from a workflow
# file, so the superseding run rewrites those statuses to `skipped`.
# Only the exact cancellation status is touched; a real failure is
# left alone.
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -eu
api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}"
ctx='check / check (push)'
for sha in $(git rev-list --max-count=20 "${GITHUB_SHA}^" || true); do
latest="$(curl -sf "${api}/commits/${sha}/status" | jq -r \
--arg c "$ctx" \
'[.statuses[] | select(.context == $c)][0] // empty
| "\(.status)|\(.description)"')" || continue
[ "$latest" = 'failure|Has been cancelled' ] || continue
curl -sf -X POST "${api}/statuses/${sha}" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg c "$ctx" '{
context: $c,
state: "skipped",
description: "Superseded by a newer commit; never tested"
}')" >/dev/null
echo "neutralized superseded status on ${sha}"
done
- name: Fingerprint the build context
# `.dockerignore` keeps docs out of the build context, so a docs-only
# commit legitimately replays the whole image from cache and stays
# cheap. Every other commit writes a new fingerprint into the context,
# which invalidates the `COPY . .` layer of both check stages: a
# commit that was never linted, formatted-checked, tested and built
# cannot report success from cache.
run: |
set -eu
fp="$(git log -1 --format=%H -- . ':!*.md' ':!LICENSE' ':!.editorconfig')"
printf '%s\n' "${fp:-$GITHUB_SHA}" > .ci-fingerprint
- name: Build Docker image (runs make check)
run: script/cibuild