This post was taken from a rough gist I created whilst strategising a GitFlow branching strategy in a new team setting. Views may have likely changed since then and no on-size fits all.

Introduction

However you and your team manages branches; it’s important to have at least some kind of strategy that everyone adheres to. Rules and process should be designed to keep things consistent within the team and changes flowing to avoid complex merges and other such issues with multiple work steams on a single code repository.

Strategies can also evolve and developers can conflict with differing views. Most developers will at least attest that having some strategy is better than having no strategy. As for what’s right for you and your team is no simple answer. It can be very dependant on the software your developing, team size and frequency of changes.

In this post I’ve detailed what approach I recently took with a new team on existing code base by adopting mostly GitFlow approach..

Guidance is largely stolen from the following articles:

What is Gitflow?

Gitflow is a branching model for Git, coined by Vincent Driessen in 2010. It has been largely adopted by many teams as it is very well suited to support collaboration and scaling the development team whilst supporting changes and potential hot fixes.

Vincent Driessen has exercised some caution and he has since suggested a simpler GitHub Flow for scenarios whereby software is continuously delivered and does not need to support multiple or explicit versioning. nvie.com/posts/a-successful-git-branching-model/

So why still use Gitflow?

Although our applications are largely Web based, continuously delivered and only one production version, we still required explicit versioning. There are also other benefits and will continue and formalise our strategy with Gitflow. This repository’s goal is to explain this but also provide a test bed for branching, versioning and packaging.

Infinite Branches

Each repo should infinitely hold two main branches:

  • main (could be known as but should be main for more inclusive language)
  • develop (default, compare)

For our purposes we have retained the name master in some existing projects and moved to main for newly created repositories.

We consider origin/main to be the main branch where the source code of HEAD always reflects a production-ready state. This is the build that get’s packaged and versioned as the release

Rules for main branch

  • The main branch tracks released code only.
  • The only commits to main are merges from release branches and hotfix branches.

Rules for develop branch

We consider origin/develop to be the main branch where the source code of HEAD always reflects a state with the latest delivered development changes for the next release. Some would call this the integration branch.

This is where any automatic nightly builds are built from - which we don’t do today but could/should -

Supporting branches

The different types of branches we may use are:

Feature branches:

  • feature/*
  • bugfix/* (not a hotfix branch and still acts like a feature branch and the same flow as features)

Release branches:

  • release/* (the shortest lived of all Gitflow branches)

Hotfix branches:

  • hotfix/*

Feature Branches

We consider origin/develop to be the main branch where the source code of HEAD always reflects a state with the latest delivered development changes for the next release. Some would call this the integration branch.

This is where any automatic nightly builds are built from - which we don’t do today but could/should

Rules for feature branches

May branch off from: develop

Must merge back into: develop

Feature branches are branched off of the develop branch, and finished features and fixes are merged back into the develop branch when they’re ready for release

Release Branches

When it is time to make a release, a release branch is created off of develop:

Release branches isn’t the branch to trigger a deployment but rather a branch for formalising and merging in changes ready for a deployment.

When the release is finished, the release branch is merged into main as well as develop, to make sure that any changes made in the release branch aren’t accidentally lost by new development.

The code in the release branch is deployed onto a suitable test environment, tested, and any problems are fixed directly in the release branch. This deploy -> test -> fix -> redeploy -> retest cycle continues until you’re happy that the release is good enough to release to customers.

Rules for release branches

May branch off from: develop Must merge back into:develop and main Branch naming convention: rc/*

Hotfix branches:

Hotfix branches are created directly off the main branch

Rules for Hotfix branches

Setup & Install

Gitflow is a strategy and doesn’t require any install as such however git does provide some helpers. Using the helpers enforces sticking with the strategy and therefore recommended.

Below is the git flow helper commands vs what commands get ran by git. Useful to understand what each command executes. You could run the individual git commands manually should you wish.

Initialize Repo

Configures the develop and main branches

gitflowgit
git flow initgit init
 git commit --allow-empty -m "Initial commit"
 git checkout -b develop main

Create a feature branch

Creates feature branch ensuring it’s from develop branch

gitflowgit
git flow feature start my-featuregit checkout -b feature/my-feature develop

Share a feature branch

Pushes the feature branch to the remote server

gitflowgit
git flow feature publish my-featuregit checkout feature/my-feature
 git push origin feature/my-feature

Get latest for a feature branch

Pulls the feature branch from the remote

gitflowgit
git flow feature pull origin my-featuregit checkout feature/my-feature
 git pull --rebase origin feature/my-feature

Finalize a feature branch

Merges the feature branch into develop and ensuring a regular merge with no-fastforward is performed - only use when not creating a pull request for review

gitflowgit
git flow feature finish my-featuregit checkout develop
 git merge --no-ff feature/my-feature
 git branch -d feature/my-feature

Releases

Create a release branch

gitflowgit
git flow release start 1.2.0git checkout -b release/1.2.0 develop

Share a release branch

gitflowgit
git flow release publish 1.2.0git checkout release/1.2.0
 git push origin release/1.2.0

Finalize a release branch

gitflowgit
git flow release finish 1.2.0git checkout main
 git merge --no-ff release/1.2.0
 git tag -a 1.2.0
 git checkout develop
 git merge --no-ff release/1.2.0
 git branch -d release/1.2.0

Hotfixes

Create a hotfix branch

gitflowgit
git flow hotfix start 1.2.1 [commit]git checkout -b hotfix/1.2.1 [commit]

Finalize a hotfix branch

gitflowgit
git flow hotfix finish 1.2.1git checkout main
 git merge --no-ff hotfix/1.2.1
 git tag -a 1.2.1
 git checkout develop
 git merge --no-ff hotfix/1.2.1
 git branch -d hotfix/1.2.1

Pull Requests

Pull requests should be used for merging most feature branches.

As a general rule, on the completion of a pull request, use the Merge type of Merge (no fast forward) and avoid Squash and Rebase merges unless the impact is full understood. Gitflow helper **git flow feature finish my-feature** does this automatically for you.

Versioning

We should ensure Semantic Versioning throughout our applications, services and packages. The format should be [Major].[Minor].[Patch].[BuildNumber][BuildSuffixTag]

Major - indicates a breaking change has been made and the client will have to change in order to use the new version of the service. Major releases are infrequent and slower to deploy

Minor - indicates that minor features or significant fixes have been added

Patch - indicates a minor bug fix or any other insignificant changes

BuildNumber - indicates the incrementing ID from our Build Server.

BuildSuffixTag - optional - signifies it’s not a production build / release and differentiate different builds off the same branch.

Feature branches and the develop branch build will have either an -alpha or -preview suffix (e.g. 2.6.0.123-alpha).

Release branches and hotfix branches should always build release candidate branch build will have an -rc suffix (e.g. 2.6.0.123-rc).

main branch should always build unqualified versions - versions without build numbers (e.g. 2.6.0).

Version Numbers And GitFlow Branches

Here’’’s what to build from which branch.

Feature branches and the develop branch should always build -alpha versions (e.g. 2.6.0.123-alpha). Release branches and hotfix branches should always build release candidate versions (e.g. 2.6.0.123-rc). The main branch should always build unqualified versions - versions without build numbers (e.g. 2.6.0).

Please remember:

When you create a new branch, you need to manually update the software’s version number.

If you’re using RPM, you need to put the buildNo part of the version number into the release tag in your spec file (or add a release tag to the configuration section if you’re using Maven’s RPM plugin). What You Should Install Where

As a rule of thumb …

  • -alpha versions should only be installed on dev boxes and integration environments. They shouldn’t be deployed to any of the dedicated test environments.

  • -rc version should be installed in the dedicated test environments. In an emergency, a release candidate can be installed into the production environment.

  • Production releases can be installed anywhere - and they are the only kind of build that should be installed into the production environment.

Building

All builds should be defined in a azure-pipelines.yml that is on the root of main when using Azure DevOps Pipelines.

The yaml file should begin with the defined triggers:

trigger:
- main
- develop
- feature/*
- bugfix/*
- hotfix/*
- release/*
steps:
- task: parse-release-notes-build-task@2 # custom task that takes the version from a release notes file (change log)
  displayName: 'Parse RELEASE_NOTES.md'
  inputs:
    releaseNotesFilename: './release_notes.md'
    semverOutputVariable: 'CustomBuildNumber'

- task: PowerShell@2
  displayName: 'Is Alpha Build?'
  condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/heads/rc/')))
  inputs: 
    targetType: 'inline'
    script: |
        Write-Host "##vso[build.updatebuildnumber]$(CustomBuildNumber).$(revision)-alpha"
        Write-Host "Build.BuildNumber: $(Build.BuildNumber)"        

- task: PowerShell@2
  displayName: 'Is Release-Candidate Build?'
  condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/heads/rc/'))
  inputs: 
    targetType: 'inline'
    script: |
        Write-Host "##vso[build.updatebuildnumber]$(CustomBuildNumber).$(revision)"
        Write-Host "Build.BuildNumber: $(Build.BuildNumber)"        
That's a wrap. Spotted something incorrect? Let me know! You can comment below, create an issue on the GitHub repository or best of all, make a pull request.