diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..a6322218 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,10 @@ + + +- [ ] Closes # +- [ ] Tests added + + +- [ ] Release note not necessary because: diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml new file mode 100644 index 00000000..42425225 --- /dev/null +++ b/.github/workflows/check-pr.yml @@ -0,0 +1,59 @@ +name: Pull Request Validation + +on: + pull_request: + branches: + - main + types: + # title changes + - edited + # initial check + - opened + - edited + - reopened + # code change (e.g. this workflow) + - synchronize + +env: + LABELS: ${{ join(github.event.pull_request.labels.*.name, '|') }} + +jobs: + # This determines if "check-relnotes" needs to be run. + check-milestone: + name: "Triage: Check PR title and release notes" + runs-on: ubuntu-latest + steps: + - name: Check if release notes are necessary + uses: kaisugi/action-regex-match@v1.0.2 + id: checked-relnotes + with: + text: ${{ github.event.pull_request.body }} + regex: '^\s*- \[x\].*Release note not necessary because:\s*(.*)$' + flags: m + - name: Check if PR title is valid + id: check-title + uses: amannn/action-semantic-pull-request@v6 + env: # Needs repo options: "Squash and merge" with commit message set to "PR title" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + outputs: + no-relnotes-reason: ${{ steps.checked-relnotes.outputs.group1 }} + type: ${{ steps.check-title.outputs.type }} + # This job verifies that the relevant release notes file has been modified. + check-relnotes: + name: Check for release notes + runs-on: ubuntu-latest + needs: check-milestone + if: github.event.pull_request.user.login != 'pre-commit-ci[bot]' && needs.check-milestone.outputs.no-relnotes-reason == '' && !contains(fromJSON('["style","refactor","test","build","ci"]'), needs.check-milestone.outputs.type) + steps: + - uses: actions/checkout@v4 + with: { filter: 'blob:none', fetch-depth: 0 } + - name: Find out if a relevant release fragment is added + uses: dorny/paths-filter@v3 + id: changes + with: + filters: | # this is intentionally a string + relnotes: 'docs/release-notes/${{ github.event.pull_request.number }}.${{ (contains(github.event.pull_request.title, '!') && 'breaking') || needs.check-milestone.outputs.type }}.md' + - name: Check if a relevant release fragment is added + uses: flying-sheep/check@v1 + with: + success: ${{ steps.changes.outputs.relnotes }} diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 4acb793c..e05fb924 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -5,10 +5,16 @@ build: tools: python: "3.14" jobs: + post_checkout: + # unshallow so version can be derived from tag + - git fetch --unshallow || true create_environment: - asdf plugin add uv - asdf install uv latest - asdf global uv latest + pre_build: + # run towncrier to preview the next version's release notes + - ( find docs/release-notes -regex '[^.]+[.][^.]+.md' | grep -q . ) && uvx hatch run docs:build-towncrier build --keep || true build: html: - uvx hatch run docs:build diff --git a/docs/contributing.md b/docs/contributing.md index 699d9429..b3c3a213 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -231,23 +231,18 @@ The CI job is defined in `.github/workflows/test.yaml`, however the single point of truth for CI jobs is the Hatch test matrix defined in `pyproject.toml`. This means that local testing via hatch and remote testing on CI tests against the same python versions and uses the same environments. -## Publishing a release +## Making a PR -### Updating the version number +All PRs must either specify they lack release notes for a good reason, or provide a [towncrier][] fragment. +This fragment's format must be `{pr number}.{type}.md` where the `type` is one of `breaking`, `perf`, `fix`, `feat`, `docs`, or `chore`. +This `type` must be the leading part of your PR title to generate a [semantic commit][]. -Before making a release, you need to update the version number in the `pyproject.toml` file. -Please adhere to [Semantic Versioning][semver], in brief +[towncrier]: https://towncrier.readthedocs.io/en/stable/ +[semantic commit]: https://www.conventionalcommits.org/en/v1.0.0/ -> Given a version number MAJOR.MINOR.PATCH, increment the: -> -> 1. MAJOR version when you make incompatible API changes, -> 2. MINOR version when you add functionality in a backwards compatible manner, and -> 3. PATCH version when you make backwards compatible bug fixes. -> -> Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. +## Publishing a release -Once you are done, commit and push your changes and navigate to the "Releases" page of this project on GitHub. -Specify `vX.X.X` as a tag name and create a release. +Specify `vX.X.X` as a tag name and create a release on the Github Releases page. For more information, see [managing GitHub releases][]. This will automatically create a git tag and trigger a Github workflow that creates a release on [PyPI][]. diff --git a/docs/release-notes/.gitkeep b/docs/release-notes/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/pyproject.toml b/pyproject.toml index 32157269..4d1a55a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ optional-dependencies.doc = [ "sphinx-toolbox>=3.8", "sphinxcontrib-bibtex>=1", "sphinxext-opengraph", + "towncrier>=24.1", ] optional-dependencies.test = [ "annbatch[zarrs]", @@ -84,6 +85,7 @@ envs.default.installer = "uv" envs.default.features = [ "dev" ] envs.docs.features = [ "doc" ] envs.docs.scripts.build = "sphinx-build -M html docs docs/_build -W {args}" +envs.docs.scripts.build-towncrier = "towncrier {args}" envs.docs.scripts.clean = "git clean -fdX -- {args:docs}" envs.docs.scripts.open = "python -m webbrowser -t docs/_build/html/index.html" envs.hatch-test.python = "3.14" @@ -178,6 +180,22 @@ run.omit = [ run.patch = [ "subprocess" ] run.source = [ "annbatch" ] +[tool.towncrier] +package = "annbatch" +directory = "docs/release-notes" +filename = "CHANGELOG.md" +title_format = "## [{version}]" +fragment_types = [ + { directory = "breaking", name = "Breaking", showcontent = true }, + { directory = "feat", name = "Feature", showcontent = true }, + { directory = "fix", name = "Fixed", showcontent = true }, + { directory = "perf", name = "Performance", showcontent = true }, + { directory = "docs", name = "Docs", showcontent = true }, + { directory = "chore", name = "Misc", showcontent = true }, + { directory = "refactor", name = "Misc", showcontent = true }, +] +issue_link_format = "https://github.com/scverse/annbatch/issues/{issue}" + [tool.cruft] skip = [ "tests",