Commit-based versions for Gradle and Maven, with an independent counter for each
Git branch. Gradle plugin ID: blocksmith.versioning.
Requires Git, Java 17+, Gradle 8+ or Maven 3.9.x. The source and published tooling are public under the MIT license. Consumers do not need repository credentials. Publishing credentials belong in private environment variables or approved secret storage, never in project files.
The format is mainVersion.alphaVersion.betaVersion. Set the first two numbers
manually; only beta increases automatically on each first-parent commit.
In the root build.gradle or build.gradle.kts:
versioning {
mainVersion = 1
alphaVersion = 2
}Use literal nonnegative integers in this block. Historical scripts are read as configuration, never executed. Dynamic expressions and variables are unsupported; use the properties-file option below when sharing settings between build tools.
For Maven, add these literal properties to the root POM:
<properties>
<versioning.mainVersion>1</versioning.mainVersion>
<versioning.alphaVersion>2</versioning.alphaVersion>
</properties>Alternatively, use a tracked versioning.properties at the Git root for either
build tool:
mainVersion=1
alphaVersion=2Gradle also accepts versioning.mainVersion=1 and versioning.alphaVersion=2 in
the project's tracked gradle.properties. Choose one declaration file; do not
configure the same numbers in multiple files. An omitted component defaults to 0.
Commit the declaration. Its introduction starts at 1.2.0; the next commits are
1.2.1, 1.2.2, and so on. Committing a change to alpha produces 1.3.0;
changing main and alpha to 2 and 0 produces 2.0.0. Unrelated configuration edits
also count as commits but do not reset beta. An uncommitted prefix change previews
beta 0 with -dirty and cannot be published.
Resets are derived from committed history, without local state or additional reset tags. The usual feature-branch baseline still caps that branch's counter at zero when it is created; explicit main/alpha settings are inherited with its code. Existing branch and commit suffixes are preserved. Fetch full history and baseline tags in CI. Keep version prefixes unique for published releases; a previously published artifact version must never be overwritten.
The older start option remains available when no manual main/alpha declaration
exists. Do not combine an explicit start override with manual declarations.
Every branch starts at 0.0.0 unless a different starting version is specified.
The beta (third) number increases once per first-parent commit after its baseline,
or after the most recent manual main/alpha change.
Rebuilding the same branch and commit with the same configuration keeps its version.
| Situation | Version |
|---|---|
| First commit on master/main | 0.0.0 |
| Next commit | 0.0.1 |
| Three more commits | 0.0.4 |
| New feature branch, initialized at its fork | 0.0.0-feature-chat.b<identity>.g<commit> |
| First feature commit | 0.0.1-feature-chat.b<identity>.g<commit> |
| Uncommitted changes | Same version with -dirty appended |
Feature versions contain a readable branch label, a 16-character branch-name hash
to distinguish labels such as feature/chat and feature-chat, and the full Git
commit ID. Labels are bounded to keep artifact filenames manageable. The numbers
belong to that branch, not to another branch or another repository.
A merge commit increments the receiving branch once. A fast-forward bringing in three commits increments it three times. Commit messages do not control the bump. Reverts are new commits and also increment the version.
Main/master uses its first-parent root commit as the default baseline. Feature branches require an explicit baseline because Git cannot reliably reconstruct a branch's original creation point after merges, rebases, and cloning.
Initialize immediately after creating a branch, before its first new commit:
git switch -c feature/chat
git tag -a versioning/base/feature/chat -m '0.0.0'
git push -u origin feature/chat refs/tags/versioning/base/feature/chatThis is the only extra step per branch. All developers and CI must fetch its baseline tag. Builds read Git metadata without changing files, refs, or remotes. Protect baseline tags from deletion or replacement in your Git hosting settings.
To adopt an existing project at version 1.3.6, tag its current master commit:
git tag -a versioning/base/master -m '1.3.6'
git push origin refs/tags/versioning/base/masterThat commit is 1.3.6; the next commit is 1.3.7. Annotated baseline tags contain
only the starting numeric version. They are not release tags and are never moved.
The core artifact is also a dependency-free executable JAR:
java -jar versioning-core-0.0.1.jar print
java -jar versioning-core-0.0.1.jar init --start=1init creates the baseline for the current branch at HEAD and prints the ref to
push. It refuses to replace an existing tag. Numeric starts 0, 1, 1.2, and
1.2.3 normalize to three components. --directory=PATH and --branch=NAME are
available for tooling and detached checkouts.
An optional root versioning.properties supplies defaults:
start=0
mainBranch=mastermainBranch is optional: it defaults to origin's default branch when available,
otherwise master when present, or main. Only that primary branch omits the suffix.
This avoids publishing conflicting plain versions if both main and master exist.
The annotated baseline pins its own starting version. The start setting is used
for an untagged main branch and as the CLI's default when initializing a baseline.
To override the start for a specific invocation, use --start=1 in the CLI,
-Pversioning.start=1 in Gradle, or -Dversioning.start=1 in Maven. Such an override
deliberately changes the version policy; keep release configuration consistent.
Add the plugin repository in settings.gradle:
pluginManagement {
repositories {
maven {
url = uri('https://repo.blocksmith.studio/releases')
}
gradlePluginPortal()
}
}Apply the plugin to the root build.gradle, removing manually assigned project
versions:
plugins {
id 'java'
id 'blocksmith.versioning' version '0.0.1'
}All subprojects inherit the calculated version. The plugin integrates with normal
maven-publish publications; repository destinations and publishing credentials
remain under the consuming project's control.
./gradlew build
./gradlew versioningInfo
./gradlew publishGit inputs use a Gradle ValueSource, so configuration-cache reuse observes
commit, branch, baseline, configuration, and dirty-state changes. Publishing to a
Maven repository or Maven Local rejects a dirty working tree.
Complete Groovy and Kotlin examples are in examples/.
Add .mvn/extensions.xml:
<extensions>
<extension>
<groupId>studio.blocksmith.versioning</groupId>
<artifactId>versioning-maven-extension</artifactId>
<version>0.0.1</version>
</extension>
</extensions>Use Maven's CI-friendly version in the root POM:
<version>${revision}</version>For reactor children, also use ${revision} in the parent version, inherit the
project version, and use ${project.version} for dependencies on sibling modules.
Do not manually set revision; the extension calculates it before Maven builds
the models. Every reactor module shares the branch version.
Because core extensions resolve before project POM repositories are available,
add Reposilite to pluginRepositories in an active settings profile.
Merge the profile into your existing
settings, or use the complete project-local settings example in
examples/maven/.mvn/. That example selects its settings
through .mvn/maven.config.
mvn package
mvn install
mvn deployThe extension automatically configures the standard Flatten Maven Plugin in
resolveCiFriendliesOnly mode. It writes resolved deployment POMs under each
module's target/, including parent POMs, without editing source POMs. Do not also
configure the flatten plugin manually. Dirty trees cannot install or deploy.
Use the lifecycle phases install and deploy; direct publishing plugin goals
are rejected because they bypass POM generation.
Fetch the complete history and baseline tags. In GitHub Actions:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0For detached checkouts, explicitly set VERSIONING_BRANCH to the source branch
name, or use -Pversioning.branch=NAME / -Dversioning.branch=NAME. For pull
requests, check out the actual source commit rather than a synthetic merge commit
when you want the feature branch's version.
Builds fail clearly for missing baselines, shallow history, invalid starting versions, or a baseline removed from first-parent history. A history rewrite that loses a baseline needs an explicit recovery decision; the tool never silently resets a published counter. Do not rewrite the main branch's published history. Branch renames likewise need a baseline tag under the new branch name pointing to the original starting commit. Baseline tag names follow Git's ref namespace rules, including restrictions on prefix conflicts.
Branch-qualified versions are immutable commit versions, not Maven SNAPSHOTs.
Publish them to a repository that accepts ordinary versions. Rebuilding an
already-published version should be handled by the publishing pipeline, not by
overwriting its artifacts. Keep normal build/, target/, and cache directories
ignored so generated output does not make the checkout dirty.
./gradlew check assemble publishAllPublicationsToLocalTestRepository
python3 tests/integration.py --gradle ./gradlew --maven /path/to/mvn \
--tool-version "$(./gradlew -q versioningInfo)"The project builds its own plugin from the shared source through buildSrc, so
it follows the same branch version rules without requiring a prepublished copy.
Core tests exercise real Git histories. Integration checks publish multi-module Gradle/Maven projects into isolated local repositories, compile separate consumers, verify POM versions and cache invalidation, and reject dirty publishing.
Artifacts use studio.blocksmith.versioning as their Maven group. The Gradle
marker uses blocksmith.versioning:blocksmith.versioning.gradle.plugin.
From a clean checkout, configure private publishing credentials in environment variables and run:
./gradlew publishAllPublicationsToReposiliteRepositoryREPOSILITE_USERNAME and REPOSILITE_PASSWORD supply credentials;
REPOSILITE_URL optionally overrides the public releases destination. A publisher
needs write access to both artifact namespaces. Consumers download anonymously;
publishing remains authenticated. No network access is required to calculate a
version once the tooling and its dependencies are cached.