Skip to content

refactor: derive the version from the git tag - #129

Merged
tschm merged 1 commit into
mainfrom
dynamic-versioning
Sep 9, 2026
Merged

tschm merged 1 commit into
mainfrom
dynamic-versioning

Conversation

@tschm

@tschm tschm commented Sep 9, 2026

Copy link
Copy Markdown
Member

What

[project].version is no longer written in pyproject.toml. hatch-vcs derives it from
the git tag, so the tag is the single source of truth:

[build-system]
requires = ["hatchling", "hatch-vcs"]

[project]
dynamic = ["version"]

[tool.hatch.version]
source = "vcs"

Why

The version was written in the manifest and kept in step with the tag by hand — through
bump-my-version, and in some repos a second entry for the copy uv.lock recorded. That
is two or three copies of one number per release, each able to disagree with the others.
A derived version cannot disagree with git.

uv omits the version from the lock's own package block once it is dynamic, so that
copy is gone too. bump-my-version resolves the current version from the newest reachable
tag instead of a file, which is the same answer hatch-vcs gives — it still proposes the
right next number, it just no longer writes it anywhere.

The pytest-rhiza pin bump is a prerequisite, not a drive-by

Making the version dynamic on its own turns the required rhiza-test job red. This
repo pinned the conformance checks at pytest-rhiza 0.2.1, which predates a rhiza
project being able to derive its version; three of its assertions read the absent
[project].version as '' and fail on it rather than skipping. 0.6.0 is the first
release that accepts a VCS-derived version, is what rhiza-task 1.7.0 already resolves
by default, and is what jquantstats pins, having made this move first. It is in the
same commit because it edits the same file.

CITATION.cff keeps its bump entries

Two bump-my-version targets went away with the written number and neither is a loss —
the [project] rewrite has nothing left to match, and uv omits the version from a
dynamic project's own package block, so the lock can no longer disagree with the
manifest after a bump, which is what that entry existed to prevent.

CITATION.cff is different and keeps its entries. Its version is a statement about a
citation rather than a build input, so it has to be right in the commit the tag names —
a number no tag can supply yet. Note that check-bumpversion-config no longer validates
those patterns, since it skips a dynamic project entirely; bump-my-version still errors
at release time if one stops matching.

Verification

Run locally, not inferred:

check result
build at the newest tag wheel is exactly that tag's version
build between tags next patch as a dev release (guess-next-dev)
uvx rhiza-task@1.7.0 rhiza-test 29 passed, 6 skipped (the written-version assertions)
uvx rhiza-task@1.7.0 test pass
uv lock --check no drift
CI's shallow, tagless checkout uv sync --locked and the suite pass

rhiza_release.yml at v1.8.0 already handles a derived version: it skips the
uv version --short comparison — which exits 2 on a dynamic project — and checks the
built distribution's filename against the tag instead. Its build job checks out with
fetch-depth: 0, which hatch-vcs needs to see the tag.

Note for the reviewer

The one behaviour that genuinely changes: between tags, a local build or editable install
now reports a dev version (X.Y.(Z+1).devN+g<sha>) rather than the last release number.
That is the point — a number that looks wrong now surfaces as a release failure instead
of as a plausible constant — but it is worth knowing before the first pip show.

🤖 Generated with Claude Code

[project].version was written in pyproject.toml and had to be kept in step with the
tag by hand, via bump-my-version and (where the lock recorded it) a second entry for
uv.lock. Every release therefore carried two or three copies of one number, and a way
for them to disagree.

hatch-vcs now derives the version from `git describe`, so the tag is the single source
of truth: `dynamic = ["version"]` in [project], `source = "vcs"` under
[tool.hatch.version], and no number in any file for a bump to rewrite. uv omits the
version from the lock's own package block, so that copy is gone too. bump-my-version
resolves the current version from the newest reachable tag instead -- the same answer
hatch-vcs gives -- so it still proposes the right next number, it just no longer writes
it anywhere.

Two bump-my-version targets went with the written number, and neither is a loss: the
[project] rewrite has nothing left to match, and uv omits the version from a dynamic
project's own package block, so the lock can no longer disagree with the manifest after
a bump -- which is what that entry existed to prevent. CITATION.cff keeps its entries:
its version is a statement about a citation rather than a build input, so it has to be
right in the commit the tag names.

The pytest-rhiza pin moves 0.2.1 -> 0.6.0 in the same commit, because it has to. 0.2.1
predates a rhiza project deriving its version, and three of its assertions read the
absent [project].version as '' and fail rather than skip -- the required-field check,
the semver shape and the tag-agreement one -- turning the required rhiza-test job red.
0.6.0 is the first release that accepts a VCS-derived version, is what rhiza-task 1.7.0
already resolves by default, and is what jquantstats pins.

Verified: the tree at this repo's newest tag builds to exactly that tag's version, and
between tags to the next patch as a dev release. rhiza_release.yml (v1.8.0) already
handles a derived version -- it skips the `uv version --short` comparison and checks the
built distribution against the tag instead -- and its build job checks out with
fetch-depth: 0, which hatch-vcs requires. pytest-rhiza's test_pyproject and the synced
test_rhiza_packaging both skip their written-version assertions on a dynamic project.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tschm
tschm merged commit d35211b into main Sep 9, 2026
43 checks passed
@tschm
tschm deleted the dynamic-versioning branch September 9, 2026 04:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant