Skip to content

Add a dynamic-versioning mode to the bump machinery - #30

Merged
lewisjared merged 7 commits into
mainfrom
feat/dynamic-versioning
Aug 24, 2026
Merged

Add a dynamic-versioning mode to the bump machinery#30
lewisjared merged 7 commits into
mainfrom
feat/dynamic-versioning

Conversation

@lewisjared

Copy link
Copy Markdown
Contributor

Adds a dynamic-versioning input to the bump-version action and the bump.yaml reusable workflow, for projects whose version is derived from git tags (e.g. hatch-vcs). Those projects have no version to edit, so the release is the tag.

In dynamic mode:

  • The base version is the latest reachable v* tag with the v stripped, falling back to 0.0.0 when there is no such tag.
  • The new version is bump-rule applied to that base, computed by running uv version --bump against a scratch project seeded with the base version. This borrows uv's rules rather than reimplementing PEP 440, so patch, minor, major, stable and the pre-release segments all mean exactly what they mean in static mode, and an unsupported combination fails with uv's own message.
  • No manifest is rewritten and no lockfile is refreshed, so workspace-packages and lock do not apply.
  • The post-tag pre-release commit never happens, so pre-release-bump and pre-release-base are ignored and dev-version stays empty.
  • The commit is made only when something is left to commit, so with no changelog change the tag lands on HEAD rather than on an empty commit.
  • update-changelog and pre-commit-command behave as they do in static mode.

Static mode is untouched: every existing input and output keeps its name, default and meaning. Only project-type: uv supports dynamic versioning, and pairing it with a Node project type fails with a clear message.

The phases now call backend.apply_bump, which the manifest-backed backends implement as "run the bump command, then read the version back". This is what lets the dynamic backend compute the version without a file to read.

Tests cover the scratch-project command assembly, the bump rules end to end against uv, and the input handling. Verified by hand against throwaway repos in both modes.

Projects that derive their version from git tags (e.g. hatch-vcs) have no
version to edit, so the bump machinery now takes the base version from the
latest reachable v* tag and releases by tagging alone.

The new version is computed by applying bump-rule to a scratch project with uv,
so the rules are exactly the static mode's rather than a second implementation.
The dynamic base version came from `git describe --tags --abbrev=0`, which
returns the closest tag rather than the highest. A repo carrying moving aliases
such as `v1.5` alongside `v1.5.1` bumped from `1.5`, then failed to tag `v1.5.1`
because that tag already existed.

Sorting with git does not fix it either. Both `git describe` and
`git tag --sort=v:refname` rank `v1.6.0rc1` above `v1.6.0`, and neither skips a
tag like `vendor-3` that merely starts with `v`. Tags are now parsed with
`packaging`, which this file already uses for pre-release detection, so the
ordering cannot drift from the rest of the bump logic.

Adds unit tests for the ranking rules and integration tests against a real repo,
covering pre-release and post-release ordering, epochs, unparseable tags, an
untagged repo, and a tag on another branch.

Also folds in a few cleanups:

- Renames the scratch bump builder so it cannot be confused with `bump_command`,
  which rewrites the repo's own manifest.
- Drops the unused `check` argument on `run` and the dead `name` attribute on
  `DynamicBackend`.
- Rewraps the new prose on semantic line breaks.
Verified the multi-package case against a real uv workspace with two hatch-vcs
members. The action itself behaves as documented: it reads the base from the
tag, skips the manifest and the lockfile, ignores `workspace-packages`, and
tags `HEAD` without landing a commit.

What bites is everything around it, so those four traps are written down:

- Two release tags on one commit make hatch-vcs build the lower one. A commit
  carrying `v1.3.0` and `v1.4.0` builds `1.3.0` even though the action just
  tagged `v1.4.0`. Tagging `HEAD` without a commit makes this easy to reach.
- Every member shares one version, so nothing can be released on its own.
- `uv build` against a virtual workspace root produces `unknown-0.0.0`, which
  `release-files: dist/*` would attach to the release. `--all-packages` is the
  working form.
- A member in a subdirectory needs `search_parent_directories`, or hatch-vcs
  cannot find the repository.

Also strips trailing whitespace from the bump workflow's comments.
Dynamic mode tags `HEAD` without landing a commit, so releasing twice with
nothing merged in between puts both tags on one commit. hatch-vcs then reads
the lower one, and the artefacts come out with the previous version while the
GitHub release claims the new one.

The check runs immediately before the tag rather than at the start, so a
changelog or `pre-commit-command` commit that moves `HEAD` still releases
normally. Only a commit that would genuinely build the wrong version is
refused.

This also rules out repos that publish moving aliases such as `v1`, since an
alias on the release commit is what hatch-vcs would build.
Building a virtual workspace root produces junk artefacts whatever the version
source is, so the note was not about dynamic versioning. `build-command` is an
input to the reusable workflow rather than to this action, so the note was also
in the wrong file.
@lewisjared
lewisjared merged commit fcc0ebb into main Aug 24, 2026
2 checks passed
@lewisjared
lewisjared deleted the feat/dynamic-versioning branch August 24, 2026 03:17
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