Skip to content

Commit 6d90c29

Browse files
Merge pull request #50 from GNS-Science/check-version-for-release
Enforce presence of changelog entry that matches the `v*` version tag when releasing python package. - changelog-reader-action was already present in our workflow which will fail if a changelog entry for the version is not present. It has been moved higher in the workflow to fail early. - a new git pre-push hook that can be added to any project to prevent missing changelog entry
2 parents 67700d1 + b859e81 commit 6d90c29

7 files changed

Lines changed: 128 additions & 26 deletions

File tree

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hooks/pre-push-changelog text eol=lf

.github/workflows/python-release-uv.yml

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ on:
3333
required: false
3434
type: boolean
3535
default: true
36+
tag-prefix:
37+
description: Prefix stripped from the pushed tag to derive the version
38+
required: false
39+
type: string
40+
default: "v"
41+
changelog-path:
42+
description: Path to the changelog file relative to working-directory
43+
required: false
44+
type: string
45+
default: "CHANGELOG.md"
3646

3747

3848
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
@@ -53,32 +63,31 @@ jobs:
5363

5464
- uses: actions/checkout@v5
5565

56-
- name: install_package
57-
uses: GNS-Science/nshm-github-actions/.github/actions/python-install-uv@main
58-
with:
59-
python-version: ${{ inputs.python-version }}
60-
uv-version: ${{ inputs.uv-version }}
61-
working-directory: ${{ inputs.working-directory }}
62-
6366
#----------------------------------------------
64-
# get version
67+
# get version and verify changelog entry
6568
#----------------------------------------------
6669
- name: Get version from tag
6770
id: tag_name
71+
if: startsWith(github.ref, 'refs/tags/')
6872
run: |
69-
echo name=current_version::${GITHUB_REF#refs/tags/v} >> "$GITHUB_OUTPUT"
73+
echo "current_version=${GITHUB_REF_NAME#${{ inputs.tag-prefix }}}" >> "$GITHUB_OUTPUT"
7074
shell: bash
7175

72-
#----------------------------------------------
73-
# get changelog entry
74-
#----------------------------------------------
7576
- name: Get Changelog Entry
7677
id: changelog_reader
78+
if: startsWith(github.ref, 'refs/tags/')
7779
uses: GNS-Science/changelog-reader-action@master
7880
with:
7981
validation_depth: 10
8082
version: ${{ steps.tag_name.outputs.current_version }}
81-
path: ${{ inputs.working-directory }}/CHANGELOG.md
83+
path: ${{ inputs.working-directory }}/${{ inputs.changelog-path }}
84+
85+
- name: install_package
86+
uses: GNS-Science/nshm-github-actions/.github/actions/python-install-uv@main
87+
with:
88+
python-version: ${{ inputs.python-version }}
89+
uv-version: ${{ inputs.uv-version }}
90+
working-directory: ${{ inputs.working-directory }}
8291

8392
#----------------------------------------------
8493
# build wheels and tarball

.github/workflows/python-release.yml

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ on:
3333
required: false
3434
type: boolean
3535
default: true
36+
tag-prefix:
37+
description: Prefix stripped from the pushed tag to derive the version
38+
required: false
39+
type: string
40+
default: "v"
41+
changelog-path:
42+
description: Path to the changelog file relative to working-directory
43+
required: false
44+
type: string
45+
default: "CHANGELOG.md"
3646

3747

3848
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
@@ -53,32 +63,31 @@ jobs:
5363

5464
- uses: actions/checkout@v5
5565

56-
- name: install_package
57-
uses: GNS-Science/nshm-github-actions/.github/actions/python-install@main
58-
with:
59-
python-version: ${{ inputs.python-version }}
60-
poetry-version: ${{ inputs.poetry-version }}
61-
working-directory: ${{ inputs.working-directory }}
62-
6366
#----------------------------------------------
64-
# get version
67+
# get version and verify changelog entry
6568
#----------------------------------------------
6669
- name: Get version from tag
6770
id: tag_name
71+
if: startsWith(github.ref, 'refs/tags/')
6872
run: |
69-
echo name=current_version::${GITHUB_REF#refs/tags/v} >> "$GITHUB_OUTPUT"
73+
echo "current_version=${GITHUB_REF_NAME#${{ inputs.tag-prefix }}}" >> "$GITHUB_OUTPUT"
7074
shell: bash
7175

72-
#----------------------------------------------
73-
# get changelog entry
74-
#----------------------------------------------
7576
- name: Get Changelog Entry
7677
id: changelog_reader
78+
if: startsWith(github.ref, 'refs/tags/')
7779
uses: GNS-Science/changelog-reader-action@master
7880
with:
7981
validation_depth: 10
8082
version: ${{ steps.tag_name.outputs.current_version }}
81-
path: ${{ inputs.working-directory }}/CHANGELOG.md
83+
path: ${{ inputs.working-directory }}/${{ inputs.changelog-path }}
84+
85+
- name: install_package
86+
uses: GNS-Science/nshm-github-actions/.github/actions/python-install@main
87+
with:
88+
python-version: ${{ inputs.python-version }}
89+
poetry-version: ${{ inputs.poetry-version }}
90+
working-directory: ${{ inputs.working-directory }}
8291

8392
#----------------------------------------------
8493
# build wheels and tarball

README.MD

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Creates a GitHub release and publishes the package to PyPI. Would commonly be on
3030
3131
Requires the secret `PYPI_API_TOKEN`.
3232

33+
When triggered by a version tag, the workflow verifies that `CHANGELOG.md` contains a `## [VERSION]` section matching the tag before proceeding. It fails fast (before installing dependencies) if the entry is missing.
34+
3335
Example use:
3436

3537
```yml
@@ -38,6 +40,8 @@ jobs:
3840
uses: GNS-Science/nshm-github-actions/.github/workflows/python-release.yml@main
3941
with:
4042
python-version: '3.12'
43+
# tag-prefix: 'v' # default: v
44+
# changelog-path: 'CHANGELOG.md' # default: CHANGELOG.md
4145
secrets: inherit
4246
```
4347

@@ -83,12 +87,16 @@ jobs:
8387

8488
Creates a GitHub release and publishes the package to PyPI using `uv build`. Requires the secret `PYPI_API_TOKEN`.
8589

90+
Same changelog-entry gate as the Poetry variant above.
91+
8692
```yml
8793
jobs:
8894
release-and-distribute:
8995
uses: GNS-Science/nshm-github-actions/.github/workflows/python-release-uv.yml@main
9096
with:
9197
python-version: '3.12'
98+
# tag-prefix: 'v' # default: v
99+
# changelog-path: 'CHANGELOG.md' # default: CHANGELOG.md
92100
secrets: inherit
93101
```
94102

@@ -121,3 +129,30 @@ Example use:
121129
```
122130

123131
See [deploy-to-aws.yml](./.github/workflows/deploy-to-aws.yml) for a list of supported and required list of secrets and environment variables as well as workflow inputs.
132+
133+
## Pre-push changelog hook
134+
135+
A local git hook is available at [`hooks/pre-push-changelog`](./hooks/pre-push-changelog). It performs the same changelog-entry check as the release workflows, blocking a version tag push before it reaches CI.
136+
137+
### Install
138+
139+
Run once per repository checkout. On Windows, use Git Bash (not PowerShell or cmd).
140+
141+
```bash
142+
mkdir -p .githooks
143+
curl -o .githooks/pre-push \
144+
https://raw.githubusercontent.com/GNS-Science/nshm-github-actions/main/hooks/pre-push-changelog
145+
chmod +x .githooks/pre-push
146+
git config core.hooksPath .githooks
147+
```
148+
149+
The hook can be bypassed with `git push --no-verify`, but CI will still enforce the check.
150+
151+
### Configuration
152+
153+
Override defaults via environment variables (e.g. via `direnv` or your shell profile):
154+
155+
| Variable | Default | Description |
156+
|---|---|---|
157+
| `TAG_PREFIX` | `v` | Prefix stripped from the tag name to derive the version |
158+
| `CHANGELOG_PATH` | `CHANGELOG.md` | Path to the changelog file (relative to repo root) |

hooks/pre-push-changelog

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Prevent pushing a version tag unless the changelog has a matching entry.
5+
# Override defaults via env vars: CHANGELOG_PATH, TAG_PREFIX
6+
CHANGELOG_PATH="${CHANGELOG_PATH:-CHANGELOG.md}"
7+
TAG_PREFIX="${TAG_PREFIX:-v}"
8+
9+
while read -r local_ref local_sha remote_ref remote_sha; do
10+
case "${local_ref}" in
11+
refs/tags/${TAG_PREFIX}*)
12+
version="${local_ref#refs/tags/${TAG_PREFIX}}"
13+
if ! grep -qE "^## \[${version}\]" "${CHANGELOG_PATH}"; then
14+
echo "ERROR: ${CHANGELOG_PATH} is missing a '## [${version}]' section for tag ${local_ref#refs/tags/}." >&2
15+
echo "Update ${CHANGELOG_PATH}, delete the tag (git tag -d ${local_ref#refs/tags/}), commit, and re-tag." >&2
16+
exit 1
17+
fi
18+
;;
19+
esac
20+
done
21+
22+
exit 0

samplePythonProject/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
## [0.1.0] - 2024-01-01
11+
12+
### Added
13+
- Initial release fixture for nshm-github-actions workflow testing.

samplePythonProjectUv/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
## [0.1.0] - 2024-01-01
11+
12+
### Added
13+
- Initial release fixture for nshm-github-actions workflow testing.

0 commit comments

Comments
 (0)