ci(release): tag main every morning on ermite and publish in the same run
A schedule at 07:00 Europe/Paris (ermite's cron timezone) runs next-release-tag.sh on main and pushes the tag with the job token. That push starts no run, so the release job publishes the tag in the same run. The tag job never runs on the GitHub mirror, which only publishes the tags it receives. Manual signed tags still publish, and the policy test is back in CI.
6 files · +183 -5
M .forgejo/workflows/ci.yml +3 -0
| @@ -41,6 +41,9 @@ jobs: | |||
| 41 | 41 | - name: go test | |
| 42 | 42 | run: go test -race ./... | |
| 43 | 43 | ||
| 44 | + | - name: release-version policy | |
| 45 | + | run: bash .github/scripts/next-release-tag_test.sh | |
| 46 | + | ||
| 44 | 47 | - name: golangci-lint | |
| 45 | 48 | uses: golangci/golangci-lint-action@v9.3.0 | |
| 46 | 49 | with: | |
| @@ -41,6 +41,9 @@ jobs: | |||||
| 41 | - name: go test | 41 | - name: go test | ||
| 42 | run: go test -race ./... | 42 | run: go test -race ./... | ||
| 43 | 43 | ||||
| 44 | + | - name: release-version policy | |||
| 45 | + | run: bash .github/scripts/next-release-tag_test.sh | |||
| 46 | + | ||||
| 44 | - name: golangci-lint | 47 | - name: golangci-lint | ||
| 45 | uses: golangci/golangci-lint-action@v9.3.0 | 48 | uses: golangci/golangci-lint-action@v9.3.0 | ||
| 46 | with: | 49 | with: | ||
A .github/scripts/next-release-tag.sh +60 -0
| @@ -0,0 +1,60 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # Print the release tag for HEAD, or nothing when its unreleased commits do not | |
| 3 | + | # require a release. A tag already pointing to HEAD is emitted again so a | |
| 4 | + | # failed GoReleaser job can be retried without minting a second tag. | |
| 5 | + | set -euo pipefail | |
| 6 | + | ||
| 7 | + | latest=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n1) | |
| 8 | + | head=$(git rev-parse HEAD) | |
| 9 | + | if [ -n "$latest" ] && [ "$(git rev-parse "$latest^{}")" = "$head" ]; then | |
| 10 | + | printf '%s\n' "$latest" | |
| 11 | + | exit 0 | |
| 12 | + | fi | |
| 13 | + | ||
| 14 | + | if [ -n "$latest" ]; then | |
| 15 | + | range="${latest}..HEAD" | |
| 16 | + | else | |
| 17 | + | range="HEAD" | |
| 18 | + | fi | |
| 19 | + | ||
| 20 | + | minor=false | |
| 21 | + | patch=false | |
| 22 | + | while IFS= read -r commit; do | |
| 23 | + | subject=$(git log -1 --format=%s "$commit") | |
| 24 | + | body=$(git log -1 --format=%b "$commit") | |
| 25 | + | ||
| 26 | + | # A breaking change is a v0.x minor release. | |
| 27 | + | if grep -Eq '^[[:alpha:]]+(\([^)]+\))?!:' <<<"$subject" || grep -Eiq '^BREAKING[ -]CHANGE:' <<<"$body"; then | |
| 28 | + | minor=true | |
| 29 | + | continue | |
| 30 | + | fi | |
| 31 | + | ||
| 32 | + | type=${subject%%(*} | |
| 33 | + | type=${type%%:*} | |
| 34 | + | type=${type%%!*} | |
| 35 | + | type=$(tr '[:upper:]' '[:lower:]' <<<"$type") | |
| 36 | + | case "$type" in | |
| 37 | + | feat) minor=true ;; | |
| 38 | + | fix|perf|refactor|build|chore|ci) patch=true ;; | |
| 39 | + | docs|test|style) ;; | |
| 40 | + | *) ;; | |
| 41 | + | esac | |
| 42 | + | done < <(git rev-list "$range") | |
| 43 | + | ||
| 44 | + | if [ "$minor" != true ] && [ "$patch" != true ]; then | |
| 45 | + | exit 0 | |
| 46 | + | fi | |
| 47 | + | ||
| 48 | + | # The renamed project starts a new public release line at v0.1.0. | |
| 49 | + | if [ -z "$latest" ]; then | |
| 50 | + | printf '%s\n' 'v0.1.0' | |
| 51 | + | exit 0 | |
| 52 | + | fi | |
| 53 | + | ||
| 54 | + | version=${latest#v} | |
| 55 | + | IFS=. read -r major minor_version patch_version <<<"$version" | |
| 56 | + | if [ "$minor" = true ]; then | |
| 57 | + | printf 'v%s.%s.0\n' "$major" "$((minor_version + 1))" | |
| 58 | + | else | |
| 59 | + | printf 'v%s.%s.%s\n' "$major" "$minor_version" "$((patch_version + 1))" | |
| 60 | + | fi | |
| @@ -0,0 +1,60 @@ | |||||
| 1 | + | #!/usr/bin/env bash | |||
| 2 | + | # Print the release tag for HEAD, or nothing when its unreleased commits do not | |||
| 3 | + | # require a release. A tag already pointing to HEAD is emitted again so a | |||
| 4 | + | # failed GoReleaser job can be retried without minting a second tag. | |||
| 5 | + | set -euo pipefail | |||
| 6 | + | ||||
| 7 | + | latest=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n1) | |||
| 8 | + | head=$(git rev-parse HEAD) | |||
| 9 | + | if [ -n "$latest" ] && [ "$(git rev-parse "$latest^{}")" = "$head" ]; then | |||
| 10 | + | printf '%s\n' "$latest" | |||
| 11 | + | exit 0 | |||
| 12 | + | fi | |||
| 13 | + | ||||
| 14 | + | if [ -n "$latest" ]; then | |||
| 15 | + | range="${latest}..HEAD" | |||
| 16 | + | else | |||
| 17 | + | range="HEAD" | |||
| 18 | + | fi | |||
| 19 | + | ||||
| 20 | + | minor=false | |||
| 21 | + | patch=false | |||
| 22 | + | while IFS= read -r commit; do | |||
| 23 | + | subject=$(git log -1 --format=%s "$commit") | |||
| 24 | + | body=$(git log -1 --format=%b "$commit") | |||
| 25 | + | ||||
| 26 | + | # A breaking change is a v0.x minor release. | |||
| 27 | + | if grep -Eq '^[[:alpha:]]+(\([^)]+\))?!:' <<<"$subject" || grep -Eiq '^BREAKING[ -]CHANGE:' <<<"$body"; then | |||
| 28 | + | minor=true | |||
| 29 | + | continue | |||
| 30 | + | fi | |||
| 31 | + | ||||
| 32 | + | type=${subject%%(*} | |||
| 33 | + | type=${type%%:*} | |||
| 34 | + | type=${type%%!*} | |||
| 35 | + | type=$(tr '[:upper:]' '[:lower:]' <<<"$type") | |||
| 36 | + | case "$type" in | |||
| 37 | + | feat) minor=true ;; | |||
| 38 | + | fix|perf|refactor|build|chore|ci) patch=true ;; | |||
| 39 | + | docs|test|style) ;; | |||
| 40 | + | *) ;; | |||
| 41 | + | esac | |||
| 42 | + | done < <(git rev-list "$range") | |||
| 43 | + | ||||
| 44 | + | if [ "$minor" != true ] && [ "$patch" != true ]; then | |||
| 45 | + | exit 0 | |||
| 46 | + | fi | |||
| 47 | + | ||||
| 48 | + | # The renamed project starts a new public release line at v0.1.0. | |||
| 49 | + | if [ -z "$latest" ]; then | |||
| 50 | + | printf '%s\n' 'v0.1.0' | |||
| 51 | + | exit 0 | |||
| 52 | + | fi | |||
| 53 | + | ||||
| 54 | + | version=${latest#v} | |||
| 55 | + | IFS=. read -r major minor_version patch_version <<<"$version" | |||
| 56 | + | if [ "$minor" = true ]; then | |||
| 57 | + | printf 'v%s.%s.0\n' "$major" "$((minor_version + 1))" | |||
| 58 | + | else | |||
| 59 | + | printf 'v%s.%s.%s\n' "$major" "$minor_version" "$((patch_version + 1))" | |||
| 60 | + | fi | |||
A .github/scripts/next-release-tag_test.sh +48 -0
| @@ -0,0 +1,48 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | set -euo pipefail | |
| 3 | + | ||
| 4 | + | repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) | |
| 5 | + | script="$repo_root/.github/scripts/next-release-tag.sh" | |
| 6 | + | tmp=$(mktemp -d) | |
| 7 | + | trap 'rm -rf "$tmp"' EXIT | |
| 8 | + | ||
| 9 | + | cd "$tmp" | |
| 10 | + | git init --quiet | |
| 11 | + | git config user.name 'Release Test' | |
| 12 | + | git config user.email 'release-test@example.invalid' | |
| 13 | + | ||
| 14 | + | git commit --allow-empty --quiet -m 'feat: first release' | |
| 15 | + | expect() { | |
| 16 | + | local want=$1 | |
| 17 | + | local got | |
| 18 | + | got=$($script) | |
| 19 | + | if [ "$got" != "$want" ]; then | |
| 20 | + | printf 'next-release-tag = %q, want %q\n' "$got" "$want" >&2 | |
| 21 | + | exit 1 | |
| 22 | + | fi | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | expect 'v0.1.0' | |
| 26 | + | git tag -a v0.1.0 -m 'release v0.1.0' | |
| 27 | + | ||
| 28 | + | # A rerun after tag creation must release the existing tag, not skip it. | |
| 29 | + | expect 'v0.1.0' | |
| 30 | + | ||
| 31 | + | git commit --allow-empty --quiet -m 'docs: explain releases' | |
| 32 | + | expect '' | |
| 33 | + | ||
| 34 | + | git commit --allow-empty --quiet -m 'fix: repair installer' | |
| 35 | + | expect 'v0.1.1' | |
| 36 | + | git tag -a v0.1.1 -m 'release v0.1.1' | |
| 37 | + | ||
| 38 | + | git commit --allow-empty --quiet -m 'refactor!: replace extension handshake' | |
| 39 | + | expect 'v0.2.0' | |
| 40 | + | ||
| 41 | + | # GoReleaser passes tag_sort directly to `git tag --sort`, so keep the | |
| 42 | + | # configured semantic-version sort valid before a release run depends on it. | |
| 43 | + | tag_sort=$(sed -n 's/^[[:space:]]*tag_sort:[[:space:]]*//p' "$repo_root/.goreleaser.yaml") | |
| 44 | + | if [ "$tag_sort" != '-version:refname' ]; then | |
| 45 | + | printf 'goreleaser tag_sort = %q, want %q\n' "$tag_sort" '-version:refname' >&2 | |
| 46 | + | exit 1 | |
| 47 | + | fi | |
| 48 | + | git tag --sort "$tag_sort" --list 'v*.*.*' >/dev/null | |
| @@ -0,0 +1,48 @@ | |||||
| 1 | + | #!/usr/bin/env bash | |||
| 2 | + | set -euo pipefail | |||
| 3 | + | ||||
| 4 | + | repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) | |||
| 5 | + | script="$repo_root/.github/scripts/next-release-tag.sh" | |||
| 6 | + | tmp=$(mktemp -d) | |||
| 7 | + | trap 'rm -rf "$tmp"' EXIT | |||
| 8 | + | ||||
| 9 | + | cd "$tmp" | |||
| 10 | + | git init --quiet | |||
| 11 | + | git config user.name 'Release Test' | |||
| 12 | + | git config user.email 'release-test@example.invalid' | |||
| 13 | + | ||||
| 14 | + | git commit --allow-empty --quiet -m 'feat: first release' | |||
| 15 | + | expect() { | |||
| 16 | + | local want=$1 | |||
| 17 | + | local got | |||
| 18 | + | got=$($script) | |||
| 19 | + | if [ "$got" != "$want" ]; then | |||
| 20 | + | printf 'next-release-tag = %q, want %q\n' "$got" "$want" >&2 | |||
| 21 | + | exit 1 | |||
| 22 | + | fi | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | expect 'v0.1.0' | |||
| 26 | + | git tag -a v0.1.0 -m 'release v0.1.0' | |||
| 27 | + | ||||
| 28 | + | # A rerun after tag creation must release the existing tag, not skip it. | |||
| 29 | + | expect 'v0.1.0' | |||
| 30 | + | ||||
| 31 | + | git commit --allow-empty --quiet -m 'docs: explain releases' | |||
| 32 | + | expect '' | |||
| 33 | + | ||||
| 34 | + | git commit --allow-empty --quiet -m 'fix: repair installer' | |||
| 35 | + | expect 'v0.1.1' | |||
| 36 | + | git tag -a v0.1.1 -m 'release v0.1.1' | |||
| 37 | + | ||||
| 38 | + | git commit --allow-empty --quiet -m 'refactor!: replace extension handshake' | |||
| 39 | + | expect 'v0.2.0' | |||
| 40 | + | ||||
| 41 | + | # GoReleaser passes tag_sort directly to `git tag --sort`, so keep the | |||
| 42 | + | # configured semantic-version sort valid before a release run depends on it. | |||
| 43 | + | tag_sort=$(sed -n 's/^[[:space:]]*tag_sort:[[:space:]]*//p' "$repo_root/.goreleaser.yaml") | |||
| 44 | + | if [ "$tag_sort" != '-version:refname' ]; then | |||
| 45 | + | printf 'goreleaser tag_sort = %q, want %q\n' "$tag_sort" '-version:refname' >&2 | |||
| 46 | + | exit 1 | |||
| 47 | + | fi | |||
| 48 | + | git tag --sort "$tag_sort" --list 'v*.*.*' >/dev/null | |||
M .github/workflows/ci.yml +4 -0
| @@ -50,6 +50,10 @@ jobs: | |||
| 50 | 50 | ||
| 51 | 51 | # Keep Linux-only quality checks in the Ubuntu test job so CI needs | |
| 52 | 52 | # only the three platform runners. | |
| 53 | + | - name: release-version policy | |
| 54 | + | if: matrix.os == 'ubuntu-latest' | |
| 55 | + | run: bash .github/scripts/next-release-tag_test.sh | |
| 56 | + | ||
| 53 | 57 | - name: golangci-lint | |
| 54 | 58 | if: matrix.os == 'ubuntu-latest' | |
| 55 | 59 | uses: golangci/golangci-lint-action@v9.3.0 | |
| @@ -50,6 +50,10 @@ jobs: | |||||
| 50 | 50 | ||||
| 51 | # Keep Linux-only quality checks in the Ubuntu test job so CI needs | 51 | # Keep Linux-only quality checks in the Ubuntu test job so CI needs | ||
| 52 | # only the three platform runners. | 52 | # only the three platform runners. | ||
| 53 | + | - name: release-version policy | |||
| 54 | + | if: matrix.os == 'ubuntu-latest' | |||
| 55 | + | run: bash .github/scripts/next-release-tag_test.sh | |||
| 56 | + | ||||
| 53 | - name: golangci-lint | 57 | - name: golangci-lint | ||
| 54 | if: matrix.os == 'ubuntu-latest' | 58 | if: matrix.os == 'ubuntu-latest' | ||
| 55 | uses: golangci/golangci-lint-action@v9.3.0 | 59 | uses: golangci/golangci-lint-action@v9.3.0 | ||
M .github/workflows/release.yml +64 -3
| @@ -1,23 +1,84 @@ | |||
| 1 | 1 | name: release | |
| 2 | 2 | ||
| 3 | - | # Pushing a v* tag publishes the release. This workflow intentionally runs on | |
| 4 | - | # both ermite (publishes to git.bnema.dev) and the GitHub mirror (publishes a | |
| 5 | - | # copy on github.com, because installed `zut update` only reads GitHub). | |
| 3 | + | # Every morning at 07:00 Europe/Paris, ermite tags main when its unreleased | |
| 4 | + | # conventional commits call for a release, then publishes it in the same run (a | |
| 5 | + | # tag pushed with the job token starts no run). A v* tag pushed by hand also | |
| 6 | + | # publishes. The GitHub mirror never tags: it receives the forge's tags and | |
| 7 | + | # publishes a copy, because installed `zut update` only reads GitHub. | |
| 6 | 8 | on: | |
| 9 | + | schedule: | |
| 10 | + | - cron: "0 7 * * *" | |
| 11 | + | timezone: Europe/Paris | |
| 12 | + | workflow_dispatch: | |
| 7 | 13 | push: | |
| 8 | 14 | tags: ["v*"] | |
| 9 | 15 | ||
| 10 | 16 | permissions: | |
| 11 | 17 | contents: write | |
| 12 | 18 | ||
| 19 | + | concurrency: | |
| 20 | + | group: release | |
| 21 | + | cancel-in-progress: false | |
| 22 | + | ||
| 13 | 23 | jobs: | |
| 24 | + | tag: | |
| 25 | + | if: github.event_name != 'push' && github.server_url != 'https://github.com' | |
| 26 | + | runs-on: ubuntu-latest | |
| 27 | + | outputs: | |
| 28 | + | tag: ${{ steps.version.outputs.tag }} | |
| 29 | + | steps: | |
| 30 | + | - name: Check out main | |
| 31 | + | uses: actions/checkout@v4 | |
| 32 | + | with: | |
| 33 | + | fetch-depth: 0 | |
| 34 | + | fetch-tags: true | |
| 35 | + | ref: main | |
| 36 | + | ||
| 37 | + | - name: Compute the next release tag | |
| 38 | + | id: version | |
| 39 | + | shell: bash | |
| 40 | + | run: | | |
| 41 | + | set -euo pipefail | |
| 42 | + | if git log -1 --format=%B | grep -Fq '[release=skip]'; then | |
| 43 | + | echo "The latest commit requested that the release be skipped." | |
| 44 | + | exit 0 | |
| 45 | + | fi | |
| 46 | + | tag=$(bash .github/scripts/next-release-tag.sh) | |
| 47 | + | if [ -z "$tag" ]; then | |
| 48 | + | echo "No releasable conventional commits for this run." | |
| 49 | + | exit 0 | |
| 50 | + | fi | |
| 51 | + | echo "Releasing $tag from $(git rev-parse --short HEAD)" | |
| 52 | + | echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| 53 | + | ||
| 54 | + | - name: Create and push the tag | |
| 55 | + | if: steps.version.outputs.tag != '' | |
| 56 | + | env: | |
| 57 | + | TAG: ${{ steps.version.outputs.tag }} | |
| 58 | + | run: | | |
| 59 | + | # A retry finds the tag already on HEAD: it is published again as is. | |
| 60 | + | if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then | |
| 61 | + | exit 0 | |
| 62 | + | fi | |
| 63 | + | git config user.name "github-actions[bot]" | |
| 64 | + | git config user.email "github-actions[bot]@users.noreply.github.com" | |
| 65 | + | git tag -a "$TAG" -m "release $TAG" | |
| 66 | + | git push origin "refs/tags/$TAG" | |
| 67 | + | ||
| 14 | 68 | release: | |
| 69 | + | needs: tag | |
| 70 | + | # A pushed tag publishes on ermite and on the mirror (the tag job is | |
| 71 | + | # skipped there); a scheduled or dispatched run publishes what it tagged. | |
| 72 | + | if: >- | |
| 73 | + | !cancelled() && needs.tag.result != 'failure' && | |
| 74 | + | (github.event_name == 'push' || needs.tag.outputs.tag != '') | |
| 15 | 75 | runs-on: ubuntu-latest | |
| 16 | 76 | steps: | |
| 17 | 77 | - name: Check out the tag | |
| 18 | 78 | uses: actions/checkout@v4 | |
| 19 | 79 | with: | |
| 20 | 80 | fetch-depth: 0 | |
| 81 | + | ref: ${{ needs.tag.outputs.tag && format('refs/tags/{0}', needs.tag.outputs.tag) || github.ref }} | |
| 21 | 82 | ||
| 22 | 83 | - name: Set up Go | |
| 23 | 84 | uses: actions/setup-go@v7 | |
| @@ -1,23 +1,84 @@ | |||||
| 1 | name: release | 1 | name: release | ||
| 2 | 2 | ||||
| 3 | - | # Pushing a v* tag publishes the release. This workflow intentionally runs on | 3 | + | # Every morning at 07:00 Europe/Paris, ermite tags main when its unreleased |
| 4 | - | # both ermite (publishes to git.bnema.dev) and the GitHub mirror (publishes a | 4 | + | # conventional commits call for a release, then publishes it in the same run (a |
| 5 | - | # copy on github.com, because installed `zut update` only reads GitHub). | 5 | + | # tag pushed with the job token starts no run). A v* tag pushed by hand also |
| 6 | + | # publishes. The GitHub mirror never tags: it receives the forge's tags and | |||
| 7 | + | # publishes a copy, because installed `zut update` only reads GitHub. | |||
| 6 | on: | 8 | on: | ||
| 9 | + | schedule: | |||
| 10 | + | - cron: "0 7 * * *" | |||
| 11 | + | timezone: Europe/Paris | |||
| 12 | + | workflow_dispatch: | |||
| 7 | push: | 13 | push: | ||
| 8 | tags: ["v*"] | 14 | tags: ["v*"] | ||
| 9 | 15 | ||||
| 10 | permissions: | 16 | permissions: | ||
| 11 | contents: write | 17 | contents: write | ||
| 12 | 18 | ||||
| 19 | + | concurrency: | |||
| 20 | + | group: release | |||
| 21 | + | cancel-in-progress: false | |||
| 22 | + | ||||
| 13 | jobs: | 23 | jobs: | ||
| 24 | + | tag: | |||
| 25 | + | if: github.event_name != 'push' && github.server_url != 'https://github.com' | |||
| 26 | + | runs-on: ubuntu-latest | |||
| 27 | + | outputs: | |||
| 28 | + | tag: ${{ steps.version.outputs.tag }} | |||
| 29 | + | steps: | |||
| 30 | + | - name: Check out main | |||
| 31 | + | uses: actions/checkout@v4 | |||
| 32 | + | with: | |||
| 33 | + | fetch-depth: 0 | |||
| 34 | + | fetch-tags: true | |||
| 35 | + | ref: main | |||
| 36 | + | ||||
| 37 | + | - name: Compute the next release tag | |||
| 38 | + | id: version | |||
| 39 | + | shell: bash | |||
| 40 | + | run: | | |||
| 41 | + | set -euo pipefail | |||
| 42 | + | if git log -1 --format=%B | grep -Fq '[release=skip]'; then | |||
| 43 | + | echo "The latest commit requested that the release be skipped." | |||
| 44 | + | exit 0 | |||
| 45 | + | fi | |||
| 46 | + | tag=$(bash .github/scripts/next-release-tag.sh) | |||
| 47 | + | if [ -z "$tag" ]; then | |||
| 48 | + | echo "No releasable conventional commits for this run." | |||
| 49 | + | exit 0 | |||
| 50 | + | fi | |||
| 51 | + | echo "Releasing $tag from $(git rev-parse --short HEAD)" | |||
| 52 | + | echo "tag=$tag" >> "$GITHUB_OUTPUT" | |||
| 53 | + | ||||
| 54 | + | - name: Create and push the tag | |||
| 55 | + | if: steps.version.outputs.tag != '' | |||
| 56 | + | env: | |||
| 57 | + | TAG: ${{ steps.version.outputs.tag }} | |||
| 58 | + | run: | | |||
| 59 | + | # A retry finds the tag already on HEAD: it is published again as is. | |||
| 60 | + | if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then | |||
| 61 | + | exit 0 | |||
| 62 | + | fi | |||
| 63 | + | git config user.name "github-actions[bot]" | |||
| 64 | + | git config user.email "github-actions[bot]@users.noreply.github.com" | |||
| 65 | + | git tag -a "$TAG" -m "release $TAG" | |||
| 66 | + | git push origin "refs/tags/$TAG" | |||
| 67 | + | ||||
| 14 | release: | 68 | release: | ||
| 69 | + | needs: tag | |||
| 70 | + | # A pushed tag publishes on ermite and on the mirror (the tag job is | |||
| 71 | + | # skipped there); a scheduled or dispatched run publishes what it tagged. | |||
| 72 | + | if: >- | |||
| 73 | + | !cancelled() && needs.tag.result != 'failure' && | |||
| 74 | + | (github.event_name == 'push' || needs.tag.outputs.tag != '') | |||
| 15 | runs-on: ubuntu-latest | 75 | runs-on: ubuntu-latest | ||
| 16 | steps: | 76 | steps: | ||
| 17 | - name: Check out the tag | 77 | - name: Check out the tag | ||
| 18 | uses: actions/checkout@v4 | 78 | uses: actions/checkout@v4 | ||
| 19 | with: | 79 | with: | ||
| 20 | fetch-depth: 0 | 80 | fetch-depth: 0 | ||
| 81 | + | ref: ${{ needs.tag.outputs.tag && format('refs/tags/{0}', needs.tag.outputs.tag) || github.ref }} | |||
| 21 | 82 | ||||
| 22 | - name: Set up Go | 83 | - name: Set up Go | ||
| 23 | uses: actions/setup-go@v7 | 84 | uses: actions/setup-go@v7 | ||
M CONTRIBUTING.md +4 -2
| @@ -147,13 +147,15 @@ If a validation command cannot pass because of an unrelated repository or enviro | |||
| 147 | 147 | ||
| 148 | 148 | ## Releases | |
| 149 | 149 | ||
| 150 | - | The maintainer publishes a release by pushing a signed version tag to the forge: | |
| 150 | + | Every morning at 07:00 Europe/Paris, the release workflow on git.bnema.dev tags `main` when its unreleased conventional commits call for a release (`.github/scripts/next-release-tag.sh`: `feat` or a breaking change bumps the minor version, `fix`, `perf`, `refactor`, `build`, `chore` and `ci` the patch). It then runs GoReleaser and publishes the archives and `checksums.txt`. Put `[release=skip]` in the latest commit message to skip a morning. Run it now with `ermite workflow run zut release.yml`. | |
| 151 | + | ||
| 152 | + | The maintainer can also publish by pushing a signed version tag: | |
| 151 | 153 | ||
| 152 | 154 | ```sh | |
| 153 | 155 | git tag -s vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z | |
| 154 | 156 | ``` | |
| 155 | 157 | ||
| 156 | - | The tag triggers the release workflow, which runs GoReleaser and publishes the archives and `checksums.txt` on git.bnema.dev. The GitHub mirror receives the same tag and publishes a copy, because installed `zut update` builds read releases from GitHub only. | |
| 158 | + | The GitHub mirror receives the tags and publishes a copy, because installed `zut update` builds read releases from GitHub only. | |
| 157 | 159 | ||
| 158 | 160 | ## Pull request shape | |
| 159 | 161 | ||
| @@ -147,13 +147,15 @@ If a validation command cannot pass because of an unrelated repository or enviro | |||||
| 147 | 147 | ||||
| 148 | ## Releases | 148 | ## Releases | ||
| 149 | 149 | ||||
| 150 | - | The maintainer publishes a release by pushing a signed version tag to the forge: | 150 | + | Every morning at 07:00 Europe/Paris, the release workflow on git.bnema.dev tags `main` when its unreleased conventional commits call for a release (`.github/scripts/next-release-tag.sh`: `feat` or a breaking change bumps the minor version, `fix`, `perf`, `refactor`, `build`, `chore` and `ci` the patch). It then runs GoReleaser and publishes the archives and `checksums.txt`. Put `[release=skip]` in the latest commit message to skip a morning. Run it now with `ermite workflow run zut release.yml`. |
| 151 | + | ||||
| 152 | + | The maintainer can also publish by pushing a signed version tag: | |||
| 151 | 153 | ||||
| 152 | ```sh | 154 | ```sh | ||
| 153 | git tag -s vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z | 155 | git tag -s vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z | ||
| 154 | ``` | 156 | ``` | ||
| 155 | 157 | ||||
| 156 | - | The tag triggers the release workflow, which runs GoReleaser and publishes the archives and `checksums.txt` on git.bnema.dev. The GitHub mirror receives the same tag and publishes a copy, because installed `zut update` builds read releases from GitHub only. | 158 | + | The GitHub mirror receives the tags and publishes a copy, because installed `zut update` builds read releases from GitHub only. |
| 157 | 159 | ||||
| 158 | ## Pull request shape | 160 | ## Pull request shape | ||
| 159 | 161 | ||||