The ultimate way to get rid of versioning administration is to use CI/CD (Continuous Integration/Continuous Deployment). However, in many cases CD is not possible. For example when creating libraries, or software requiring versions for compliance reasons. Versioning is then still needed. This should be easy, but it turns out that without a proper procedure it can become very messy very fast. This blog will describe a simple set up of such a procedure based on trunk-based development and Semantic Versioning.

Feature Branches

First of all, all features should be done using a feature branch that is merged to main through a Pull Request (PR), aka. a Merge Request. The recommended merging strategy to use is squash for merge commits to keep the version history on main uncluttered.

Feature Branches
Figure 1. Feature Branches and commits

(created with Umletino, source file)

Feature branches should be short-lived and usually correspond with a user story. So user stories should be small or else be broken up into multiple smaller ones. After merging a feature branch into main it can be deleted. CI (Continuous Integration) is achieved by merging all feature branches to main as soon as possible (trunk-based development).

Feature branch name format: feature-<issueNumber>-<issueTitle>.

Versioning

Use Semantic Versioning. This basically means, use version number format: <Major>.<minor>.<fix>, with:

  • <Major>: major version number, only changes upon big changes.

  • <minor>: minor version, changes when a small new features is added.

  • <fix>: fix number, changes when a bug is fixed.

Versions correspond to tags in the source repository.

Release Branches

In order not to block CI on main for production fixes Release branches are used.

Release Branches
Figure 2. Release Branches and tags

(created with Umletino, source file)

A production fix is also known as a hotfix or a patch version.

Release branches are not meant to be merged to main. But fixes are cherry-picked via a dedicated feature branch for this fix to main. Cherry-picking is used because release branches and main tend to diverge quickly.

Release branch name format: release-<majorVerion>-<minorVersion>.

Example:

To fix the release:

  • a problem is found in tag M.m.f on main, which is already in production

  • a release branch release-M.m is created that spawns from this tag

  • a feature branch is created that spawns from release-M.m

  • the fix is implemented in this feature branch

  • a pull request (PR) is created to merge this feature branch to release-M.m

  • after merging a new release is created by creating tag M.m.(f+1) on release-M.m

To fix main:

  • a feature branch is created that spawns from main

  • the fixes are cherry-picked from the release branch to this feature branch

  • a pull request (PR) is created to merge this feature branch to main

shadow-left