Skip to content

Simple Release Versioning with Trunkbased Development, SemVer and Conventional Commits

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:

  1. MAJOR version when you make incompatible API changes,
  2. MINOR version when you add functionality in a backwards compatible manner, and
  3. 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!

Published inCI/CDpost

Be First to Comment

Leave a Reply