zerv is designed to run next to semantic-release, not instead of it. Split the work by
branch:
- semantic-release owns main. It reads conventional commits, decides the release
version, writes the changelog, tags, and publishes.
- zerv owns every other branch. Pull requests, feature branches, develop, release
branches, dirty working trees: each Git state gets a SemVer on the spot, computed from
tags, distance, branch, and dirtiness alone.
Release tools answer what version is this release? CI builds artifacts on every push, and
those builds need versions long before a release exists. zerv answers what version is this
exact Git state?
The payoff: trunk-based development where every branch is still e2e-testable.
- Every commit versioned - a version for any commit on any branch, including
uncommitted changes. No build is ever unversioned.
- Branch-based pre-releases -
zerv flow maps branch patterns to pre-release types
(develop and beta/* → beta, release/* → rc, everything else → alpha), so artifact
names carry where they came from.
- One payload, every format - generate ZERV RON once, then convert to SemVer, PEP440,
CalVer, docker tags,
v-prefixed major tags. Anything expressible as a schema or Tera
template.
zerv vs semantic-release
zerv’s own repository runs both in its CD pipeline
(cd.yml):
semantic-release publishes the release, then zerv versions the same commit for artifact
naming.
The zerv-flow demonstration repo shows the full
split. On pull requests, zerv runs alone: it versions the PR head, tags a pre-release, and
feeds semver, PEP440, and docker tags to the deploy jobs. On main, semantic-release
publishes and zerv versions the release build.
See coexisting with semantic-release for the workflow wiring.
The core trade-off
zerv deliberately does not read commit messages or automate releases. It computes
versions from VCS state (tags, distance, branch, dirtiness) and gets out of the way. That
makes it:
- Deterministic - the same Git state always yields the same version, regardless of
commit message style.
- Complementary - it runs alongside release automation instead of competing with it.
See coexisting with semantic-release.
- Language-agnostic - one Rust binary versions your Docker images, Python packages,
Rust crates, and deployment manifests identically.
One ZERV RON payload converts to whatever each pipeline stage needs:
setuptools-scm versions Python packages at build
time: the build backend reads Git state and writes the version into the package metadata.
That is the right tool when the package itself is the only artifact.
zerv versions every build in CI — Docker images, deployment manifests, Rust crates, npm
packages — from the same Git state, with no Python build in the loop. If your pipeline
only ever needs the version inside package metadata, stay with setuptools-scm.
zerv vs dunamai
dunamai is the closest tool in spirit: a Python
library and CLI that produces standards-compliant versions from VCS tags, with an
opinionated PEP 440 default. It even supports more version control systems than zerv
(Mercurial, Subversion, Fossil, among others).
The differences show up in CI for mixed stacks:
- Runtime - zerv is one static Rust binary; dunamai needs a Python environment.
- Branch semantics -
zerv flow derives the pre-release label and
number from branch patterns (develop → beta, release/* → rc); dunamai’s pre-release
comes from the last tag, with the branch available only as a {branch} substitution in
custom formats.
- Output model - zerv can emit a ZERV RON payload once and
re-render it into every format downstream; each dunamai invocation produces one string.
zerv vs git describe
git describe needs no install and prints v1.0.0-5-gabc1234 — distance and commit from
the nearest tag. If that raw string is all your pipeline needs, use it.
zerv starts where that shape stops: it parses the tag into version fields, normalizes to
SemVer or PEP 440, derives pre-release segments from branch patterns
(flow), and renders through schemas and Tera
templates.
When to reach for something else
Last modified on September 16, 2026