Skip to content

About

Independent commit-based branch versions for Gradle and Maven.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Blocksmith Versioning

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.

Manual main and alpha versions

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=2

Gradle 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.

Version rules

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 a branch

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/chat

This 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/master

That 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=1

init 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=master

mainBranch 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.

Gradle

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 publish

Git 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/.

Maven

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 deploy

The 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.

CI and history requirements

Fetch the complete history and baseline tags. In GitHub Actions:

- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
  with:
    fetch-depth: 0

For 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.

Build and verification

./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.

Publish the tooling

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 publishAllPublicationsToReposiliteRepository

REPOSILITE_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.

About

Independent commit-based branch versions for Gradle and Maven.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages