I have scripted versioning strategies for Trunk Based Development many times thoughout my career, including using simple Github Actions like Github Tag Action, but the result was never spot on. So I finally gave in overcoming the complexity of tuning GitVersion!
Trunk Based Development
From trunkbaseddevelopment.com:
A source-control branching model, where developers collaborate on code in a single branch called ‘trunk’ *, resist any pressure to create other long-lived development branches by employing documented techniques. They therefore avoid merge hell, do not break the build, and live happily ever after.
A great fit for Continuous Delivery, and very easy to apply!
SemVer
Versioning does not need to be just about incrementing a number to distinguise releases. It can also be semantic; alas SemVer, or short for Semantic Versioning.
From the site:
Given a version number MAJOR.MINOR.PATCH, increment the:
- MAJOR version when you make incompatible API changes,
- MINOR version when you add functionality in a backwards compatible manner, and
- PATCH version when you make backwards compatible bug fixes.
Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.
This is all nice and such, but how do you integrate that with the process of continuously delivering code?
Conventional Commits
Conventional Commits is a convention for how to turn human written commit messages into machine readable meaning. It also happen to nicely follow the convention for SemVer.
- Major / Breaking Change
refactor!: drop support for Node 6 BREAKING CHANGE: refactor to use JavaScript features not available in Node 6.
- Minor / Feature
feat(lang): add polish language
- Patch
fix: correct minor typos in code
GitVersion
GitVersion looks simple and promising at first glance.
However, when digging into the documentation, a different picture immerges.
GitVersion comes with three different strategies; Mainline Development, Continuous Delivery and Continuous Deployment, all quite difficult to decipher and grasp. All of them seems at first to be a possible fit for Trunk Based Development, but I ended up only getting Continuous Delivery to follow the simple philipsopy of Trunk Based Development, yet being flexible enough of when to actually bump versions and not bump any of major/minor/patch more then once until merge to main happens.
Configuration
GitVersion uses a config file to configure strategies, and there are quite few configurable aspects of said strategies. Let’s get started!
Branches
Trunk Based Development is all about getting into trunk (main/master in git lingo) fast. We have two branch strategies; either we are on main (where we don’t do any development) or we are on a development branch. GitVersion however comes with a lot of them that needs to be disabled.
feature: # Match nothing regex: ^\b$ develop: # Match nothing regex: ^\b$ release: # Match nothing regex: ^\b$ pull-request: # Match nothing regex: ^\b$ hotfix: # Match nothing regex: ^\b$ support: # Match nothing regex: ^\b$y
We consider all branches except main as development branches. These should incremented with patch by default.
development: increment: Patch # Everything except main and master regex: ^(?!(main|master)$)
Detecting Conventional Commits
The following regular expressions detect when to bump major/minor/patch, and are based on the Conventional Commit v1.0.0 specification.
- Major
major-version-bump-message: "(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([a-z]+\\))?(!: .+|: (.+\\n\\n)+BREAKING CHANGE: .+)"
- Minor
minor-version-bump-message: "(feat)(\\([a-z]+\\))?: .+"
- Patch
patch-version-bump-message: "(build|chore|ci|docs|fix|perf|refactor|revert|style|test)(\\([a-z]+\\))?: .+"
Here’s the complete config:
mode: ContinuousDelivery # Conventional Commits https://www.conventionalcommits.org/en/v1.0.0/ # https://regex101.com/r/Ms7Vx6/2 major-version-bump-message: "(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([a-z]+\\))?(!: .+|: (.+\\n\\n)+BREAKING CHANGE: .+)" # https://regex101.com/r/Oqhi2m/1 minor-version-bump-message: "(feat)(\\([a-z]+\\))?: .+" # https://regex101.com/r/f5C4fP/1 patch-version-bump-message: "(build|chore|ci|docs|fix|perf|refactor|revert|style|test)(\\([a-z]+\\))?: .+" # Match nothing no-bump-message: ^\b$ continuous-delivery-fallback-tag: '' branches: development: increment: Patch # Everything except main and master regex: ^(?!(main|master)$) track-merge-target: true source-branches: [] feature: # Match nothing regex: ^\b$ develop: # Match nothing regex: ^\b$ main: source-branches: [] release: # Match nothing regex: ^\b$ pull-request: # Match nothing regex: ^\b$ hotfix: # Match nothing regex: ^\b$ support: # Match nothing regex: ^\b$
Tagging
A common practice when releasing in git is to tag a commit with the release number, i.e. v1.2.3
. When practicing continuous delivery, this can become quite verbose, especially when producing pre-releases, as every commit becomes a potential release. GitVersion Continuous Delivery strategy traverses each commit from last version tag when calculating the current version, so pre-releases does not necessary need to be tagged. Instead, the commit sha can be used as the pre-release identifier for back-referencing any pre-release artifacts.
Ofcourse, when using Github or similar platforms, a release is a tag, so in that case you might want to use tagging for pre-releases anyway.
Caveats
Forgetting to merge a merge commit back to the development branch when continuing working on the same branch means the automatic patch increment that happens for commits after branching does not occur. This will skew any pre-release versions. According to the documentation, the parameter track-merge-target
should solve that scenario, but it seems it has not been implemented!
Summary
GitVersion turned out to be a really nice versioning tool once overcoming the knowledge curve of understanding all the concepts, features and configurations. Check out the GitVersion Github Action and Azure Pipeline Task for simple integration!
Be First to Comment