detached 63547d4

release: auto-tag and publish on every green ci run

- release.yml now triggers on workflow_run of ci (completed+success),
  not on bare push, so we never publish binaries of broken code
- checks out github.event.workflow_run.head_sha, computes the next
  vX.Y.Z by bumping the patch of the newest existing tag (starts at
  v0.0.1), pushes the tag, runs goreleaser
- supports [skip release] in the commit message for docs/ci-only
  commits that shouldn't cut a version
commit
63547d43ba5a11b3a79adc305e90927febfa4ed8
author
patriceckhart · · 2026-04-18 09:13 UTC
parent
c0adbc4
signature
unsigned
1 file · +69 -10
M .github/workflows/release.yml +69 -10
@@ -1,24 +1,84 @@
11 name: release
22
3+# Every push to main that passes CI cuts a new release. The workflow:
4+# 1. waits for the ci workflow on the same commit to succeed
5+# 2. looks up the latest existing vX.Y.Z tag
6+# 3. bumps the patch number (vX.Y.Z+1) — or starts at v0.0.1 if none
7+# 4. creates and pushes that tag
8+# 5. runs goreleaser, which builds binaries for linux/darwin/windows
9+# (amd64 + arm64) and attaches them plus checksums.txt to a fresh
10+# GitHub Release page
11+#
12+# Skip a release by putting [skip release] anywhere in the commit
13+# message — useful for docs-only or ci-only commits.
314 on:
4- push:
5- tags:
6- - "v*"
15+ workflow_run:
16+ workflows: [ci]
17+ types: [completed]
18+ branches: [main]
719
820 permissions:
9- # Needed for GoReleaser to create the GitHub Release and upload assets.
21+ # Needed to create tags and to upload release archives.
1022 contents: write
1123
24+# Only one release job at a time. If two pushes land in quick
25+# succession, queue them so they don't race on the tag.
26+concurrency:
27+ group: release
28+ cancel-in-progress: false
29+
1230 jobs:
13- goreleaser:
31+ release:
32+ # Only run when ci succeeded and we're on main, and when the head
33+ # commit doesn't opt out via [skip release].
34+ if: >-
35+ github.event.workflow_run.conclusion == 'success' &&
36+ github.event.workflow_run.head_branch == 'main' &&
37+ !contains(github.event.workflow_run.head_commit.message, '[skip release]')
1438 runs-on: ubuntu-latest
1539 steps:
1640 - name: checkout
1741 uses: actions/checkout@v4
1842 with:
19- # GoReleaser needs the full history to build a useful
20- # changelog from commit messages.
43+ # Full history so we can read existing tags.
2144 fetch-depth: 0
45+ # Check out the exact sha that ci verified, not whatever the
46+ # branch tip happens to be at this moment (which may have
47+ # advanced since ci finished).
48+ ref: ${{ github.event.workflow_run.head_sha }}
49+
50+ - name: compute next version
51+ id: bump
52+ shell: bash
53+ run: |
54+ set -euo pipefail
55+ # Find the newest vX.Y.Z tag. If the repo has none yet, start
56+ # at v0.0.0 so the first release becomes v0.0.1.
57+ latest=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n1)
58+ if [ -z "$latest" ]; then
59+ latest="v0.0.0"
60+ fi
61+ echo "latest tag: $latest"
62+
63+ # Strip the v prefix, split on dots, bump the patch.
64+ ver="${latest#v}"
65+ IFS=. read -r major minor patch <<<"$ver"
66+ patch=$((patch + 1))
67+ next="v${major}.${minor}.${patch}"
68+ echo "next tag: $next"
69+
70+ echo "next=$next" >> "$GITHUB_OUTPUT"
71+ echo "version=${next#v}" >> "$GITHUB_OUTPUT"
72+
73+ - name: create and push tag
74+ env:
75+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
76+ run: |
77+ git config user.name "github-actions[bot]"
78+ git config user.email "github-actions[bot]@users.noreply.github.com"
79+ git tag -a "${{ steps.bump.outputs.next }}" \
80+ -m "release ${{ steps.bump.outputs.next }}"
81+ git push origin "${{ steps.bump.outputs.next }}"
2282
2383 - name: set up go
2484 uses: actions/setup-go@v5
@@ -33,11 +93,10 @@ jobs:
3393 version: latest
3494 args: release --clean
3595 env:
36- # Default token for the release itself (uploads artifacts,
37- # creates the release page).
96+ # goreleaser reads the tag we just pushed and builds binaries
97+ # for every (goos, goarch) combination in .goreleaser.yaml.
3898 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3999 # Optional: PAT with `repo` scope for patriceckhart/homebrew-tap
40100 # so we can push a Formula/zot.rb update on every release.
41- # Create the secret in repo settings → actions → secrets.
42101 # If unset, goreleaser skips the brew step (skip_upload: auto).
43102 HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
@@ -1,24 +1,84 @@
1 name: release1 name: release
2 2
3+# Every push to main that passes CI cuts a new release. The workflow:
4+# 1. waits for the ci workflow on the same commit to succeed
5+# 2. looks up the latest existing vX.Y.Z tag
6+# 3. bumps the patch number (vX.Y.Z+1) — or starts at v0.0.1 if none
7+# 4. creates and pushes that tag
8+# 5. runs goreleaser, which builds binaries for linux/darwin/windows
9+# (amd64 + arm64) and attaches them plus checksums.txt to a fresh
10+# GitHub Release page
11+#
12+# Skip a release by putting [skip release] anywhere in the commit
13+# message — useful for docs-only or ci-only commits.
3 on:14 on:
4- push:15+ workflow_run:
5- tags:16+ workflows: [ci]
6- - "v*"17+ types: [completed]
18+ branches: [main]
7 19
8 permissions:20 permissions:
9- # Needed for GoReleaser to create the GitHub Release and upload assets.21+ # Needed to create tags and to upload release archives.
10 contents: write22 contents: write
11 23
24+# Only one release job at a time. If two pushes land in quick
25+# succession, queue them so they don't race on the tag.
26+concurrency:
27+ group: release
28+ cancel-in-progress: false
29+
12 jobs:30 jobs:
13- goreleaser:31+ release:
32+ # Only run when ci succeeded and we're on main, and when the head
33+ # commit doesn't opt out via [skip release].
34+ if: >-
35+ github.event.workflow_run.conclusion == 'success' &&
36+ github.event.workflow_run.head_branch == 'main' &&
37+ !contains(github.event.workflow_run.head_commit.message, '[skip release]')
14 runs-on: ubuntu-latest38 runs-on: ubuntu-latest
15 steps:39 steps:
16 - name: checkout40 - name: checkout
17 uses: actions/checkout@v441 uses: actions/checkout@v4
18 with:42 with:
19- # GoReleaser needs the full history to build a useful43+ # Full history so we can read existing tags.
20- # changelog from commit messages.
21 fetch-depth: 044 fetch-depth: 0
45+ # Check out the exact sha that ci verified, not whatever the
46+ # branch tip happens to be at this moment (which may have
47+ # advanced since ci finished).
48+ ref: ${{ github.event.workflow_run.head_sha }}
49+
50+ - name: compute next version
51+ id: bump
52+ shell: bash
53+ run: |
54+ set -euo pipefail
55+ # Find the newest vX.Y.Z tag. If the repo has none yet, start
56+ # at v0.0.0 so the first release becomes v0.0.1.
57+ latest=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n1)
58+ if [ -z "$latest" ]; then
59+ latest="v0.0.0"
60+ fi
61+ echo "latest tag: $latest"
62+
63+ # Strip the v prefix, split on dots, bump the patch.
64+ ver="${latest#v}"
65+ IFS=. read -r major minor patch <<<"$ver"
66+ patch=$((patch + 1))
67+ next="v${major}.${minor}.${patch}"
68+ echo "next tag: $next"
69+
70+ echo "next=$next" >> "$GITHUB_OUTPUT"
71+ echo "version=${next#v}" >> "$GITHUB_OUTPUT"
72+
73+ - name: create and push tag
74+ env:
75+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
76+ run: |
77+ git config user.name "github-actions[bot]"
78+ git config user.email "github-actions[bot]@users.noreply.github.com"
79+ git tag -a "${{ steps.bump.outputs.next }}" \
80+ -m "release ${{ steps.bump.outputs.next }}"
81+ git push origin "${{ steps.bump.outputs.next }}"
22 82
23 - name: set up go83 - name: set up go
24 uses: actions/setup-go@v584 uses: actions/setup-go@v5
@@ -33,11 +93,10 @@ jobs:
33 version: latest93 version: latest
34 args: release --clean94 args: release --clean
35 env:95 env:
36- # Default token for the release itself (uploads artifacts,96+ # goreleaser reads the tag we just pushed and builds binaries
37- # creates the release page).97+ # for every (goos, goarch) combination in .goreleaser.yaml.
38 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}98 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39 # Optional: PAT with `repo` scope for patriceckhart/homebrew-tap99 # Optional: PAT with `repo` scope for patriceckhart/homebrew-tap
40 # so we can push a Formula/zot.rb update on every release.100 # so we can push a Formula/zot.rb update on every release.
41- # Create the secret in repo settings → actions → secrets.
42 # If unset, goreleaser skips the brew step (skip_upload: auto).101 # If unset, goreleaser skips the brew step (skip_upload: auto).
43 HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}102 HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 477

↑↓ moveenter openesc close