Skip to content
Closed
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: 29 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
# Cut a release from GitHub: Actions → Release → Run workflow (no local git required).
# Bumps from the latest published release tag; works with protected master (no direct push).
# See release.md.
# Opens a PR to sync the Makefile after publishing.
# See release.md
name: Release

on:
workflow_dispatch:
inputs:
bump:
description: "Bump from latest release (e.g. v1.3.0 + patch → v1.3.1)"
description: Version bump
required: true
type: choice
default: patch
Expand Down Expand Up @@ -74,14 +71,13 @@ jobs:
esac
VERSION="${MAJOR}.${MINOR}.${PATCH}"
TAG="v${VERSION}"
echo "Bump: ${BUMP} → releasing ${VERSION}"
echo "Bump: ${BUMP} → ${VERSION} (was ${CURRENT})"
if git ls-remote --exit-code origin "refs/tags/${TAG}" >/dev/null 2>&1; then
echo "Tag ${TAG} already exists on remote"
exit 1
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "previous=${CURRENT}" >> "$GITHUB_OUTPUT"

- name: Check release does not exist
env:
Expand All @@ -94,6 +90,19 @@ jobs:
exit 1
fi

- name: Update Makefile to release version (workspace only)
env:
VERSION: ${{ steps.meta.outputs.version }}
run: |
set -euo pipefail
sed -i "s/^VERSION ?=.*/VERSION ?= ${VERSION}/" Makefile
MAKEFILE_VERSION=$(sed -n 's/^VERSION ?=[[:space:]]*//p' Makefile | head -1)
if [ "${MAKEFILE_VERSION}" != "${VERSION}" ]; then
echo "Makefile VERSION is ${MAKEFILE_VERSION}, expected ${VERSION}"
exit 1
fi
echo "Makefile in this job is now VERSION=${VERSION} (not committed to master yet)"

- name: Run tests
run: |
go test ./... -count=1
Expand All @@ -105,13 +114,18 @@ jobs:
VERSION: ${{ steps.meta.outputs.version }}
run: |
set -euo pipefail
MAKEFILE_VERSION=$(sed -n 's/^VERSION ?=[[:space:]]*//p' Makefile | head -1)
if [ "${MAKEFILE_VERSION}" != "${VERSION}" ]; then
echo "Refusing to build: Makefile=${MAKEFILE_VERSION} release=${VERSION}"
exit 1
fi
LDFLAGS="-s -w -X dwatch/cmd.Version=${VERSION}"
mkdir -p dist

