diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b4b5255 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +hooks/pre-push-changelog text eol=lf diff --git a/.github/workflows/python-release-uv.yml b/.github/workflows/python-release-uv.yml index ed4197a..c27977a 100644 --- a/.github/workflows/python-release-uv.yml +++ b/.github/workflows/python-release-uv.yml @@ -33,6 +33,16 @@ on: required: false type: boolean default: true + tag-prefix: + description: Prefix stripped from the pushed tag to derive the version + required: false + type: string + default: "v" + changelog-path: + description: Path to the changelog file relative to working-directory + required: false + type: string + default: "CHANGELOG.md" # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. @@ -53,32 +63,31 @@ jobs: - uses: actions/checkout@v5 - - name: install_package - uses: GNS-Science/nshm-github-actions/.github/actions/python-install-uv@main - with: - python-version: ${{ inputs.python-version }} - uv-version: ${{ inputs.uv-version }} - working-directory: ${{ inputs.working-directory }} - #---------------------------------------------- - # get version + # get version and verify changelog entry #---------------------------------------------- - name: Get version from tag id: tag_name + if: startsWith(github.ref, 'refs/tags/') run: | - echo name=current_version::${GITHUB_REF#refs/tags/v} >> "$GITHUB_OUTPUT" + echo "current_version=${GITHUB_REF_NAME#${{ inputs.tag-prefix }}}" >> "$GITHUB_OUTPUT" shell: bash - #---------------------------------------------- - # get changelog entry - #---------------------------------------------- - name: Get Changelog Entry id: changelog_reader + if: startsWith(github.ref, 'refs/tags/') uses: GNS-Science/changelog-reader-action@master with: validation_depth: 10 version: ${{ steps.tag_name.outputs.current_version }} - path: ${{ inputs.working-directory }}/CHANGELOG.md + path: ${{ inputs.working-directory }}/${{ inputs.changelog-path }} + + - name: install_package + uses: GNS-Science/nshm-github-actions/.github/actions/python-install-uv@main + with: + python-version: ${{ inputs.python-version }} + uv-version: ${{ inputs.uv-version }} + working-directory: ${{ inputs.working-directory }} #---------------------------------------------- # build wheels and tarball diff --git a/.github/workflows/python-release.yml b/.github/workflows/python-release.yml index fd19b4b..ad61106 100644 --- a/.github/workflows/python-release.yml +++ b/.github/workflows/python-release.yml @@ -33,6 +33,16 @@ on: required: false type: boolean default: true + tag-prefix: + description: Prefix stripped from the pushed tag to derive the version + required: false + type: string + default: "v" + changelog-path: + description: Path to the changelog file relative to working-directory + required: false + type: string + default: "CHANGELOG.md" # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. @@ -53,32 +63,31 @@ jobs: - uses: actions/checkout@v5 - - name: install_package - uses: GNS-Science/nshm-github-actions/.github/actions/python-install@main - with: - python-version: ${{ inputs.python-version }} - poetry-version: ${{ inputs.poetry-version }} - working-directory: ${{ inputs.working-directory }} - #---------------------------------------------- - # get version + # get version and verify changelog entry #---------------------------------------------- - name: Get version from tag id: tag_name + if: startsWith(github.ref, 'refs/tags/') run: | - echo name=current_version::${GITHUB_REF#refs/tags/v} >> "$GITHUB_OUTPUT" + echo "current_version=${GITHUB_REF_NAME#${{ inputs.tag-prefix }}}" >> "$GITHUB_OUTPUT" shell: bash - #---------------------------------------------- - # get changelog entry - #---------------------------------------------- - name: Get Changelog Entry id: changelog_reader + if: startsWith(github.ref, 'refs/tags/') uses: GNS-Science/changelog-reader-action@master with: validation_depth: 10 version: ${{ steps.tag_name.outputs.current_version }} - path: ${{ inputs.working-directory }}/CHANGELOG.md + path: ${{ inputs.working-directory }}/${{ inputs.changelog-path }} + + - name: install_package + uses: GNS-Science/nshm-github-actions/.github/actions/python-install@main + with: + python-version: ${{ inputs.python-version }} + poetry-version: ${{ inputs.poetry-version }} + working-directory: ${{ inputs.working-directory }} #---------------------------------------------- # build wheels and tarball diff --git a/README.MD b/README.MD index 6ea816a..a463500 100644 --- a/README.MD +++ b/README.MD @@ -30,6 +30,8 @@ Creates a GitHub release and publishes the package to PyPI. Would commonly be on Requires the secret `PYPI_API_TOKEN`. +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. + Example use: ```yml @@ -38,6 +40,8 @@ jobs: uses: GNS-Science/nshm-github-actions/.github/workflows/python-release.yml@main with: python-version: '3.12' + # tag-prefix: 'v' # default: v + # changelog-path: 'CHANGELOG.md' # default: CHANGELOG.md secrets: inherit ``` @@ -83,12 +87,16 @@ jobs: Creates a GitHub release and publishes the package to PyPI using `uv build`. Requires the secret `PYPI_API_TOKEN`. +Same changelog-entry gate as the Poetry variant above. + ```yml jobs: release-and-distribute: uses: GNS-Science/nshm-github-actions/.github/workflows/python-release-uv.yml@main with: python-version: '3.12' + # tag-prefix: 'v' # default: v + # changelog-path: 'CHANGELOG.md' # default: CHANGELOG.md secrets: inherit ``` @@ -121,3 +129,30 @@ Example use: ``` 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. + +## Pre-push changelog hook + +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. + +### Install + +Run once per repository checkout. On Windows, use Git Bash (not PowerShell or cmd). + +```bash +mkdir -p .githooks +curl -o .githooks/pre-push \ + https://raw.githubusercontent.com/GNS-Science/nshm-github-actions/main/hooks/pre-push-changelog +chmod +x .githooks/pre-push +git config core.hooksPath .githooks +``` + +The hook can be bypassed with `git push --no-verify`, but CI will still enforce the check. + +### Configuration + +Override defaults via environment variables (e.g. via `direnv` or your shell profile): + +| Variable | Default | Description | +|---|---|---| +| `TAG_PREFIX` | `v` | Prefix stripped from the tag name to derive the version | +| `CHANGELOG_PATH` | `CHANGELOG.md` | Path to the changelog file (relative to repo root) | diff --git a/hooks/pre-push-changelog b/hooks/pre-push-changelog new file mode 100644 index 0000000..63b0e61 --- /dev/null +++ b/hooks/pre-push-changelog @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Prevent pushing a version tag unless the changelog has a matching entry. +# Override defaults via env vars: CHANGELOG_PATH, TAG_PREFIX +CHANGELOG_PATH="${CHANGELOG_PATH:-CHANGELOG.md}" +TAG_PREFIX="${TAG_PREFIX:-v}" + +while read -r local_ref local_sha remote_ref remote_sha; do + case "${local_ref}" in + refs/tags/${TAG_PREFIX}*) + version="${local_ref#refs/tags/${TAG_PREFIX}}" + if ! grep -qE "^## \[${version}\]" "${CHANGELOG_PATH}"; then + echo "ERROR: ${CHANGELOG_PATH} is missing a '## [${version}]' section for tag ${local_ref#refs/tags/}." >&2 + echo "Update ${CHANGELOG_PATH}, delete the tag (git tag -d ${local_ref#refs/tags/}), commit, and re-tag." >&2 + exit 1 + fi + ;; + esac +done + +exit 0 diff --git a/samplePythonProject/CHANGELOG.md b/samplePythonProject/CHANGELOG.md new file mode 100644 index 0000000..a0865f0 --- /dev/null +++ b/samplePythonProject/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.1.0] - 2024-01-01 + +### Added +- Initial release fixture for nshm-github-actions workflow testing. diff --git a/samplePythonProjectUv/CHANGELOG.md b/samplePythonProjectUv/CHANGELOG.md new file mode 100644 index 0000000..a0865f0 --- /dev/null +++ b/samplePythonProjectUv/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.1.0] - 2024-01-01 + +### Added +- Initial release fixture for nshm-github-actions workflow testing.