Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/release-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Release check

# Fails if a release tag points at a commit that is not on trunk, or at a
# commit whose getVersion() does not match the tag. This cannot block the
# release, but a failed run makes the mistake visible right away. Releases
# made with ./release.sh already run these checks before tagging.

on:
push:
tags:
- 'v*'
release:
types: [published]

jobs:
check:
name: Tag matches version on trunk
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
coverage: none

- name: Check that the tagged commit is on trunk
run: |
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/trunk; then
echo "::error::Tag ${GITHUB_REF_NAME} points at ${GITHUB_SHA}, which is not on trunk. Merge the release commit into trunk."
exit 1
fi

- name: Check that getVersion() matches the tag
run: |
CODE_VERSION="$(php -r 'require "PhpcsChanged/functions.php"; echo PhpcsChanged\getVersion();')"
if [ "v${CODE_VERSION}" != "${GITHUB_REF_NAME}" ]; then
echo "::error::Tag ${GITHUB_REF_NAME} does not match getVersion() in PhpcsChanged/functions.php (${CODE_VERSION}). Bump the version before releasing."
exit 1
fi
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,15 @@ If something isn't working the way you expect, use the `--debug` option. This wi
## Inspiration

This was inspired by the amazing work in https://github.com/Automattic/phpcs-diff

## Releasing

Releases are made from `trunk` using the `release.sh` script, which requires the [gh CLI](https://cli.github.com/).

1. Open and merge a PR that updates `getVersion()` in `PhpcsChanged/functions.php` to the new version. Use a minor version bump (eg: `3.1.0`) for new features or options and a patch version bump (eg: `3.0.2`) for bug fixes only.
2. Check out `trunk` and pull the merged PR.
3. Run `./release.sh <version>` (eg: `./release.sh 3.1.0`). Add `--dry-run` to run only the checks.

The script refuses to release unless `trunk` is clean and matches `origin/trunk`, `getVersion()` returns the new version, and the new version is higher than the latest release tag. It then creates the tag and a GitHub release with generated notes at the current `trunk` commit.

If you make a release another way, the "Release check" GitHub workflow will fail when the release tag points at a commit that isn't on `trunk` or whose `getVersion()` doesn't match the tag.
95 changes: 95 additions & 0 deletions release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env bash
# release.sh — tag and publish a new phpcs-changed release on GitHub
#
# Usage:
# ./release.sh <version> eg: ./release.sh 3.1.0
# ./release.sh --dry-run <version> run all checks but do not create the release
#
# Before running this, merge a PR that sets getVersion() in
# PhpcsChanged/functions.php to <version>. The script refuses to release unless:
# - you are on trunk with no uncommitted changes to tracked files
# - local trunk matches origin/trunk, so the release commit is on the remote
# - getVersion() returns exactly <version>
# - the tag v<version> does not already exist
# - <version> is higher than the latest existing release tag
#
# It then creates the v<version> tag and GitHub release (with generated notes)
# at the current origin/trunk commit using the gh CLI.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"

fail() {
echo "Error: $*" >&2
exit 1
}

DRY_RUN=false
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN=true
shift
fi

VERSION="${1:-}"
if [[ -z "$VERSION" ]]; then
fail "Usage: $0 [--dry-run] <version> (eg: $0 3.1.0)"
fi
VERSION="${VERSION#v}"
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
fail "Version '$VERSION' must look like X.Y.Z"
fi
TAG="v$VERSION"

command -v gh >/dev/null || fail "The gh CLI is required: https://cli.github.com/"
command -v php >/dev/null || fail "php is required to read the version from PhpcsChanged/functions.php"

BRANCH="$(git branch --show-current)"
[[ "$BRANCH" == "trunk" ]] || fail "You must be on trunk to release (currently on '$BRANCH')"

if [[ -n "$(git status --porcelain --untracked-files=no)" ]]; then
fail "You have uncommitted changes; commit or stash them first"
fi

echo "Fetching origin..."
git fetch --quiet --tags origin trunk

LOCAL_SHA="$(git rev-parse HEAD)"
REMOTE_SHA="$(git rev-parse origin/trunk)"
if [[ "$LOCAL_SHA" != "$REMOTE_SHA" ]]; then
fail "Local trunk ($(git rev-parse --short HEAD)) does not match origin/trunk ($(git rev-parse --short origin/trunk)). Merge the version bump via a PR and pull before releasing."
fi

CODE_VERSION="$(php -r 'require "PhpcsChanged/functions.php"; echo PhpcsChanged\getVersion();')"
if [[ "$CODE_VERSION" != "$VERSION" ]]; then
fail "getVersion() in PhpcsChanged/functions.php returns '$CODE_VERSION', not '$VERSION'. Merge a PR bumping the version first."
fi

if git rev-parse --quiet --verify "refs/tags/$TAG" >/dev/null; then
fail "Tag $TAG already exists"
fi

LATEST_TAG="$(git tag --list 'v*' --sort=-v:refname | head -n 1)"
if [[ -n "$LATEST_TAG" ]]; then
if ! php -r 'exit(version_compare($argv[1], $argv[2], ">") ? 0 : 1);' "$VERSION" "${LATEST_TAG#v}"; then
fail "Version $VERSION is not higher than the latest release tag $LATEST_TAG"
fi
fi

echo "All checks passed: releasing $TAG at $(git rev-parse --short "$REMOTE_SHA") (previous release: ${LATEST_TAG:-none})"

if [[ "$DRY_RUN" == "true" ]]; then
echo "Dry run; not creating the release."
exit 0
fi

read -r -p "Create tag and GitHub release $TAG? [y/N] " CONFIRM
if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then
echo "Aborted."
exit 1
fi

gh release create "$TAG" --target "$REMOTE_SHA" --title "$TAG" --generate-notes
git fetch --quiet --tags origin
echo "Released $TAG."
Loading