Merge 98d8255ddc39ad6ff61104fed1c352b3e16e78e6 into 6baf0bbb67a874c59bc9ea1831b5c8fc41d5f764
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 | ||||