Skip to content

Commit 6392dec

Browse files
Zac-HDclaude
andauthored
Guard release version info against typos and desync (#473)
Release 27.7.1 escaped with a year-typo'd changelog header while __version__ still said 26.6.1: CI only checked that the changelog head was >= __version__, and the release job's update_version() silently patched the workspace copy before tagging and uploading (#469). - New always-on check: the newest changelog version may never be dated in the future, and its month must be a valid calendar month. - In --ensure-tag (release) mode, require the changelog head, __version__, and the usage.rst pre-commit rev to be exactly equal, and no longer run update_version() there - a mismatch now fails the release instead of being patched on the fly. Pre-commit keeps the auto-fix behavior. - Before creating a new tag for a x.y.1 release, require its year.month to be the current (or immediately previous) month; patch releases of an existing series are exempt. Tag existence is now checked via ls-remote, since a shallow CI checkout has no local tags. - Rename the 27.7.1 changelog entry to the intended 26.7.1 and sync __version__ and the usage.rst example accordingly. Claude-Session: https://claude.ai/code/session_01Cqsspf84UPK1Gg5rka8hRz Co-authored-by: Claude <noreply@anthropic.com>
1 parent 68b07ff commit 6392dec

4 files changed

Lines changed: 64 additions & 10 deletions

File tree

docs/changelog.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Changelog
44

55
`CalVer, YY.month.patch <https://calver.org/>`_
66

7-
27.7.1
7+
26.7.1
88
======
99
- Add :ref:`ASYNC401 <async401>` pytest-raises-exception-group, recommending ``pytest.RaisesGroup`` over ``pytest.raises(ExceptionGroup)``. `(issue #430) <https://github.com/python-trio/flake8-async/issues/430>`_
1010

docs/usage.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ adding the following to your ``.pre-commit-config.yaml``:
3333
minimum_pre_commit_version: '2.9.0'
3434
repos:
3535
- repo: https://github.com/python-trio/flake8-async
36-
rev: 26.6.1
36+
rev: 26.7.1
3737
hooks:
3838
- id: flake8-async
3939
# args: ["--enable=ASYNC100,ASYNC112", "--disable=", "--autofix=ASYNC"]

flake8_async/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939

4040
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
41-
__version__ = "26.6.1"
41+
__version__ = "26.7.1"
4242

4343

4444
# taken from https://github.com/Zac-HD/shed

tests/check_changelog_and_version.py

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from __future__ import annotations
55

6+
import datetime
67
import re
78
import sys
89
from pathlib import Path
@@ -24,6 +25,10 @@
2425
T = TypeVar("T", bound="Version")
2526

2627

28+
def today() -> datetime.date:
29+
return datetime.datetime.now(tz=datetime.timezone.utc).date()
30+
31+
2732
class Version(NamedTuple):
2833
year: int
2934
month: int
@@ -77,6 +82,19 @@ def test_last_release_against_changelog() -> None:
7782
assert latest_release >= VERSION, f"{latest_release}, {VERSION}"
7883

7984

85+
def test_latest_release_is_not_in_the_future() -> None:
86+
# With CalVer, a typo'd year or month gives a version that quietly sorts
87+
# after every correct release (https://github.com/python-trio/flake8-async/issues/469),
88+
# so the newest changelog entry may never be dated later than today.
89+
latest = next(iter(get_releases()))
90+
now = today()
91+
assert 1 <= latest.month <= 12, f"{latest} does not have a valid month"
92+
assert (latest.year + 2000, latest.month) <= (
93+
now.year,
94+
now.month,
95+
), f"latest release {latest} is dated in the future (today is {now:%Y-%m})"
96+
97+
8098
def test_version_increments_are_correct() -> None:
8199
versions = list(get_releases())
82100
for prev, current in zip(versions[1:], versions):
@@ -90,15 +108,48 @@ def test_version_increments_are_correct() -> None:
90108
assert current == prev._replace(patch=prev.patch + 1), msg
91109

92110

111+
def check_version_info_is_in_sync() -> None:
112+
"""Assert that all places stating a version agree, before release.
113+
114+
In pre-commit, update_version() auto-fixes these instead; but at release
115+
time nothing may be patched up on the fly, or the built package would not
116+
match the repository contents.
117+
"""
118+
latest = next(iter(get_releases()))
119+
assert (
120+
latest == VERSION
121+
), f"changelog head is {latest}, but __version__ is {VERSION}"
122+
m = re.search(r"^ rev: (\d+\.\d+\.\d+)$", USAGE.read_text(), flags=re.MULTILINE)
123+
assert m is not None, "pre-commit example not found in usage.rst"
124+
assert m.group(1) == str(VERSION), (
125+
f"pre-commit example in usage.rst pins rev {m.group(1)}, "
126+
f"but __version__ is {VERSION}"
127+
)
128+
129+
93130
def ensure_tagged() -> None:
94131
last_version = next(iter(get_releases()))
95132
repo = Repo(ROOT_PATH)
96-
if str(last_version) not in iter(map(str, repo.tags)):
97-
# create_tag is partially unknown in pyright, which kinda looks like
98-
# https://github.com/gitpython-developers/GitPython/issues/1473
99-
# which should be resolved?
100-
repo.create_tag(str(last_version)) # type: ignore
101-
repo.remotes.origin.push(str(last_version))
133+
# Local tags can be missing in a shallow CI checkout, so ask the remote.
134+
if repo.git.ls_remote("origin", f"refs/tags/{last_version}"):
135+
return
136+
if last_version.patch == 1:
137+
# A new year.month series must match the date it is released (patch
138+
# releases keep the year.month of their series, so aren't checked).
139+
# One month of slack covers a release PR merged just after month end.
140+
now = today()
141+
months_ago = (now.year - 2000 - last_version.year) * 12 + (
142+
now.month - last_version.month
143+
)
144+
assert 0 <= months_ago <= 1, (
145+
f"refusing to tag {last_version}: today is {now:%Y-%m}, and with"
146+
" CalVer the version should match the date of release"
147+
)
148+
# create_tag is partially unknown in pyright, which kinda looks like
149+
# https://github.com/gitpython-developers/GitPython/issues/1473
150+
# which should be resolved?
151+
repo.create_tag(str(last_version)) # type: ignore
152+
repo.remotes.origin.push(str(last_version))
102153

103154

104155
def update_version() -> None:
@@ -128,7 +179,10 @@ def update_version() -> None:
128179
if __name__ == "__main__":
129180
test_last_release_against_changelog()
130181
test_version_increments_are_correct()
182+
test_latest_release_is_not_in_the_future()
131183

132-
update_version()
133184
if "--ensure-tag" in sys.argv:
185+
check_version_info_is_in_sync()
134186
ensure_tagged()
187+
else:
188+
update_version()

0 commit comments

Comments
 (0)