Mainline branching strategy using GitVersion(5 min read)

Many versioning and branching strategies are available. I prefer a simple approach: mainline development using GitVersion for automatic Semantic Versioning.

Branch diagram showing Mainline branching strategy

Branching strategy

Different development scenarios require different branching and versioning strategies. For instance:

  • Packaged apps with multiple concurrent versions.
  • In-house web applications with distinct integration, test, and production environments.
  • Open-source libraries with community development contributions.

I mostly work on commercial web applications with multiple deployment environments but only one production version. I also contribute to open-source projects. My strategy is straightforward: a single main branch with temporary feature branches that merge back into main. Feature Flags are used when necessary for testing without affecting production.

Additional branches are created only as needed, such as for urgent production fixes. For example, if new work is already merged into main, I will create a support/x.y branch from the last release commit for the hotfix.

I build and package the application once, deploying it to multiple environments with only runtime configuration changes.

A simple branching strategy minimizes complexity and accelerates deployment, though it may not suitable for application that need to support multiple versions.

Mainline strategy

The mainline approach keeps the main branch in a releasable state, automatically increasing the version PATCH number with each commit. After a release, the next commit to main is tagged with a new MINOR version number.

Process:

  1. Develop features on feature branches.
  2. Merge them into main via pull requests, increasing the PATCH version.
  3. Builds on main are potential releases.
  4. After a release, tag the next merge to main to increase the MINOR version.
  5. Create a support/x.y branch for post-release fixes, only if needed.
  6. Merge hotfixes into support/x.y and then merge support back into main.

Note: GitVersion also supports release/x.y branches, however they are configured by default for release candidate builds (e.g. 1.5.3-rc.1); for Mainline mode go straight to support/x.y branches.

Version numbering

I follow Semantic Versioning, with MAJOR.MINOR.PATCH format, which has strict semantics for libraries and APIs.

For applications, where there is no public API, I increase the MINOR version with each release and use the PATCH version for builds and hotfixes.

Increasing version numbers

In Mainline mode, the PATCH version increases with each commit to main. Unlike the default mode (which only increase build metadata), this simplifies integration with packaging systems like NuGet and containers.

I use tags to increase MINOR version numbers after each release, although Gitversion has several other ways to increase the version if you prefer. Using tags speeds up subsequent version calculations.

Using GitVersion

GitVersion automatically generates version numbers based on Git history. For instance, after tagging "v1.5", the next merge to main will be calculated as 1.5.1.

GitVersion supports various branching models but I use the simpler Mainline mode, focusing on a single main branch with feature and hotfix branches as needed. I avoid a separate develop branch unless it's necessary, such as for community contributions to an open-source project.

The typical GitVersion.yml configuration I use is:

assembly-versioning-scheme: MajorMinor
assembly-informational-format: '{SemVer}+{ShortSha}'
mode: Mainline
branches:
  main:
    regex: ^(origin\/)?main$
  support:
    regex: ^(origin\/)?support\/.+$
ignore:
  sha: []

GitVersion's main advantage is that version numbers are independent of the build system, calculated solely from Git history. This is useful for small projects where builds are run locally.

Limitations of GitVersion

GitVersion needs the Git history to calculate version numbers, which can require configuration changes in build systems (that often default to a shallow checkout) and can increase build time and disk space.

Branching diagram

The branching diagram was generated using Mermaid:

%%{init: { 'theme': 'base', 'gitGraph': {'rotateCommitLabel': false}} }%%
gitGraph
    commit id: "0.1.0"
    commit id: "1.5.0" tag: "v1.5"
    branch feature/n1-foo
    commit id: "pre-release 1.5.1-feature-n1-foo"
    checkout main
    branch feature/n2-bar
    commit id: "pre-release 1.5.1-feature-n2-bar"
    checkout main
    merge feature/n1-foo id: "1.5.1"
    branch feature/n3-waz
    commit id: "pre-release 1.5.2-feature-n3-waz"
    checkout main
    merge feature/n2-bar id: "1.5.2"
    branch support/1.5 order: 2
    checkout main
    merge feature/n3-waz id: "1.6.0" tag: "v1.6"
    branch feature/n4-hum
    commit id: "pre-release 1.6.1-feature-n4-hum"
    checkout support/1.5
    branch hotfix/n5-fix order: 2
    commit id: "pre-release 1.5.3-hotfix-n5-fix"
    checkout support/1.5
    merge hotfix/n5-fix id: "1.5.3"
    checkout main
    merge support/1.5 id: "1.6.1" type: reverse

Other branching strategies

GitVersion supports other branching strategies like GitFlow and GitHubFlow, often involving more complexity and separate develop and main branches. These are more suitable for libraries, APIs, or applications needing concurrent version support.

For complex applications, multiple long-lived support/x branches might be necessary. Alternatively, having separate branches for each environment (e.g., test and production) can clarify what is in each environment but complicate management.

For a detailed discussion on various branching patterns, refer to this article by Martin Fowler.

Conclusions and next steps

Keep your branching strategy simple until complexity is necessary. Start with a single main branch in Mainline mode, using GitVersion for automatic Semantic Versioning, increasing the PATCH number for each build.

For most projects, especially new applications, this approach suffices. Tag subsequent main merges with new MINOR versions for new builds. Create additional branches only when needed.

Fixes can often go directly into main for the next deployment. Create support/x branches only for urgent production fixes when new work is already merged into main.

Keeping it simple ensures smooth project management.

Leave a Reply

Your email address will not be published. Required fields are marked *