build() {
local goos=$1 goarch=$2
local base="dwatch_${TAG}_${goos}_${goarch}"
echo "Building ${goos}/${goarch}..."
echo "Building ${goos}/${goarch} version ${VERSION}..."
GOOS=$goos GOARCH=$goarch CGO_ENABLED=0 go build -ldflags "${LDFLAGS}" -o dwatch .
tar -czf "dist/${base}.tar.gz" dwatch
rm dwatch
Expand Down Expand Up @@ -140,15 +154,14 @@ jobs:
TAG: ${{ steps.meta.outputs.tag }}
run: |
set -euo pipefail
CURRENT=$(sed -n 's/^VERSION ?=[[:space:]]*//p' Makefile | head -1)
if [ "${CURRENT}" = "${VERSION}" ]; then
echo "Makefile already at ${VERSION}; no PR needed"
REMOTE_VERSION=$(git show origin/master:Makefile | sed -n 's/^VERSION ?=[[:space:]]*//p' | head -1)
if [ "${REMOTE_VERSION}" = "${VERSION}" ]; then
echo "master Makefile already at ${VERSION}"
exit 0
fi
BRANCH="chore/bump-version-${VERSION}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -b "${BRANCH}"
git fetch origin master
git checkout -b "${BRANCH}" origin/master
sed -i "s/^VERSION ?=.*/VERSION ?= ${VERSION}/" Makefile
git add Makefile
git commit -m "chore: bump VERSION to ${VERSION}"
Expand All @@ -157,5 +170,5 @@ jobs:
--base master \
--head "${BRANCH}" \
--title "chore: bump VERSION to ${VERSION}" \
--body "Automated after ${TAG}. Merge to keep the Makefile in sync with the latest release."
gh pr merge --auto --squash || echo "Enable auto-merge on the PR or merge manually"
--body "Automated after ${TAG}. Merge so master Makefile matches the release."
gh pr merge --auto --squash || echo "Merge manually after CI passes"
42 changes: 24 additions & 18 deletions release.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,45 @@
# Releasing dwatch

Releases are cut from GitHub Actions on protected `master` — no local `git tag` or push required.
Releases are cut from GitHub Actions. Branch protection on `master` stays enabled.

Pre-built binaries for end users are listed on the [GitHub releases](https://github.com/silverbucket/dwatch/releases) page.
Pre-built binaries: [GitHub releases](https://github.com/silverbucket/dwatch/releases).

## One-time setup: `release` environment

In the repo on GitHub: **Settings** → **Environments** → **New environment** → name it `release`.
**Settings** → **Environments** → **New environment** → `release`.

Recommended for a protected `master` branch:
Recommended:

- **Required reviewers** — one or more people must approve before binaries are published
- **Deployment branches** — limit to `master` only (releases are built from `master` HEAD)
- **Required reviewers** before publish
- **Deployment branches** → `master` only

Ensure **Settings** → **Actions** → **General** → **Workflow permissions** is **Read and write**.
**Settings** → **Actions** → **General** → workflow permissions **Read and write**.

## Cut a release

1. **Actions** → **Release** → **Run workflow** — choose **patch** (default), **minor**, or **major**
2. CI reads the **latest published release** (e.g. `v1.3.0`), computes the next version (`1.3.1` for patch), runs tests, and publishes binaries
3. Approve the **release** environment deployment if reviewers are configured
4. Merge the automated **Makefile bump PR** when it appears (required for protected `master`; keeps `make build` in sync)
1. **Actions** → **Release** → **Run workflow** → **patch** (default), **minor**, or **major**
2. Approve the **release** environment if reviewers are configured
3. When the job finishes, merge the **Makefile bump PR** if one was opened (so `master` matches the release)

The workflow refuses to publish if that tag or GitHub Release already exists.
The workflow refuses to publish if that tag or release already exists.

## Version bumps

| Bump | Example (`v1.3.0` latest) |
|------|-----------------------------|
| **patch** (default) | `v1.3.1` |
| Bump | Example (latest `v1.3.0`) |
|------|---------------------------|
| **patch** | `v1.3.1` |
| **minor** | `v1.4.0` |
| **major** | `v2.0.0` |

Current version is taken from the latest GitHub release tag. If there are no releases yet, the workflow falls back to `VERSION` in the Makefile.
The starting point is the latest stable GitHub release tag (drafts and pre-releases excluded). If there are no releases yet, the Makefile `VERSION` on `master` is used.

## Protected `master`
## What the workflow does (order of operations)

The release job does **not** push directly to `master`. After a successful publish, it opens a pull request to update `VERSION` in the Makefile. Merge that PR so local `make build` matches the release.
1. **Compute** the new version from the latest release plus your bump choice.
2. **Edit the Makefile in the CI workspace** to that version (this happens **before** build; it does not commit to `master` yet).
3. **Run tests** on that tree.
4. **Build** binaries. The job checks that the Makefile matches the release version, then compiles with `-ldflags` so `dwatch --version` is correct.
5. **Publish** the GitHub Release and upload tarballs.
6. **Open a pull request** to update `VERSION` on `master`, because branch protection requires a PR for commits on `master`.

Step 6 does not change what is inside the tarballs from step 4. It only brings `master` in line for anyone cloning the repo and running `make build` locally.
Loading