diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 6584577..65c36cb 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -50,7 +50,8 @@ permissions: {} jobs: tag: - name: Tag + # Shown under the caller's job, such as `tag / Major tags`. + name: Major tags runs-on: ubuntu-latest environment: ${{ inputs.environment }} permissions: @@ -78,24 +79,32 @@ jobs: REPOSITORY: ${{ github.repository }} run: | set -euo pipefail + # Each outcome is a notice on the run, and a line of the job's summary. + summary="${GITHUB_STEP_SUMMARY:-/dev/null}" + echo "## Major tags" >> "$summary" + report() { + echo "::notice title=Tag::$1" + echo "- $1" >> "$summary" + } # The version tags, vX.Y.Z only, oldest first. versions=$(git tag --list 'v*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V || true) if [ -z "$versions" ]; then - echo "There's no version tag vX.Y.Z, so there's no major tag to move." + report "There's no version tag vX.Y.Z, so there's no major tag to move." exit 0 fi for major in $(echo "$versions" | sed -E 's/^(v[0-9]+)\..*$/\1/' | sort -u); do newest=$(echo "$versions" | grep -E "^$major\." | tail -n 1) # The commit of a tag, also of an annotated one. target=$(git rev-parse "$newest^{commit}") + short=$(git rev-parse --short "$target") current=$(git rev-parse --verify --quiet "refs/tags/$major^{commit}" || true) if [ "$current" = "$target" ]; then - echo "$major is at $newest, $target, already." + report "$major is at $newest, $short, already." elif [ -n "$current" ]; then gh api --method PATCH "repos/$REPOSITORY/git/refs/tags/$major" -f sha="$target" -F force=true > /dev/null - echo "Moved $major to $newest, $target." + report "Moved $major to $newest, $short." else gh api "repos/$REPOSITORY/git/refs" -f ref="refs/tags/$major" -f sha="$target" > /dev/null - echo "Tagged $newest's commit, $target, $major." + report "Made $major at $newest, $short." fi done diff --git a/README.md b/README.md index d898e0a..9e989de 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ jobs: It works alongside [TagBot](https://github.com/JuliaRegistries/TagBot), which tags each release `vX.Y.Z` once the registry records it, at the commit that the registry records, with its GitHub release, but never moves a major tag; for a package in a subdirectory, TagBot's `tag_prefix: NO_PREFIX` makes its tags `vX.Y.Z` rather than `-vX.Y.Z`. The tag workflow follows whatever version tags exist, whenever they appear, including those that a maintainer pushes by hand: a major tag moves only to its major version's newest release, by version, so an older version's tag doesn't move it, pre-releases such as `v2.0.0-rc1` are left out, and a rerun changes nothing. So callers of the major tag get a release once it's registered and tagged, and never a release whose tag doesn't exist yet. +For each major tag, it reports what it did, made it, moved it or left it, as a notice on the run and a line of the job's summary, under the job ` / Major tags`. It moves the tags with the ReleaseFlow app's token, or else `RELEASEFLOW_TOKEN`, or else the job's own token; a ruleset on `v*` tags must let that token through, such as with the app on its bypass list. Its triggers run on the default branch or on a tag, not on the main branch, so the `releaseflow` environment must allow them, as described in [The ReleaseFlow environment](#the-releaseflow-environment), or the caller passes `with: { environment: '' }` to run the job without an environment, with the job's own token. diff --git a/ReleaseFlow/CHANGELOG.md b/ReleaseFlow/CHANGELOG.md index 4f3ea2c..a7fdff1 100644 --- a/ReleaseFlow/CHANGELOG.md +++ b/ReleaseFlow/CHANGELOG.md @@ -8,7 +8,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added -- The reusable workflow `tag.yml`, which moves each major tag, such as `v1`, to the newest release of its major version, `vX.Y.Z`, whichever made the tag, TagBot or a maintainer, and whenever, for a repository that callers follow by its major tag; it never moves a major tag backwards, and leaves pre-releases out. +- The reusable workflow `tag.yml`, which moves each major tag, such as `v1`, to the newest release of its major version, `vX.Y.Z`, whichever made the tag, TagBot or a maintainer, and whenever, for a repository that callers follow by its major tag; it never moves a major tag backwards, leaves pre-releases out, and reports what it did for each major tag as a notice on the run and a line of the job's summary. - This repository moves `v1` with it, through `Self-Tag.yml`, once TagBot tags a release `vX.Y.Z`, with `tag_prefix: NO_PREFIX`, in place of `ReleaseFlow-vX.Y.Z`, and with the ReleaseFlow app's token, which the ruleset on `v*` tags lets through; so the workflows' callers get a release once ReleaseFlow's is registered. ## [1.0.1] - 2026-10-20 diff --git a/ReleaseFlow/test/tag.jl b/ReleaseFlow/test/tag.jl index 040f0a6..2ddb2a4 100644 --- a/ReleaseFlow/test/tag.jl +++ b/ReleaseFlow/test/tag.jl @@ -13,33 +13,41 @@ work = repository() git(work, "config", "tag.gpgsign", "false") commits = [commit!(work, "Commit $(i)", "file.txt" => string(i)) for i in 1:6] + summary = joinpath(directory, "summary.md") function step() write(log, "") - environment = Dict("PATH" => "$(bin):$(ENV["PATH"])", "LOG" => log, "REPOSITORY" => "Example/Toy.jl") + write(summary, "") + environment = Dict("PATH" => "$(bin):$(ENV["PATH"])", "LOG" => log, "REPOSITORY" => "Example/Toy.jl", "GITHUB_STEP_SUMMARY" => summary) output = IOBuffer() process = run(pipeline(Cmd(addenv(`bash $(script)`, environment); dir = work, ignorestatus = true); stdout = output, stderr = output)) - return (code = process.exitcode, output = String(take!(output)), log = read(log, String)) + return (code = process.exitcode, output = String(take!(output)), log = read(log, String), summary = read(summary, String)) end + short(commit) = git(work, "rev-parse", "--short", commit) tag!(name, commit) = git(work, "tag", "-f", name, commit) created(major, commit) = "gh api repos/Example/Toy.jl/git/refs -f ref=refs/tags/$(major) -f sha=$(commit)" moved(major, commit) = "gh api --method PATCH repos/Example/Toy.jl/git/refs/tags/$(major) -f sha=$(commit) -F force=true" # Without version tags, there's nothing to move. result = step() - @test result.code == 0 && occursin("There's no version tag vX.Y.Z", result.output) && isempty(result.log) + @test result.code == 0 && isempty(result.log) + @test occursin("::notice title=Tag::There's no version tag vX.Y.Z", result.output) + @test result.summary == "## Major tags\n- There's no version tag vX.Y.Z, so there's no major tag to move.\n" # The newest release of a major version without its major tag: the major tag is made at its commit. tag!("v1.0.0", commits[1]) tag!("v1.0.1", commits[2]) result = step() @test result.code == 0 && result.log == created("v1", commits[2]) * "\n" + @test occursin("::notice title=Tag::Made v1 at v1.0.1, $(short(commits[2])).", result.output) # A major tag at an older release moves to the newest. tag!("v1", commits[1]) result = step() @test result.log == moved("v1", commits[2]) * "\n" + @test occursin("::notice title=Tag::Moved v1 to v1.0.1, $(short(commits[2])).", result.output) # A major tag at the newest release stays. tag!("v1", commits[2]) result = step() - @test isempty(result.log) && occursin("v1 is at v1.0.1, $(commits[2]), already.", result.output) + @test isempty(result.log) && occursin("::notice title=Tag::v1 is at v1.0.1, $(short(commits[2])), already.", result.output) + @test result.summary == "## Major tags\n- v1 is at v1.0.1, $(short(commits[2])), already.\n" # Each major version's tag follows its own newest release, by version rather than by date, and pre-releases are left out. tag!("v1.10.0", commits[3]) tag!("v1.9.0", commits[4]) @@ -47,6 +55,8 @@ tag!("v2.1.0-rc1", commits[6]) result = step() @test result.log == moved("v1", commits[3]) * "\n" * created("v2", commits[5]) * "\n" + # One line for each major tag. + @test result.summary == "## Major tags\n- Moved v1 to v1.10.0, $(short(commits[3])).\n- Made v2 at v2.0.0, $(short(commits[5])).\n" # A version tag pushed by hand, older than the newest, moves nothing. tag!("v1", commits[3]) tag!("v2", commits[5])