Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ jobs:
outputs:
label: ${{ steps.v.outputs.label }}
train: ${{ steps.v.outputs.train }}
code: ${{ steps.v.outputs.code }}
# From the guard, not from `v`: a release tag on a commit the snapshot
# run already published is raised there. See that step.
code: ${{ steps.ordinal.outputs.code }}
date: ${{ steps.v.outputs.date }}
prerelease: ${{ steps.v.outputs.prerelease }}
steps:
Expand Down Expand Up @@ -60,7 +62,30 @@ jobs:
# is invisible to it — which is how 408283049 reached TestFlight ahead of
# the scheme. The per-store floors in tool/release/version.sh are what cover
# those; raise one whenever something is shipped outside this workflow.
#
# Equal and lower are different failures, and only one of them is a bug.
#
# **Lower** means the ordinal went backwards — a rewritten history, which
# is what the check was written for. It still stops the run.
#
# **Equal** on a tag means this commit was already published as a
# snapshot. That is not an accident, it is the normal shape of a release:
# every push to main publishes, so by the time anyone can tag a commit it
# has already spent its number, and both uploads below run for a snapshot
# exactly as they do for a release. `v26.1` only shipped because its
# commit never went out as a snapshot; on `v26.2` (`d2b4664b`, carrying
# both `26w35g` and `v26.2`) the tag run computed the snapshot's own
# 426000507 and died. There is no way to tag around it — a branch push
# and a tag push are two events on one commit, so a fresh commit collides
# the same way.
#
# So a tie on a tag takes the next number instead. What that gives up is
# the ordinal reading back as an exact commit count for release builds
# only; what it buys is a release path that exists. A tie on a *branch*
# is still a failure — nothing should publish the same commit twice as a
# snapshot.
- name: Ordinal must be higher than the last published one
id: ordinal
env:
GH_TOKEN: ${{ github.token }}
# Through the environment rather than interpolated into the script:
Expand All @@ -79,10 +104,19 @@ jobs:
previous="$(gh api "repos/$GITHUB_REPOSITORY/releases?per_page=100" \
--jq '[.[] | (.body // "") | capture("<!--\\s*dpip-build:\\s*(?<c>\\d+)") | .c | tonumber] | max // 0')"
echo "previous=$previous current=$CODE"
if [ "$CODE" -le "$previous" ]; then
echo "::error::build ordinal $CODE is not above the published $previous"
if [ "$CODE" -lt "$previous" ]; then
echo "::error::build ordinal $CODE is below the published $previous"
exit 1
fi
if [ "$CODE" -eq "$previous" ]; then
if [ "$GITHUB_REF_TYPE" != "tag" ]; then
echo "::error::build ordinal $CODE repeats the published $previous"
exit 1
fi
CODE=$((previous + 1))
echo "::notice::this commit already published $previous as a snapshot; the release takes $CODE"
fi
echo "code=$CODE" >> "$GITHUB_OUTPUT"

build:
needs: version
Expand Down
17 changes: 12 additions & 5 deletions tool/release/version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
# code what both stores sort by 426000298 426000299
# date the day the build was cut 26-08-17 26-08-17
#
# One code, not one per store. They can be aligned because the two floors do
# not both apply at once — see FLOOR and BURNED_TRAINS below.
# One code, not one per store. They can be aligned because the leading
# generation digit clears both stores' history at once — see SCHEME below.
#
# They are separated because they answer different questions and obey
# different rules:
Expand Down Expand Up @@ -47,9 +47,16 @@ set -euo pipefail
# commit — and it moves by exactly as much as the work did.
#
# What it costs: a rewritten history can lower it, where a clock cannot. That
# is why the release workflow refuses to build when the code is not above
# every one already published — a rewrite then stops the run instead of
# poisoning a store, and the fix is to raise FLOOR by a line.
# is why the release workflow compares the code against every one it has
# already published, and refuses to build when this one is *below* — a rewrite
# stops the run instead of poisoning a store, and the fix is to raise SCHEME.
#
# It is also why a release tag does not always get the count. Every push to
# main publishes a snapshot, and a snapshot uploads to both stores, so a
# commit has spent its number by the time anyone can tag it; the workflow
# raises a tagged build past the tie rather than failing on it. A release's
# code is therefore the next free number, not necessarily its commit count.
# See the ordinal step in `.github/workflows/release.yml`.
#
# The code is `4 | yy | commits-this-year`, read straight off the digits:
#
Expand Down
Loading