All checks were successful
check / check (push) Successful in 3m5s
Gitea cancels an in-flight run when a newer commit lands on the same branch and records the cancellation as `failure` / "Has been cancelled". The workflow rewrote that to `skipped`, but Gitea's Combine() folds `skipped` into `success`, so the combined-status API returned green for a commit nothing had ever tested. Rewrite it to `failure` / "Superseded by a newer commit; never tested" instead: red-but-honest, and never `pending`, which would block the commit forever. Re-running the superseded commit would have been better still, but is not reachable on this Gitea (1.25.4): its API exposes no rerun endpoint, workflow dispatch takes a ref rather than a SHA, and every replay would be a full uncached build with no bound on how many pile up behind a burst of merges. The step also stops hardcoding its status context: the logic moves into script/ci-mark-superseded, which derives the context from the workflow name, job name and event -- the same three values Gitea builds it from -- and fails loudly when no status on the commit being built carries that context, so renaming the workflow or the job cannot silently disable the rewrite. That is item 2 of #147; item 1 there is untouched. Tests drive the script against a fake Gitea covering the cancelled, laundered-skipped, genuinely-failed, passing and renamed cases, so jq joins the builder image to run them.
87 lines
3.3 KiB
Bash
Executable File
87 lines
3.3 KiB
Bash
Executable File
#!/bin/sh
|
|
# script/ci-mark-superseded: record an honest status on commits whose CI
|
|
# run Gitea cancelled because a newer commit landed on the same branch.
|
|
# Gitea writes `failure` / "Has been cancelled" for such a run, which
|
|
# reads as a test result on a commit nothing ever tested. Cancellation is
|
|
# unconditional server-side for push events, so the superseding run
|
|
# rewrites those statuses to `failure` with a description that says the
|
|
# commit was never tested. `skipped` cannot be used: Gitea's combined
|
|
# status folds `skipped` into `success`, so a never-tested commit would
|
|
# report green. Genuine failures and successes are never touched.
|
|
#
|
|
# Called by the Gitea Actions workflow, which supplies GITHUB_API_URL,
|
|
# GITHUB_REPOSITORY, GITHUB_SHA, GITHUB_WORKFLOW, GITHUB_JOB,
|
|
# GITHUB_EVENT_NAME and GITEA_TOKEN. ANCESTOR_LIMIT (default 20) caps how
|
|
# far back the walk looks.
|
|
set -eu
|
|
|
|
SUPERSEDED_DESC='Superseded by a newer commit; never tested'
|
|
|
|
# Gitea builds the commit-status context as
|
|
# "<workflow name> / <job name> (<event>)", the same three values the
|
|
# runner exports, so derive it rather than hardcoding the result.
|
|
context() {
|
|
printf '%s / %s (%s)' \
|
|
"$GITHUB_WORKFLOW" "$GITHUB_JOB" "$GITHUB_EVENT_NAME"
|
|
}
|
|
|
|
# The status Gitea created for this very job proves which context string
|
|
# it uses. If the derived one is missing, the workflow or the job was
|
|
# renamed and the match below would silently stop firing, restoring the
|
|
# false-red bug with no signal. Fail loudly instead.
|
|
require_own_context() {
|
|
if ! _body="$(curl -sf --retry 3 --retry-delay 2 --max-time 30 \
|
|
"${1}/commits/${GITHUB_SHA}/status")"; then
|
|
echo "cannot read commit statuses for ${GITHUB_SHA}" >&2
|
|
return 1
|
|
fi
|
|
_found="$(printf '%s' "$_body" | jq -r '(.statuses // [])[].context')"
|
|
if printf '%s\n' "$_found" | grep -qxF "$2"; then
|
|
return 0
|
|
fi
|
|
echo "no commit status with context '${2}' on ${GITHUB_SHA}:" >&2
|
|
echo "workflow or job renamed? contexts present:" >&2
|
|
printf '%s\n' "$_found" >&2
|
|
return 1
|
|
}
|
|
|
|
# Latest status for our context on a commit, as "state|description".
|
|
status_of() {
|
|
curl -sf "${1}/commits/${2}/status" | jq -r --arg c "$3" \
|
|
'[(.statuses // [])[] | select(.context == $c)][0] // empty
|
|
| "\(.status)|\(.description)"'
|
|
}
|
|
|
|
mark_superseded() {
|
|
curl -sf -X POST "${1}/statuses/${2}" \
|
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
-H 'Content-Type: application/json' \
|
|
-d "$(jq -nc --arg c "$3" --arg d "$SUPERSEDED_DESC" \
|
|
'{context: $c, state: "failure", description: $d}')" \
|
|
>/dev/null
|
|
}
|
|
|
|
main() {
|
|
_api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}"
|
|
_ctx="$(context)"
|
|
|
|
require_own_context "$_api" "$_ctx"
|
|
|
|
_walk="$(git rev-list \
|
|
--max-count="${ANCESTOR_LIMIT:-20}" "${GITHUB_SHA}^" || true)"
|
|
|
|
for _sha in $_walk; do
|
|
_latest="$(status_of "$_api" "$_sha" "$_ctx")"
|
|
# A run that was cancelled, or one an earlier revision of this
|
|
# script laundered into `skipped`. Anything else stands.
|
|
case "$_latest" in
|
|
'failure|Has been cancelled' | "skipped|${SUPERSEDED_DESC}") ;;
|
|
*) continue ;;
|
|
esac
|
|
mark_superseded "$_api" "$_sha" "$_ctx"
|
|
echo "marked superseded: ${_sha}"
|
|
done
|
|
}
|
|
|
|
main "$@"
|