diff --git a/CHANGELOG.md b/CHANGELOG.md index ac27f087..2170c75c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ - Cover every statement and branch in the test suite, and hold it there with `fail_under = 100`. - Store `src/django_bootstrap5/widgets.py` with LF line endings, like every other file and as `.editorconfig` requires. It was the only file in the repository with CRLF. +- Show commits that touch nothing in `src/` in `just release-check` as a second group, instead of hiding them. Tooling, CI and documentation changes belong in the release notes too, and the old output read as a complete list while being a filtered one. +- Fail `just build` when `uv.lock` does not name the version in `pyproject.toml`, and add `just relock` to fix it. `uv sync --frozen` does not check the project's own version, so a stale lock passed every packaging check. +- Say in MAINTAINING.md that `just release-check` reporting no `Unreleased` entries straight after a cut means nothing is pending, not that the changelog was lost. ## 26.3 (2026-08-28) - Drop support for Django 4.2 (EOL). diff --git a/MAINTAINING.md b/MAINTAINING.md index d2c934bc..e866a13c 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -102,10 +102,24 @@ it half-updated. Dependabot reopens it on the next run. ## Release process -1. `just release-check` — lists the commits touching `src/` since the last tag next to the current - `Unreleased` entries. Every one of those commits must be covered by an entry, or be a no-op for - users (a docstring typo, say). A code change that reaches users without an entry ships invisible. -2. On a release branch, update `CHANGELOG.md` and bump `version` in `pyproject.toml`; open a PR and merge it +1. `just release-check` — lists the commits since the last tag next to the current `Unreleased` + entries, in two groups. Every commit touching `src/` must be covered by an entry, or be a + no-op for users (a docstring typo, say). A code change that reaches users without an entry + ships invisible. The second group is everything else, and it needs reading rather than a rule: + a Dependabot bump of a dev dependency is genuinely nothing to a user, while a lint rule, a CI + gate or a contributor policy belongs in the notes under tooling. django-marina 26.3 shipped + with nine such changes unrecorded, and django-bootstrap4 and django-bootstrap5 each found + four or five more of their own, because the recipe only showed the first group. + Straight after a cut, in the window between merging the release PR and pushing the tag, this + prints `(none)` for the `Unreleased` entries next to every commit since the previous tag. + That is correct and it means nothing is pending: stamping the section removed the heading the + recipe looks for, and the tag it compares against is still the old one. It does not mean the + changelog was lost. +2. On a release branch, update `CHANGELOG.md`, bump `version` in `pyproject.toml`, and run + `just relock` so `uv.lock` names the new version; open a PR and merge it. `uv sync --frozen` + does not check the project's own version, so a stale lock otherwise survives every packaging + check unnoticed. `just build` now fails on the mismatch, which is what catches it if the + relock is forgotten. 3. Check out and pull `main` 4. `just build` — builds wheel + tarball, runs packaging checks, and smoke-tests both against an isolated env 5. `just release-tag` — creates and pushes the version tag; GitHub Actions publishes to PyPI diff --git a/justfile b/justfile index c465f5db..62ea792d 100644 --- a/justfile +++ b/justfile @@ -20,6 +20,10 @@ VERSION := `sed -n 's/^ *version.*=.*"\([^"]*\)".*/\1/p' pyproject.toml` @upgrade: uv sync --all-groups --upgrade +# Regenerate uv.lock without upgrading anything (use after bumping version) +@relock: + uv lock + # Install for development (uses uv.lock) install: uv sync --frozen --all-groups @@ -60,8 +64,20 @@ install: @tests *ARGS: uvx --with tox-uv tox {{ARGS}} +# Fail if uv.lock does not name the version in pyproject.toml. +# `uv sync --frozen` does not check the project's own version, so without this a build +# passes with a stale lock: django-bootstrap4 shipped a bump with the previous version +# still in the lock, and `just build` reported everything green. Fix with `just relock`. +[private] +@_lockfile-matches-version: + LOCKED=`awk -F'"' '/^version = /{v=$2} /editable = "\."/{print v; exit}' uv.lock`; \ + if [ "$LOCKED" != "{{ VERSION }}" ]; then \ + echo "Error - pyproject.toml says {{ VERSION }}, uv.lock says $LOCKED. Run 'just relock'."; \ + exit 1; \ + fi + # Build artefacts + packaging checks (local preflight) -@build: clean-build install +@build: clean-build install _lockfile-matches-version uv build uvx twine check dist/* uvx check-manifest @@ -82,16 +98,21 @@ install: echo "Example not found."; \ fi -# Show src changes and changelog entries since the last tag (release preflight) +# Show changes and changelog entries since the last tag (release preflight) @release-check: TAG=$(git describe --tags --abbrev=0); \ - echo "Commits touching src/ since $TAG:"; \ + echo "Commits touching src/ since $TAG. Each needs an entry, or is a no-op for users:"; \ git log --format=' %h %s' "$TAG"..HEAD -- src/ | grep . || echo " (none)"; \ echo ""; \ + echo "Commits touching nothing in src/. Tooling, CI and docs still ship, so read these too:"; \ + git log --format=' %h %s' "$TAG"..HEAD -- . ':(exclude)src/' | grep . || echo " (none)"; \ + echo ""; \ echo "Entries in the Unreleased section of CHANGELOG.md:"; \ awk '/^## Unreleased/{f=1;next} /^## /{f=0} f' CHANGELOG.md | grep '^- ' || echo " (none)"; \ echo ""; \ - echo "Every commit above must be covered by an entry, or be a no-op for users." + echo "Version: pyproject.toml {{ VERSION }}, uv.lock `awk -F'\"' '/^version = /{v=$2} /editable = \"\\.\"/{print v; exit}' uv.lock`"; \ + echo ""; \ + echo "A commit touching src/ and other paths appears in both lists." # Create and push a release tag (publishing happens in GitHub Actions) @release-tag: porcelain branch build