Introduction#
Versioning is one of those things that seems simple until you try to automate it consistently across a team. Manual version bumps get forgotten, build numbers drift, and tracing a deployed binary back to its source commit becomes guesswork.
Nerdbank.GitVersioning (NBGV) solves this by deriving version numbers directly from git history. Every commit produces a unique, deterministic SemVer-compliant version with no manual intervention. This guide covers how to set it up for .NET projects, configure it for your workflow, prepare releases, and integrate it into CI/CD pipelines.
How NBGV Computes Versions#
NBGV uses two inputs to calculate a version:
- The
versionfield in aversion.jsonfile (e.g.,1.2-beta) - The git height: the number of commits since the
versionfield was last changed
The resulting version follows the pattern: {major}.{minor}.{height}[-{prerelease}][+{commit-id}]
Concrete Example#
Given "version": "1.0-alpha" in version.json and 24 commits since it was set:
| Output | Value |
|---|---|
| SemVer2 | 1.0.24-alpha+g9a7eb6c819 |
| NuGetPackageVersion | 1.0.24-alpha |
| AssemblyVersion | 1.0.0.0 |
| AssemblyFileVersion | 1.0.24.27065 |
The 1.0 comes from version.json, 24 is the git height (used as PATCH), -alpha is the prerelease tag, and +g9a7eb6c819 is the git commit ID appended for non-public builds.
Why Git Height Instead of Commit Hashes#
Git commit hashes are not sortable in SemVer. An incrementing PATCH number ensures NuGet consumers always get the latest version when updating. The height resets automatically when you bump the major or minor version.
How It Differs from GitVersion#
NBGV does not depend on git tags or branch names for version calculation. Tags are optional markers, not inputs. This means clones that skip tag fetching still produce correct versions, and build reproducibility is maintained regardless of tag state.
Installation#
Install the CLI Tool#
1
dotnet tool install -g nbgv
For CI environments, a local install is preferred:
1
dotnet tool install --tool-path . nbgv
Initialize Your Repository#
Run nbgv install from the root of your git repository:
1
nbgv install
This creates two files:
version.json– the version configurationDirectory.Build.props– adds the NuGet package reference
You can specify an initial version:
1
nbgv install --version 1.0-beta
Manual Setup#
If you prefer manual configuration, create version.json at the repository root:
1
2
3
4
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.0-beta"
}
Then add the NuGet package reference in Directory.Build.props:
1
2
3
4
5
6
7
<Project>
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning"
Version="3.9.50"
PrivateAssets="all" />
</ItemGroup>
</Project>
The PrivateAssets="all" attribute prevents NBGV from becoming a transitive dependency of your packages.
Understanding version.json#
Minimal Configuration#
1
2
3
4
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.0-beta"
}
Full Configuration Reference#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.0-beta",
"assemblyVersion": {
"precision": "minor"
},
"nugetPackageVersion": {
"semVer": 2
},
"publicReleaseRefSpec": [
"^refs/heads/main$",
"^refs/heads/release/.*$"
],
"cloudBuild": {
"setAllVariables": true,
"buildNumber": {
"enabled": true,
"includeCommitId": {
"when": "nonPublicReleaseOnly",
"where": "buildMetadata"
}
}
},
"release": {
"tagName": "v{version}",
"branchName": "release/v{version}",
"versionIncrement": "minor",
"firstUnstableTag": "alpha"
},
"pathFilters": [
"./src/",
":^./docs/"
]
}
Key Properties#
version (required): The base version in major.minor[-prerelease] format. This is the only required field.
publicReleaseRefSpec: An array of regex patterns matching branches or tags that produce public release builds. Public builds omit the git commit ID suffix. Non-public builds (e.g., feature branches) include it for traceability.
assemblyVersion.precision: Controls how much of the version populates AssemblyVersion. The default minor produces 1.0.0.0, avoiding binding redirect churn on .NET Framework when shipping patches.
nugetPackageVersion.semVer: Set to 2 to include build metadata in NuGet package versions. SemVer 1 is the default for backward compatibility with older NuGet clients.
cloudBuild: Configures CI/CD integration. When buildNumber.enabled is true, NBGV sets the build number on supported CI systems automatically.
release: Controls the nbgv prepare-release command behavior including branch naming, version increment strategy, and the prerelease tag applied to the development branch after branching.
pathFilters: In mono-repos, this limits which file changes count toward version height. Prefix paths with :^ to exclude them.
Setting Up for Different Project Structures#
Single Solution (Unified Versioning)#
Place version.json at the repository root. All projects share the same version.
1
2
3
4
5
6
repo-root/
version.json
Directory.Build.props
src/
ProjectA/ProjectA.csproj
ProjectB/ProjectB.csproj
For solutions with many projects, add a performance optimization to Directory.Build.props:
1
2
3
4
5
6
7
8
9
10
<Project>
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning"
Version="3.9.50"
PrivateAssets="all" />
</ItemGroup>
<PropertyGroup>
<GitVersionBaseDirectory>$(MSBuildThisFileDirectory)</GitVersionBaseDirectory>
</PropertyGroup>
</Project>
The GitVersionBaseDirectory property tells MSBuild to compute the version once and share it across all projects, rather than running the version calculation per project.
Mono-Repo (Per-Component Versioning)#
Use version.json inheritance. Place a root file with shared settings, then override per component:
1
2
3
4
5
6
7
8
9
repo-root/
version.json # base version and shared settings
Directory.Build.props
src/
LibraryA/
version.json # "inherit": true, "version": "2.0-beta"
LibraryB/
version.json # "inherit": true, "version": "3.1"
SharedLib/ # inherits root version.json
Root version.json:
1
2
3
4
5
6
7
8
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.0-beta",
"publicReleaseRefSpec": ["^refs/heads/main$"],
"cloudBuild": {
"buildNumber": { "enabled": true }
}
}
Per-component version.json (e.g., src/LibraryA/version.json):
1
2
3
4
5
6
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"inherit": true,
"version": "2.0-beta",
"pathFilters": ["./"]
}
This inherits publicReleaseRefSpec, cloudBuild, and other settings from the root, but uses its own version. The pathFilters setting ensures version height only increments when files within this component’s directory change.
Verifying Your Setup#
After installation, build your project and check the generated version:
1
nbgv get-version
For specific version properties:
1
2
3
nbgv get-version -v NuGetPackageVersion
nbgv get-version -v SemVer2
nbgv get-version -v AssemblyVersion
For full JSON output:
1
nbgv get-version -f json
Version Properties Explained#
Given "version": "1.2-beta" with 24 commits of git height:
| Property | Value | Purpose |
|---|---|---|
| AssemblyVersion | 1.2.0.0 |
.NET assembly identity and binding. Low precision avoids binding redirect churn. |
| AssemblyFileVersion | 1.2.24.27065 |
Win32 file properties. Includes git height. |
| AssemblyInformationalVersion | 1.2.24-beta+g9a7eb6c819 |
Human-readable version with full SemVer and commit ID. |
| NuGetPackageVersion (SemVer1) | 1.2.24-beta |
Package version for older NuGet clients. |
| NuGetPackageVersion (SemVer2) | 1.2.24-beta+g9a7eb6c819 |
Package version with build metadata for NuGet 4.3+. |
Preparing Releases#
The nbgv prepare-release command automates release branching and version bumping.
What It Does#
- Creates a release branch from the current branch
- On the release branch: removes (or replaces) the prerelease tag
- On the original branch: increments the version and applies a new prerelease tag
Basic Release Workflow#
Starting state on main with version.json:
1
{ "version": "1.2-beta" }
Run the prepare-release command:
1
nbgv prepare-release
This produces two changes:
Release branch (release/v1.2) gets:
1
{ "version": "1.2" }
Main branch is updated to:
1
{ "version": "1.3-alpha" }
The branch name and version increment behavior are controlled by the release section in version.json.
Adding a Prerelease Tag to the Release Branch#
If you want the release branch to start with a release candidate tag:
1
nbgv prepare-release rc
This creates the release branch with "version": "1.2-rc" instead of "version": "1.2".
Controlling Version Increment#
Override which version component gets bumped on the development branch:
1
nbgv prepare-release --versionIncrement major
Or set a specific next version:
1
nbgv prepare-release --nextVersion 2.0
Configuring Release Behavior in version.json#
1
2
3
4
5
6
7
8
{
"release": {
"branchName": "release/v{version}",
"tagName": "v{version}",
"versionIncrement": "minor",
"firstUnstableTag": "alpha"
}
}
| Property | Description |
|---|---|
branchName |
Template for the release branch name. {version} is replaced with the version. |
tagName |
Template for the git tag name. |
versionIncrement |
Which part to increment on the dev branch: major, minor, or build. |
firstUnstableTag |
Prerelease tag applied to the incremented version on the dev branch. |
Complete Release Workflow Example#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Start on main branch with version 2.1-beta
git checkout main
# Step 1: Prepare the release
nbgv prepare-release
# Creates release/v2.1 branch with version "2.1"
# Updates main to version "2.2-alpha"
# Step 2: Switch to the release branch for stabilization
git checkout release/v2.1
# Step 3: Apply bug fixes if needed, then build and publish
dotnet build --configuration Release
dotnet pack --configuration Release
# Step 4: Tag the release commit
nbgv tag
# Creates tag v2.1.0 (or whatever the computed version is)
# Step 5: Push the tag
git push origin v2.1.0
# Step 6: For hotfixes, continue committing on release/v2.1
# Each commit increments the patch version: 2.1.1, 2.1.2, etc.
Tagging Releases#
Using the nbgv tag Command#
1
nbgv tag
This creates a git tag on the current commit using the version computed by NBGV. The tag name follows the release.tagName pattern from version.json (default: v{version}).
Tags Do Not Drive Versioning#
This is a fundamental design choice. NBGV does not use tags as inputs for version calculation. Tags are convenience markers. This means:
- Clones that do not fetch tags still produce correct versions
- Tags added retroactively do not change historical versions
- Forgotten tags do not break the build
Manual Tagging#
If you prefer custom tag names:
1
2
git tag "v$(nbgv get-version -v SimpleVersion)"
git push origin "v$(nbgv get-version -v SimpleVersion)"
Finding the Commit for a Version#
To trace a version back to its source commit:
1
nbgv get-commits 1.2.24
CI/CD Integration#
GitHub Actions#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
name: Build and Package
on:
push:
branches: [main, release/*]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history required for version calculation
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- uses: dotnet/nbgv@master
id: nbgv
with:
setAllVars: true
- name: Display version
run: |
echo "SemVer2: ${{ steps.nbgv.outputs.SemVer2 }}"
echo "NuGetPackageVersion: ${{ steps.nbgv.outputs.NuGetPackageVersion }}"
- name: Build
run: dotnet build --configuration Release
- name: Pack
run: dotnet pack --configuration Release --no-build
- name: Publish NuGet
if: github.ref == 'refs/heads/main'
run: dotnet nuget push **/*.nupkg
--source https://api.nuget.org/v3/index.json
--api-key ${{ secrets.NUGET_API_KEY }}
The fetch-depth: 0 setting is critical. Without full git history, NBGV cannot calculate the version height.
The dotnet/nbgv@master action exposes version properties as step outputs, including SemVer2, NuGetPackageVersion, AssemblyVersion, GitCommitId, PublicRelease, and more.
Azure DevOps Pipelines#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
trigger:
branches:
include:
- main
- release/*
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
fetchDepth: 0 # Full history required
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '8.0.x'
- script: dotnet tool install --tool-path . nbgv
displayName: 'Install NBGV'
- script: ./nbgv cloud
displayName: 'Set version variables'
- script: dotnet build --configuration Release
displayName: 'Build'
- script: dotnet pack --configuration Release --no-build
displayName: 'Pack'
The nbgv cloud command auto-detects the CI system and sets build numbers and environment variables accordingly.
CI/CD Requirements#
- Full git clone: The
.gitfolder must be present. Source-only downloads will not work. - No shallow clones: NBGV needs the full commit history. Set
fetch-depth: 0in GitHub Actions orfetchDepth: 0in Azure Pipelines. - Detached HEAD: Some CI systems check out a detached HEAD. NBGV handles this via environment variables provided by the CI system.
Recommended version.json Configurations#
Open Source Library#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "2.1-preview",
"publicReleaseRefSpec": [
"^refs/heads/main$",
"^refs/tags/v\\d+\\.\\d+"
],
"nugetPackageVersion": {
"semVer": 2
},
"cloudBuild": {
"buildNumber": {
"enabled": true
}
},
"release": {
"branchName": "release/v{version}",
"versionIncrement": "minor",
"firstUnstableTag": "preview"
}
}
Enterprise Application#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "3.0",
"assemblyVersion": {
"precision": "major"
},
"publicReleaseRefSpec": [
"^refs/heads/main$",
"^refs/heads/release/.*$"
],
"cloudBuild": {
"setAllVariables": true,
"buildNumber": {
"enabled": true,
"includeCommitId": {
"when": "nonPublicReleaseOnly",
"where": "buildMetadata"
}
}
},
"release": {
"branchName": "release/v{version}",
"versionIncrement": "minor",
"firstUnstableTag": "alpha"
}
}
Troubleshooting#
Version Height Is Zero or Unexpected#
Ensure your CI pipeline fetches full git history. Shallow clones are the most common cause.
1
2
3
4
# GitHub Actions
- uses: actions/checkout@v4
with:
fetch-depth: 0
Version Does Not Change After Commits#
If using pathFilters, only commits that modify files matching the filter patterns increment the version height. Verify your path patterns are correct.
Build Fails with “version.json Not Found”#
NBGV walks up the directory tree to find version.json. Ensure the file exists at or above your project directory and is committed to the repository (not just present on disk).
NuGet Package Version Includes Commit Hash#
This is expected for non-public builds. Add your branch to publicReleaseRefSpec to produce clean versions without the commit ID suffix.
Best Practices#
- Always include the
$schemafield inversion.jsonfor editor auto-completion and validation. - Use
publicReleaseRefSpecto define which branches produce clean release versions. - Keep
assemblyVersionprecision low (defaultminor) to avoid binding redirect issues on .NET Framework. - Use
pathFiltersin mono-repos to prevent unrelated changes from incrementing component versions. - Use
nbgv prepare-releaseinstead of manually managing release branches and version bumps. - Do not manually set version properties in
.csprojfiles. Let NBGV handle all version assignment. - Verify versions locally with
nbgv get-versionbefore pushing changes toversion.json. - Do not depend on tags for versioning. Use
nbgv tagfor convenience, but understand that tags are outputs, not inputs.
Conclusion#
Nerdbank.GitVersioning removes the manual effort from version management. By deriving versions from git history and a declarative version.json configuration, every commit produces a unique, traceable, SemVer-compliant version. The prepare-release workflow automates release branching and version bumping, while CI/CD integration ensures consistent versioning across all environments. Once configured, versioning becomes something you configure once and rarely think about again.