Most of the time when people start to talk about “source control strategies” I flippantly respond with “if you need a strategy for your source control, you’re probably doing it wrong” – and to a point, I want to echo that here; The absolute best thing source control could ever be, is a magic box in which you put your bytes, which is durable, supports quality version tracking, and most importantly, gives your bytes back to you in the exact way you put them in. Any strategy required on top of that, should be absolutely minimal to reduce any mental overhead. Thankfully, git is fantastic at providing those core qualities of a good revision control system, but true to form, it’s flexibility can lead people to wondering what the “right way” to branch merge and tag is.
First though, some assertions:
- A branch and a tag in git are functionally equivalent pointers to a hash
- When building and releasing software, it’s important to know where the last production build came from
- Continuous integration is not optional – good CI is vital to building quality software
Given these assertions, in my opinion there is a fairly obvious and friction free way to use git.
A two branch strategy
I prefer a two branch strategy, and have implemented this in a number of organisations and projects.
The two branches I suggest are:
Master is git’s default “main” / “trunk” / “where the code is” branch that you get when you create a git repository. If you’ve used git, you’ll have seen a master branch. Integration is the “working” pre-release branch that your CI server should be continuously building.
The core idea is that master should always represent the last released version of your codebase, while integration provides a branch for your developers to merge their code in many times a day. Your integration branch is then the source of builds for a staging or testing environment in a continuous delivery system.
Here are some common scenarios and how they work in a two branch master/integration setup
I want to deploy the current version of my code
- Sign off your integration (either manually or automatically)
- Tag the current head of integration with a release or version number
- Merge integration into master
- Release master
I want to implement a new large feature or user story
- Branch off master (last production release) into a short lived feature branch
- Write your code
- Merge your code into integration for your CI server to test and deploy
I want to implement a fairly trivial change (maybe just a single commit)
Just go ahead and commit that change to integration.
We have two versions of our software, and we need to bug-fix version 1
- Find the appropriate version 1 release tag and create a new branch from there
- Make your changes on this branch
- Compile and build a release
- Tag the head of your bug-fix branch with the new release version
- Merge your bug-fix branch into integration (resolve any conflicts)
- If that merge was conflict free, go ahead and merge it into master, otherwise, merge integration to master with the conflict resolution
Two developers are working on similar features on feature branches but keep getting merge conflicts!
Don’t panic. If you’re continually conflicting while merging your feature branches into integration, it’s an indication you should probably just be sharing a branch and solving this problem upstream.
I’ve been working on my feature for weeks, and when I merged into integration, I had to resolve a huge merge, help!
You need to be merging your feature branch into integration more often than you’re doing to negate this problem. Long lived features branches are a huge anti-pattern because they entirely remove the benefits of continuous integration. Instead of relying on a branch to make your changes “safe”, instead, you should consider ways to integrate your code “inert” if you’re working on a longer lived feature (consider configuration toggles / feature “walling”). Make sure you bring your changes into integration as soon as humanly possible to avoid these kinds of conflicts.
Master is production. Integration is staging. When writing new features, always branch off master, and merge into integration. When you want to promote staging to production, merge integration to master.
This branching strategy provides
- A known version of the released code
- A method to bug-fix previous versions
- Full support for continuous integration
- A workflow so simple that you don’t pay a mental cost using it
- Great support for continuous delivery (auto-deploy master to production, and integration to staging, every commit. To release, just merge to master)