Skip to content

Resolve conflicts: scalar-generated into scalar-next #4

Resolve conflicts: scalar-generated into scalar-next

Resolve conflicts: scalar-generated into scalar-next #4

name: Release PR Version
on:
# `edited` is not one of the default pull_request types, but it is the point of this
# workflow: retitling the release PR is how a maintainer picks an exact version. The
# other types keep the consistency check attached to the PR as it evolves.
pull_request:
types: [opened, reopened, edited, synchronize]
# Read-only by default; only the job that pushes the Release-As commit widens this.
permissions:
contents: read
jobs:
version-consistency:
# Also the check name the release PR's footer tells maintainers to wait for.
name: Release PR version
# Only the platform's release PR carries a version in its title; every other pull request
# skips this job, which GitHub reports as neutral. The release may be presented from
# scalar-next itself or from the release-please branch rendered off it, so both heads
# count as the release PR.
if: >-
${{ github.event.pull_request.base.ref == 'main'
&& (github.event.pull_request.head.ref == 'scalar-next'
|| startsWith(github.event.pull_request.head.ref, 'release-please--branches--scalar-next--')) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
# The version committed on the PR head is what merging would actually release.
ref: ${{ github.event.pull_request.head.sha }}
# Fails while the title version and the committed version disagree — the window between
# a maintainer's retitle and the platform re-rendering the PR from it. Without this a
# merge in that window would tag a release whose own files self-report the old version,
# and a title release-please cannot parse would silently cut no release at all.
- name: Compare the title version with the committed version
env:
# The title is human input, so it is bound through the environment (never
# interpolated into the script) and matched against a strict semver pattern
# before it is read.
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
pattern='^release: ((0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?)$'
if [[ ! "$PR_TITLE" =~ $pattern ]]; then
echo "::error::Release PR title must be \"release: X.Y.Z\" with a full semver version, got: $PR_TITLE"
exit 1
fi
title_version="${BASH_REMATCH[1]}"
manifest_version="$(jq -r '.["."]' .release-please-manifest.json)"
if [[ "$title_version" != "$manifest_version" ]]; then
echo "::error::Release PR title says $title_version but the pull request is versioned $manifest_version. Wait for the release PR to be re-rendered at $title_version before merging."
exit 1
fi
echo "Release PR title and committed version agree on $title_version."
apply-title-version:
name: Apply edited release version
# A human retitle of the open release PR is bridged into the canonical explicit-version
# mechanism (a Release-As commit on scalar-next, which the platform re-renders the
# release PR from). `changes.title` is only set when the title itself changed, and bot
# senders are ignored so the platform's own retitles cannot bounce back into another
# commit.
if: >-
${{ github.event.action == 'edited'
&& github.event.changes.title != null
&& github.event.sender.type != 'Bot'
&& github.event.pull_request.state == 'open'
&& github.event.pull_request.base.ref == 'main'
&& (github.event.pull_request.head.ref == 'scalar-next'
|| startsWith(github.event.pull_request.head.ref, 'release-please--branches--scalar-next--')) }}
# One bridge run at a time per pull request, newest retitle wins. Two retitles in quick
# succession (a version typo corrected seconds later) would otherwise start two runs that
# both read the version committed on the PR head, both get past the no-op guard, and both
# push — and since each fetches scalar-next immediately before committing, the second
# push fast-forwards instead of failing. release-please honours the newest Release-As
# footer, so the release would land on whichever run happened to push last, not on the
# title the maintainer actually left behind.
concurrency:
group: release-title-edit-${{ github.event.pull_request.number }}
cancel-in-progress: true
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
# The PR head, so the version the pull request currently carries can be read before
# deciding whether anything needs to change.
ref: ${{ github.event.pull_request.head.sha }}
- name: Push a Release-As commit for the edited version
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
set -euo pipefail
pattern='^release: ((0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?)$'
if [[ ! "$PR_TITLE" =~ $pattern ]]; then
echo "::error::Release PR title must be \"release: X.Y.Z\" with a full semver version, got: $PR_TITLE"
exit 1
fi
# Only ever a validated semver from here on, so it is safe in a commit message.
version="${BASH_REMATCH[1]}"
manifest_version="$(jq -r '.["."]' .release-please-manifest.json)"
# The re-rendered PR is retitled to the version it now carries; stopping here keeps
# that from producing an endless chain of Release-As commits.
if [[ "$version" == "$manifest_version" ]]; then
echo "This release PR already carries $version; nothing to do."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git fetch origin scalar-next
git checkout -B scalar-next origin/scalar-next
git commit --allow-empty -m "chore: release $version" -m "Release-As: $version"
# Plain (non-force) push: losing a race against a regeneration push fails loudly
# rather than discarding it, and the retitle can simply be repeated.
git push origin scalar-next
echo "Pushed Release-As: $version to scalar-next. The release PR will be re-rendered at that version."