Skip to content

Tag: versioning

Trunk Based Release Versioning – The Simple Way

Some while ago I wrote an article about release versioning using Trunk-Based development where I used GitVersion for calculating the release version. Since then I have experienced multiple issues with that setup up to the point when I had enough and decided to make my own release versioning script, which I simply call Trunk-Based Release Versioning!

There is no complex configuration, it’s just a bash script that can be executed in a git repository. It follows the Trunk-Based Development branching model and identifies previous release versions by looking for SemVer formatted tags. It outputs at what commits the next release starts and ends and includes the version and the commit messages. Bumping version is done by writing commit messages that follows the Conventional Commits specification.

Trunk-Based Development For Smaller Teams

This is the simplest strategy, there is only the default branch. A release contains everything between two release tags.

Scaled Trunk-Based Development

Development is done on short lived development branches. It is possible, and preferable, to continuously release by merging into the default branch. All commits between two release tags make up a release, same as for the previous strategy, but it is also possible to pre-release from the development branch as pictured by the red boxes. A pre-release contains all commits on the branch since last release and when merging it all becomes the new release. Note that previous merge commits from the development branch onto the default branch also are considered release points in order to be able to continuously work from a single branch. This also works if merging the opposite way from the default branch into the development branch in order to for example enforce gated check-ins to the default branch.

For a full example using Github Actions, see this workflow.

That’s it! Keep releasing simple.

Leave a Comment

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!

Leave a Comment