-
Notifications
You must be signed in to change notification settings - Fork 0
131 lines (123 loc) · 5.79 KB
/
Copy pathrelease.yml
File metadata and controls
131 lines (123 loc) · 5.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
name: release
# Snapshot the template on every push to master (and to release/* maintenance
# lines) as a versioned GitHub release, plus a rolling "latest" that the stable
# .../releases/latest/download/ URL tracks.
#
# Versions are semver vMAJOR.MINOR.PATCH. The bump is the HIGHEST level any
# commit since the last release asks for, via a [bump:*] marker in its SUBJECT
# (see the markers check in commit-convention.yml):
# * [bump:major] -> major (X+1.0.0)
# * [bump:minor] -> minor (X.Y+1.0)
# * [bump:patch] -> patch (X.Y.Z+1)
# An UNMARKED commit takes the branch default: minor on master, patch on a
# release/* maintenance branch (where you're back-patching, and a minor could
# collide with a mainline tag). Pre-semver tags (vX.Y two-component) read as
# X.Y.0; the first publish ever is v0.1.0.
on:
push:
branches:
- master
- 'release/*'
workflow_dispatch:
permissions:
contents: write
concurrency:
# Per-ref: a release/* back-patch never queues behind a master build.
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
release:
# Don't publish on branch creation (e.g. opening a release/* line at an
# existing release commit) — there are no new commits to release, and it
# would otherwise fall through to a spurious bump.
if: ${{ github.event_name != 'push' || !github.event.created }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Compute next version
id: version
run: |
set -euo pipefail
# Select the baseline by tag NAME: a release/vX.Y branch sees only its
# X.Y line, master (or any other ref) sees all clean tags globally.
# The rolling "latest" tag and any suffixed tag are excluded.
case "${GITHUB_REF_NAME:-}" in
release/v*) default_lvl=1 # maintenance line: unmarked -> patch
mm="$(printf '%s' "${GITHUB_REF_NAME#release/v}" | cut -d. -f1,2 | sed 's/[.]/\\./g')"
tagpat="^v${mm}(\.[0-9]+)?$" ;;
*) default_lvl=2 # mainline: unmarked -> minor
tagpat='^v[0-9]+\.[0-9]+(\.[0-9]+)?$' ;;
esac
# `|| true` so an empty tag set (no match) doesn't fail the pipeline
# under pipefail — that's the fresh-start case, which yields v0.1.0.
latest="$(git tag -l 'v[0-9]*' | { grep -E "$tagpat" || true; } | sed 's/^v//' | sort -V | tail -1)"
if [ -z "$latest" ]; then
next="0.1.0"
else
# No new commits since this line's latest release => nothing to do.
commits="$(git rev-list "v${latest}..HEAD")"
if [ -z "$commits" ]; then
echo "no new commits since v${latest} — nothing to release"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
maj=$(echo "$latest" | awk -F. '{print $1+0}')
min=$(echo "$latest" | awk -F. '{print $2+0}')
pat=$(echo "$latest" | awk -F. '{print $3+0}') # missing patch -> 0
rank=0 # highest level seen: 1=patch 2=minor 3=major
while IFS= read -r sha; do
[ -n "$sha" ] || continue
msg="$(git log -1 --format=%s "$sha")" # subject line only
if printf '%s' "$msg" | grep -qi '\[bump:major\]'; then lvl=3
elif printf '%s' "$msg" | grep -qi '\[bump:minor\]'; then lvl=2
elif printf '%s' "$msg" | grep -qi '\[bump:patch\]'; then lvl=1
else lvl=$default_lvl; fi
[ "$lvl" -gt "$rank" ] && rank=$lvl
done <<< "$commits"
case "$rank" in
3) next="$((maj + 1)).0.0" ;;
2) next="${maj}.$((min + 1)).0" ;;
1) next="${maj}.${min}.$((pat + 1))" ;;
esac
fi
echo "tag=v${next}" >> "$GITHUB_OUTPUT"
echo "Next version: v${next}"
- name: Create repo archive
id: archive
if: steps.version.outputs.skip != 'true'
run: |
# Pack the working tree (minus .git) into a zstd-compressed tarball.
# The static toolchain lives in its own repo (yeet-src/toolchain) and
# is fetched on demand into a per-machine cache, so nothing heavy
# ships in this archive. The toolchain/ subtree is dev-time source
# only (build recipe + embed glue) — the payload under template/build/
# already carries its synced copies, so exclude it from the archive.
repo="${GITHUB_REPOSITORY##*/}"
tar \
--exclude="./.git" \
--exclude="./toolchain" \
--use-compress-program "zstd -19 -T0" \
-cf "${RUNNER_TEMP}/${repo}.tar.zst" \
-C "${GITHUB_WORKSPACE}" .
echo "asset=${RUNNER_TEMP}/${repo}.tar.zst" >> "$GITHUB_OUTPUT"
- name: Publish versioned release
if: steps.version.outputs.skip != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
# Mark the newest MAINLINE version as GitHub "Latest" so the stable
# .../releases/latest/download/<asset> URL tracks it (no separate
# rolling "latest" release — GitHub's built-in Latest is the single
# source of truth). A release/* back-port must NOT steal Latest from a
# newer mainline release, so it publishes with --latest=false.
if [ "${GITHUB_REF_NAME}" = master ]; then mk=--latest; else mk='--latest=false'; fi
gh release create "${{ steps.version.outputs.tag }}" \
"${{ steps.archive.outputs.asset }}" \
--title "${{ steps.version.outputs.tag }}" \
--notes "Repo snapshot of ${GITHUB_SHA}" \
--target "${GITHUB_SHA}" \
"$mk"