Releasing and hotfixing with single git master branch
Jan 08, 2020Git, Gitflow, CI/CD
People know gitflow, and they like it. It's a complicated guidelines about git branching, tagging and releasing strategies. Maybe it's good thing when it first came out - in 2010 - when GitHub wasn't that popular, Travis CI hadn't been founded and Jenkins was not called "Jenkins".
So let's take a look at what we have on the table today that were not there or not so popular that everyone can easily setup ten years ago:
- Continues integrations tools working together with GitHub: most of them regard git as first class citizen by implementing features around commits, branches and tags. They also interact tightly with GitHub (or other platforms such as GitLab) with web hooks and GitHub APIs.
- Continues delivery with automated end-to-end testing frameworks: every features and even every piece of code should be shipped and released in production asap, leading to the fact that most branches are short-lived and shall be merged asap.
- Docker and Kubernetes: dependencies can be easily defined, managed and cached, the whole running environment can be easily replicated, built up and teared down.
- Microservices and Single Page Applications: resource consumptions of services or applications are minimal and cheap. So it's possible to have a deployment for every single branch or even every single commit.
With those tools and concepts in mind, gitflow clearly becomes over-complicated and outdated. Because:
- Gitflow is decentralization friendly, just as git itself. However, with continues delivery in mind, each developer shouldn't hold changes on their own machine for too long.
- Most of the branch operations in gitflow are happening locally on developers machine, and pull request which is the most important step for CI tools is missing.
developbranch is basically useless if you can programmatically spin up deployments for each branch. And more long-lived branches means more maintenance overhead, more chances of merging features to the wrong branch, and more confusions for new comers to get to know where to start.
releasebranch is also an anti-pattern of CD. By following CD, changes should be release quickly and automatically. There should be no human interference during the release phase (human make mistakes, machines doesn't), not to mention that gitflow encourages developers to commit more changes on
releasebranch before it's actually released.
hotfixbranch is a good idea but when it comes to finishing hotfixing, gitflow becomes cumbersome again because of the
Enough talking about problems, here comers my solutions:
- Keep only one long-lived branch, by default it's called
- All the rest of the branches are short-lived, and they all checkout from
masterbranch, no matter it's
- Every time when a
hotfixbranch is ready to merge back to
masterbranch, a pull request should be created, and CI starts to run all the tests, including unit test, e2e test, code coverage check and code quality check. And CI also create a sandbox deployment of the incoming branch for reviewers or QA to validate the functionalities.
- When all the tests, checks and reviews are passing, and the branch merged into
masterbranch, then CI create new git tag one the latest commit to mark it as release candidate, as well as changelog, release note, etc. Then CD took over the baton and start automated releasing.
- Depends on release policy, the releasing can be either deployment directly to production environment or can be creating a staging deployment and notify product owners to sign off, and the last step of the signing off should trigger the production deployment automatically.
- As for
hotfix, it's every similar to
bugfixbut the steps to generate release candidates are slightly different, as shown in the graph below:
(Generated with gitgraph.js)
feature branch, git tag (release candidate)
v2.0.0 are generated as soon as they are merged into
v1.0.1 has been deployed in production, but
v2.0.0 has just been deployed in staging. Now there's a issue been found in production need to be fixed quickly but new features introduced by the
feature branch haven't been signed off yet.
Now here's the steps to do the hotfix:
- Create a new branch
hotfixfrom the current production version (
- Apply the hotfix commit:
h45h6. It can also be cherry-picked from commits that have already been merged to
- Create a pull request to
masterbranch (not shown in the graph).
- Since the base of
hotfixis different from the head of
master, a merge commit (
h45h7) from master has to be created and conflicts (if any) need to be resolved in that commit.
- As soon as all the testing, checking and reviewing are passing, the
hotfixnow can be merged into
- Once merged, a new merge commit (
h45h8) is created and the release script will be running based on this commit:
- Get the head of the former
export HOTFIX_HEAD=$(git rev-parse --short HEAD^2)
- Get the last non merge commit (which is
git rev-list --abbrev-commit --max-count=1 --no-merges $HOTFIX_HEAD
- Create a new tag on this branch. And since it's patched from
v1.0.1, the version should be
- Back to master branch, create a new tag of
v2.0.1on the latest master branch.
After all the steps, now we got 2 tags. And then 2 builds or releases can be triggered on each tag.
v2.0.1 can be deployed in staging environment automatically, and
v1.0.2 can be safely deployed to production environment since it doesn't include any new features introduced since
The steps might look complicated, however, once it's automated, the developers just need checkout a new branch, push changes, create PR and merge it. The less manual steps, the less chance of making mistake.