#!/usr/bin/env bash # Guard the trap in .gitea/workflows. # # Gitea uses .gitea/workflows IF IT EXISTS, and falls back to .github/workflows # only when it does not. It never merges them. So a workflow that lives only in # .github/ simply does not run on Gitea — no error, no run to click on. It # looks exactly like a runner that has not picked the job up, so you wait, and # waiting never fixes it. # # Workflows that must behave identically on both forges are therefore kept # byte-identical in both roots, and this script fails when they drift or when # one appears in only one root without a stated reason. set -euo pipefail cd "$(dirname "$0")/.." . ci/lib.sh # Workflows that are deliberately single-forge, and why. declare -A only_on=( ["auto-merge.yml"]="github: it drives the GitHub merge API. Gitea merges the owner's green PRs itself." ["release.yml"]="both, but different: each publishes to the forge it runs on, and Gitea is where the update feed lives." ) fail=0 for root in .github .gitea; do [ -d "$root/workflows" ] || { echo "$root/workflows is missing."; fail=1; } done for f in .github/workflows/*.yml .gitea/workflows/*.yml; do [ -e "$f" ] || continue name=$(basename "$f") gh=".github/workflows/$name" gt=".gitea/workflows/$name" case "${only_on[$name]:-}" in github:*) [ -f "$gt" ] && { echo "$name is marked GitHub-only but exists in .gitea/workflows too."; fail=1; } continue ;; both,*|"both, "*) # Present in both, intentionally different. Both copies must exist. { [ -f "$gh" ] && [ -f "$gt" ]; } \ || { echo "$name is expected in both roots, but one copy is missing."; fail=1; } continue ;; esac if [ ! -f "$gh" ] || [ ! -f "$gt" ]; then echo "$name exists in only one workflow root and is not listed in ci/workflows-in-sync.sh." echo " A workflow missing from .gitea/workflows does not run on Gitea, silently." fail=1 continue fi if ! cmp -s "$gh" "$gt"; then echo "$name has drifted between the two roots:" diff -u "$gh" "$gt" || true fail=1 fi done # Measured on Gitea 1.27.2: a workflow-level `concurrency:` block produced no # run at all — not a failed one, not a cancelled one. It may have been fixed # since, but the cost of keeping the rule is nil and the cost of rediscovering # it is an afternoon of blaming the runner. if grep -rn '^concurrency:' .gitea/workflows/ 2>/dev/null; then echo "A workflow-level concurrency: block can stop a Gitea workflow from running at all." fail=1 fi # A workflow that does not parse produces no run — the same symptom as every # other trap in this file. ci.yml cannot catch its own syntax error this way, # but release.yml and compat.yml can be caught here rather than on the tag. # # 🔴 This used to skip with a note when PyYAML was missing, and still printed # "Workflows: in sync." at the end. PyYAML is missing in exactly one place — # the GITEA RUNNER (measured: `import yaml` raises ModuleNotFoundError on # gitea/runner-images:ubuntu-latest) — which is the one forge where an # unparseable workflow produces no run and no error to click on. So the check # was switched off precisely where it was needed, and said "in sync" anyway. # # ci_python_yaml falls back to a container, so there is no skip path left: if # the workflows cannot be parsed, that is a failure, not a note. ci_python_yaml ci/check-workflows.py .github/workflows/*.yml .gitea/workflows/*.yml \ || fail=1 [ "$fail" = 0 ] || exit 1 echo "Workflows: in sync."