Skip to content

Commit 94cf6f3

Browse files
committed
merge-upstream: report the most severe gating workflow state
The gate step exited 0 whatever it found, so a commit whose CI DEB leg went red still produced a green run that merged nothing. A failed leg now fails the job and a cancelled one cancels it, and the step summary tables every gating workflow with a link to its run.
1 parent e2229db commit 94cf6f3

1 file changed

Lines changed: 80 additions & 15 deletions

File tree

.github/workflows/merge-upstream.yml

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ name: Merge into upstream
3333
# until the last of them lands. Whichever run finds them all green performs
3434
# the merge. That avoids a job sitting in a poll loop holding a runner.
3535
#
36+
# This workflow reports the most severe state of the gating workflows, so a
37+
# run that merged nothing is never green. Failure outranks cancelled, which
38+
# outranks a clean pass. A leg that is still running is not a state yet, so
39+
# nothing is reported until every leg has finished, except for a failure,
40+
# which nothing can outrank and so reports straight away.
41+
#
3642
# Note that workflow_run only ever uses the copy of this file on the default
3743
# branch, and runs in the context of the default branch rather than the branch
3844
# being merged - hence head_branch / head_sha / actor throughout.
@@ -72,10 +78,11 @@ jobs:
7278
timeout-minutes: 15
7379

7480
# Listing any permission drops the unlisted ones. The pushes below use the
75-
# app token, not GITHUB_TOKEN, so read access is all this needs.
81+
# app token, not GITHUB_TOKEN. actions: write is for this run cancelling
82+
# itself to mirror a cancelled gating workflow, nothing else needs write.
7683
permissions:
7784
contents: read
78-
actions: read
85+
actions: write
7986

8087
env:
8188
BRANCH: ${{ github.event.workflow_run.head_branch }}
@@ -95,51 +102,109 @@ jobs:
95102
set -euo pipefail
96103
97104
ready=yes
105+
failed=""
106+
cancelled=""
107+
pending=no
108+
109+
{
110+
echo "### Gating workflows for \`${HEAD_SHA}\`"
111+
echo
112+
echo "| Workflow | Status | Conclusion |"
113+
echo "| --- | --- | --- |"
114+
} >> "$GITHUB_STEP_SUMMARY"
98115
99116
for wf in ${REQUIRED_WORKFLOWS}; do
100117
# Match the commit and the push event, so a nightly schedule run of
101118
# the same workflow is never mistaken for this one.
102119
info=$(gh api \
103120
"repos/${GITHUB_REPOSITORY}/actions/workflows/${wf}/runs?head_sha=${HEAD_SHA}&event=push&per_page=1" \
104-
--jq '.workflow_runs[0] // empty | "\(.status) \(.conclusion // "none")"') || info=""
121+
--jq '.workflow_runs[0] // empty | "\(.status) \(.conclusion // "none") \(.html_url)"') || info=""
105122
106123
if [ -z "${info}" ]; then
107124
echo " ${wf}: no run for this commit"
125+
echo "| ${wf} | no run | |" >> "$GITHUB_STEP_SUMMARY"
108126
ready=no
127+
pending=yes
109128
continue
110129
fi
111130
112-
status="${info%% *}"
113-
conclusion="${info##* }"
131+
read -r status conclusion url <<< "${info}"
114132
115133
if [ "${status}" != "completed" ]; then
116134
echo " ${wf}: ${status}"
135+
echo "| [${wf}](${url}) | ${status} | |" >> "$GITHUB_STEP_SUMMARY"
117136
ready=no
137+
pending=yes
118138
continue
119139
fi
120140
121-
# A cancelled leg is not a pass. Concurrency cancels the older run
122-
# when a new commit lands, and that result says nothing about this
123-
# commit.
141+
echo " ${wf}: ${conclusion}"
142+
echo "| [${wf}](${url}) | ${status} | ${conclusion} |" >> "$GITHUB_STEP_SUMMARY"
143+
124144
case "${conclusion}" in
125145
success|skipped|neutral)
126-
echo " ${wf}: ${conclusion}"
127146
;;
128147
148+
# Concurrency cancels the older run when a newer commit lands,
149+
# so a cancelled leg is not a pass and not a failure either.
150+
cancelled)
151+
ready=no
152+
cancelled="${cancelled} ${wf}"
153+
;;
154+
155+
# Anything outside the pass set fails, so a conclusion this
156+
# workflow has not seen before gets reported rather than
157+
# silently ignored.
129158
*)
130-
echo " ${wf}: ${conclusion}"
131159
ready=no
160+
failed="${failed} ${wf}"
132161
;;
133162
esac
134163
done
135164
136-
# Exiting 0 either way: a leg that is still running or has failed is
137-
# not this workflow's problem to report, and failing here would put a
138-
# red run in the Actions tab for every commit that is merely still
139-
# building.
140165
echo "ready=${ready}" >> "$GITHUB_OUTPUT"
141-
if [ "${ready}" != "yes" ]; then
166+
167+
# Nothing outranks a failure, so it reports without waiting for the
168+
# legs that are still running.
169+
if [ -n "${failed}" ]; then
170+
{
171+
echo
172+
echo "### ${BRANCH} was not merged into master"
173+
echo
174+
echo "Gating workflows that did not pass:${failed}"
175+
} >> "$GITHUB_STEP_SUMMARY"
176+
177+
echo "::error::${BRANCH} was not merged into master, gating workflows that did not pass:${failed}"
178+
exit 1
179+
fi
180+
181+
# A leg that is still running has no state to report yet, and it can
182+
# still fail, which would outrank the cancelled below.
183+
if [ "${pending}" = "yes" ]; then
142184
echo "Not merging ${BRANCH} yet"
185+
exit 0
186+
fi
187+
188+
if [ -n "${cancelled}" ]; then
189+
{
190+
echo
191+
echo "### ${BRANCH} was not merged into master"
192+
echo
193+
echo "Gating workflows that were cancelled:${cancelled}"
194+
} >> "$GITHUB_STEP_SUMMARY"
195+
196+
echo "::warning::${BRANCH} was not merged into master, gating workflows that were cancelled:${cancelled}"
197+
198+
# An exit code only ever picks success or failure, so the run
199+
# cancels itself to mirror the cancelled leg. Parking on a fifo
200+
# with no writer afterwards blocks in the kernel until the runner
201+
# kills the job, so the script never reaches an exit code that
202+
# would overwrite the cancellation. timeout-minutes on the job
203+
# bounds the wait if the cancellation never arrives.
204+
gh run cancel "${GITHUB_RUN_ID}"
205+
206+
mkfifo "${RUNNER_TEMP}/await-cancel"
207+
cat "${RUNNER_TEMP}/await-cancel"
143208
fi
144209
145210
- uses: actions/checkout@v6

0 commit comments

Comments
 (0)