Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
- Document why bound forms, including GET filter forms, render `is-valid` on every field, and how to switch that off (#276).
- Document that `use_i18n` in `BOOTSTRAP4` is always overwritten by Django's `USE_I18N`.

- 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.
## 26.2 (2026-08-28)

- Drop support for Django 4.2 (EOL).
Expand Down
17 changes: 13 additions & 4 deletions MAINTAINING.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,19 @@ 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.
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
Expand Down
29 changes: 25 additions & 4 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading