#!/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 # " / ()", 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 "$@"