Semantic Versioning (SemVer) is a versioning scheme that uses a three-part version number: MAJOR.MINOR.PATCH. It helps to convey meaning about the underlying changes in a new release:
- MAJOR version changes indicate incompatible API changes.
- MINOR version changes add functionality in a backward-compatible manner.
- PATCH version changes are for backward-compatible bug fixes.
GitVersion is a tool that automatically generates a semantic version number based on your Git history. It analyzes your repository's commit history and branch structure to determine the appropriate version number.
All you need to do is:
- Prepare your desired
GitVersion.yml
file. - Add it to the root path of your project/Module directory, where your
.git
directory exists. - Prepare the build:
- If you are using .NET:
- Add
GitVersion.MsBuild
NuGet package to your every single project in the solution.
- Add
- If you are using Java/Kotlin:
- Add the necessary tasks to create the
gitversion.json
file byGitVersion CLI
to parse the right version for your projects.
- Add the necessary tasks to create the
- If you are using .NET:
- Boom! Build your solution (in .NET) / main module (in Java/Kotlin)
You can find a detailed explanation for configuring your GitVersion.yml
file on gitversion.net. Alternatively, you can use the sample I prepared based on GitFlow (GitFlow/v1)
available in my GitHub:
# This configuration uses GitFlow branching model which always has a main and a develop branch. see: https://nvie.com/posts/a-successful-git-branching-model/
# This configuration follows Semantic Versioning. see: https://semver.org/
# A good explanation on semantic versioning: https://semantic-versioning.org/
workflow: GitFlow/v1
assembly-versioning-scheme: MajorMinorPatchTag
assembly-file-versioning-scheme: MajorMinorPatchTag
assembly-informational-format: "{FullSemVer}"
tag-prefix: "[vV]?"
version-in-branch-pattern: (?<version>[vV]?\d+(\.\d+)?(\.\d+)?).*
major-version-bump-message: '\+semver:\s?(breaking|major)'
minor-version-bump-message: '\+semver:\s?(feature|minor)'
patch-version-bump-message: '\+semver:\s?(fix|patch)'
no-bump-message: '\+semver:\s?(none|skip)'
tag-pre-release-weight: 60000
commit-date-format: "yyyy-MM-dd"
merge-message-formats: {}
update-build-number: true
semantic-version-format: Strict #ensure that versions are consistently formatted and that the versioning process remains predictable and reliable. This can be particularly important for projects with strict dependency management and release policies.
strategies:
- Fallback #This strategy is used when no other versioning strategy applies. It ensures that a version is always generated, even if no tags or commits indicate a version change.
#ConfiguredNextVersion: #This strategy allows you to manually specify the next version number. It's useful for scenarios where you want to control the versioning process directly.
- MergeMessage #This strategy increments the version based on the merge commit message. If the message contains specific keywords (e.g., "version bump"), the version number is incremented accordingly.
#- TaggedCommit #This strategy uses the commit tagged with a version number to determine the next version. It's useful for projects that follow a strict versioning policy based on tags.
- TrackReleaseBranches #This strategy tracks branches that are used for releases. It ensures that the version number is incremented based on the commits made to these branches.
- VersionInBranchName #This strategy extracts the version number from the branch name itself. It's useful for projects that use branch names to indicate version information.
branches:
......
By placing the GitVersion.yml
file in the root of your solution (in .NET) or main module (in Java/Kotlin), the gitversion
CLI will use it for all sub-projects or sub-modules.
You can find the sample projects in my repo at GitHub:
Java project:
Kotlin project:
CSharp project:
Note: This guide is intended to use with build.gradle.kts
. You can modify it to suit your needs.
- Open
build.gradle.kts
file for every single project and inject this code block into it:
//Setting this version ensures that if any issues occur during versioning, it will use the default version.
version = "0.1.0-SNAPSHOT"
//----------------------------------- Semantic Versioning / Continues Integration -------------------------------------
val gitVersionJsonFilePath = "../gitversion.json" // make a const for the 'gitversion.json' file path.
// Register an executable task to generate the 'gitversion.json' file using the 'gitversion' CLI.
tasks.register<Exec>("gitVersionOutputJSon") {
commandLine("sh", "-c", "gitversion /output json > $gitVersionJsonFilePath")
}
/*
Register a task that depends on 'gitVersionOutputJson' to run after it.
This task will read the 'gitversion.json' file, extract the 'SemVer' key value, and assign it to the 'project.version'.
*/
tasks.register("parseGitVersion") {
dependsOn("gitVersionOutputJSon")
doLast {
val jsonSlurper = JsonSlurper()
val gitVersionFile = file(gitVersionJsonFilePath)
val gitVersion = jsonSlurper.parse(gitVersionFile) as Map<*, *>
project.version = gitVersion["SemVer"].toString()
println("---> Project version set to: ${project.version}")
}
}
/*
Configure the 'Jar' task to depend on the 'parseGitVersion' task. This ensures that every time the 'Jar' task runs,
it first executes the 'parseGitVersion' task to determine the project version.
Also assigns the title and version of the .jar file to the 'project.name' and 'project.version'.
*/
tasks.withType<Jar>().configureEach {
dependsOn("parseGitVersion")
from(sourceSets["main"].output)
manifest {
attributes(
"Implementation-Title" to project.name,
"Implementation-Version" to project.version
)
}
}
//---------------------------------------------------------------------------------------------------------------------
Note: To ensure the gitVersionOutputJSon
task is runnable, you need to install GitVersion CLI
on your operating system and set it as an environment variable
.
There are several ways To installing GitVersion CLI
:
- The first approach is to use a third party installer like:
dotnet tool install
,brew install
,choco install
, or install it via docker likedocker run
. if you are interested in doing in this way please refer to Installation guide from gitversion.net and follow the steps. - However there is also a direct approach you can use:
- First, download the latest
GitVersion CLI
from here based on your operating system. at the moment the latest version is6.0.5
- Second, extract the file into a directory and do not move the location ever.
- Third, Make an environment variable for it:
-Now, you can runexport PATH=$PATH:/home/sam/Public/gitversion-linux-x64-6.0.5
GitVersion -version
to verify and check yourGitVersion CLI
installation in terminal. you should see something like this: (if not, restart your pc to apply the changes) - First, download the latest
This block of code should be placed between your { plugins
, repositories
, ... } and { dependencies
, ...}.
finally you would have something like this:
import groovy.json.JsonSlurper
plugins {
application
}
application {
mainClass = "codelab.java.console.Main"
applicationName = "codelab.java.console.app"
}
repositories {
mavenCentral()
}
group = "codelab.java"
//Setting this version ensures that if any issues occur during versioning, it will use the default version.
version = "0.1.0-SNAPSHOT"
//----------------------------------- Semantic Versioning / Continues Integration -------------------------------------
val gitVersionJsonFilePath = "../gitversion.json" // make a const for the 'gitversion.json' file path.
// Register an executable task to generate the 'gitversion.json' file using the 'gitversion' CLI.
tasks.register<Exec>("gitVersionOutputJSon") {
commandLine("sh", "-c", "gitversion /output json > $gitVersionJsonFilePath")
}
/*
Register a task that depends on 'gitVersionOutputJson' to run after it.
This task will read the 'gitversion.json' file, extract the 'SemVer' key value, and assign it to the 'project.version'.
*/
tasks.register("parseGitVersion") {
dependsOn("gitVersionOutputJSon")
doLast {
val jsonSlurper = JsonSlurper()
val gitVersionFile = file(gitVersionJsonFilePath)
val gitVersion = jsonSlurper.parse(gitVersionFile) as Map<*, *>
project.version = gitVersion["SemVer"].toString()
println("---> Project version set to: ${project.version}")
}
}
/*
Configure the 'Jar' task to depend on the 'parseGitVersion' task. This ensures that every time the 'Jar' task runs,
it first executes the 'parseGitVersion' task to determine the project version.
Also assigns the title and version of the .jar file to the 'project.name' and 'project.version'.
*/
tasks.withType<Jar>().configureEach {
dependsOn("parseGitVersion")
from(sourceSets["main"].output)
manifest {
attributes(
"Implementation-Title" to project.name,
"Implementation-Version" to project.version
)
}
}
//---------------------------------------------------------------------------------------------------------------------
dependencies {
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
}
tasks.test {
useJUnitPlatform()
}
tasks.jar {
manifest {
attributes["Main-Class"] = application.mainClass
}
}
The Semantic Versioning block generates the gitversion.json
file in the root path of your project:
If you look at the file, you should have something like this:
{
"AssemblySemFileVer": "0.1.0.1",
"AssemblySemVer": "0.1.0.1",
"BranchName": "feature/setup",
"BuildMetaData": 5,
"CommitDate": "2024-12-05",
"CommitsSinceVersionSource": 5,
"EscapedBranchName": "feature-setup",
"FullBuildMetaData": "5.Branch.feature-setup.Sha.f2526f6ec73f6753bc39a17edd186c3114f34872",
"FullSemVer": "0.1.0-setup.1+5",
"InformationalVersion": "0.1.0-setup.1+5",
"Major": 0,
"MajorMinorPatch": "0.1.0",
"Minor": 1,
"Patch": 0,
"PreReleaseLabel": "setup",
"PreReleaseLabelWithDash": "-setup",
"PreReleaseNumber": 1,
"PreReleaseTag": "setup.1",
"PreReleaseTagWithDash": "-setup.1",
"SemVer": "0.1.0-setup.1",
"Sha": "f2526f6ec73f6753bc39a17edd186c3114f34872",
"ShortSha": "f2526f6",
"UncommittedChanges": 3,
"VersionSourceSha": "",
"WeightedPreReleaseNumber": 30001
}
Let's take a look at what happens when you run the build
task:
gitVersionOutputJSon
task tries to make thegitversion.json
file based on your project changes..parseGitVersion
task tries to parse the generated JSON file and take the 'SemVer' and assign it to theproject.version
- Now gradle uses this property to generate the
jar
file.
- Add
GitVersion.MsBuild
NuGet package to every single project in the solution:
Congrats! you finish the local project side.
name: Java Kotlin Build and Test With Gradle
on: [push, pull_request, workflow_dispatch]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/[email protected]
with:
fetch-depth: 0
- name: Set up JDK 23
uses: actions/[email protected] #https://github.com/actions/setup-java
with:
java-version: '23'
distribution: 'oracle'
- name: Install GitVersion 6.0.5 for Gradle
run: |
wget -q -O gitversion.tar.gz https://github.com/GitTools/GitVersion/releases/download/6.0.5/gitversion-linux-x64-6.0.5.tar.gz
mkdir gitversion_extracted
tar -xzf gitversion.tar.gz -C gitversion_extracted
ls -R gitversion_extracted
sudo mv gitversion_extracted/gitversion /usr/local/bin/gitversion
sudo chmod +x /usr/local/bin/gitversion
- name: Setup Gradle 8.11.1
uses: gradle/actions/setup-gradle@v4 #https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#build-with-a-specific-gradle-version
with:
gradle-version: '8.11.1'
- name: Build with Gradle 8.11.1
run: gradle build --scan --warning-mode all
dependency-submission: # See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/[email protected]
- name: Set up JDK 23
uses: actions/[email protected]
with:
java-version: '23'
distribution: 'oracle'
- name: Setup Gradle 8.11.1
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: '8.11.1'
- name: Setup Gradle Wrapper
run: gradle wrapper
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@v4
name: .NET Build and Test
on: [push, pull_request, workflow_dispatch]
jobs:
build_and_Test:
runs-on: ubuntu-latest
steps:
- name: Checkout #https://github.com/GitTools/actions/blob/main/docs/examples/github/gitversion/setup.md
uses: actions/[email protected]
with:
fetch-depth: 0
- name: Install GitVersion 6.x #https://github.com/GitTools/actions/blob/main/docs/examples/github/gitversion/setup.md
uses: gittools/actions/gitversion/[email protected]
with:
versionSpec: '6.0.x'
preferLatestVersion: true
- name: Determine Version #https://github.com/GitTools/actions/blob/main/docs/examples/github/gitversion/execute.md
uses: gittools/actions/gitversion/[email protected]
with:
useConfigFile: true
updateAssemblyInfo: true
- name: Setup .NET 9 #https://github.com/actions/setup-dotnet
uses: actions/[email protected]
with:
dotnet-version: '9.0.x'
- name: Available projects
run: dotnet sln list
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal