similitude

published on
sorted in
code

The Most Natural Branching Model

This is about an argument I’ve had several times in my professional life as a software developer. To my mind the topic is pretty basic, no rocket science at all1. It’s about branching in version control. And there’s plenty of articles and documentation out there discussing that basic topic and describing best practices.2 So, I won’t make any claim that I could add essential new thoughts to it. But since that discussion keeps popping up every now and then I’ll write down my take on it and some observations and learnings just for later reference. And also because many of the results of a web search for “best branching strategy”3 are nothing but marketing filling material articles that try to be all too neutral or just mirror some ideas collected from their own web search. In their superficiality I haven’t found them helpful for decision making in a team. In contrast, this one’s gonna be opinionated and based on my own experience and learnings from the past decade.

TL;DR: GitFlow, though having gained notable popularity, came with substantial design flaws, but at least it made people come up with names for the nothing new but most natural branching model: The Cactus Model or Trunk-based Development.

Who needs parallel history?

So, you have heard about GitFlow. Sure, you have. It was very popular about ten years ago. And that was probably because it was very well thought-through and documented, really nicely presented in a well-written article, and first and foremost, it came with a set of clear rules. And people long for clear rules, especially tech people like us, don’t we? So, before I go into any criticism, I want to make clear that I really appreciate that model having been shared with the community. Making our ideas a common good instead of keeping them enclosed in the silos of our brains makes us move forward and grow together.

Anyway, here’s my bold statement: Alternate-history branches are bad 😱. If your branching model involves two or more permanent branches meant to be merged back and forth4 think twice whether it’s really worth the hassle.

Okay, it’s not like this kind of branching and merging is bad in the sense of killing kittens or inevitably ruining your team’s productivity. It’s just that it’s extra complexity added to your version control. And tech people like us want extra complexity to be be well-founded or avoided, don’t we? So, before settling on GitFlow or some derivative you should really ask yourself why you would want multiple branches telling parallel, divergent versions of code history5? At long sight, the only timeline that matters is the one documenting what changes were made to the main branch in which order.

Why complex branching doesn’t make sense

Now, before I go ahead and talk about the obvious alternative I want to point out what appear to be the core assumptions that led to the belief that multiple parallel branches are a good idea.

Branching ≠ Deployment

I often hear developers argue for two parallel branches because they want one for each deployment environment. The underlying common misconception seems to be that automated continuous deployment can only happen from a permanent branch that perfectly mirrors the deployment history. That’s not true.

To deploy a version-controlled codebase, essentially, you need a way to specify the commit that you intend to deploy at any time. While the most immediate way would be to just pass the respective commit hash to some deployment command, you’ll probably don’t want that as it doesn’t allow for automatic deployments triggered by plain commit events. What you’d rather want is some symbolic name on your version control that your CD can watch for. This is where branches come in handy, because branches are nothing but symbolic names for commits. Yes, it’s perfectly fine to rely on branches to point to the respective commits for your automatic deployments. But apart from acting as named pointers there’s nothing that branches do for you with regards to automatic deployment. Especially, there is no use in mirroring deployment history.6

No urge to merge

A common companion to the idea of having parallel branches to tell certain kinds of history is that those branches from time to time have to be “synchronized” by merging them. And not only those permanent ones but also all kinds of side branches, like feature branches, release branches, or hotfix branches.

While it’s obvious why you want to keep side branches up to date with the main branch, it’s quite questionable why everything happening on some side branch should need to go back into the main branch, even including the history of that side branch. My take on this is to be really relaxed about side branches and their history and only care about the main branch. As I said above this is where all relevant code history is written, whereas side branches only exist for one single temporary purpose and hence, are perfectly fine to stay open-ended or even be cut out, eventually. However, there should never the need arise to merge (as in, non fast-forward merge) them back into the main branch. Let’s prove this for the three common types of side branches mentioned above:

Feature branch
This is a branch owned by one developer (or small team) working on a specific feature. Its main purpose is to be able to implement that feature without getting distracted by concurrent changes to the codebase and without having to commit intermediary states to the the main branch. Only when the feature is ready all its related code changes go to the main branch at once. At that moment it doesn’t really matter7 when and on which base the original commits were made. If our goal is to keep the history free from irrelevant information then the cleanest way of merging a feature branch to the main branch is rebasing the latter onto the former and fast-forwarding the former to the rebased head (a. k. a. “rebase-merge”).8
Release branch
This branch is meant to point to a commit to be deployed to a given target or released as a package at a well-defined version. It’s not meant to receive any active development. The only changes that are acceptable as well as expectable on release branches are hotfixes. And those do not need to be merged back to the main branch. Instead they should be applied there independently. There are some best practices regarding a well-defined procedure for applying hotfixes by cherry-picking. But the important idea is that the main and the release branch are fixed by different commits (cherry-picks are actually less special than many people think).
Hotfix branch
This is either branched off of the main branch and then used for “a hotfix release”, making it effectively just a special case of release branch. Or it’s branched off of a release branch to work on a hotfix and then merge it back to that release branch. It all boils down to making hotfix commits to releases. Therefore the above also applies to this kind of side branch.

Keep It Linear, Lovely!

It might not always be true, but in this case K.I.L.L.9 is a special case of K.I.S.S.. As I pointed out above complex branching models are oftentimes less helpful than they were supposed to be. Instead, in most common cases it’s perfectly sufficient to stick with a much simpler branching model with just one main branch and only temporary side branches. While this idea has been nothing special for several decades it became the recommended alternative to GitFlow when people realized that the latter is rather getting in their way. It has since been referred to as Trunk-based Development with trunkbaseddevelopment.com having become the go-to information source.

I just love it for its simplicity and its similarity to nature10. Instead of having a Git graph full of merge commits we can just let it grow like a natural tree with one trunk and several branches that grow from it but never back.11


  1. Darn it! I’ll probably never write about rocket science. ↩︎

  2. Sources I recommend:

     ↩︎
  3. Well, search strings starting with “best” are prone to bring up pretentious results, aren’t they? ↩︎

  4. The coupling of the two branches is important here. My criticism does not apply to multiple permanent branches that are disjunct by design. Situations where, for example, several diverged versions of a product are maintained on corresponding branches are a different kettle of fish. And so are peculiar branches dedicated to special features of a repository, e. g. gh-pages↩︎

  5. I’m a huge fan of science fiction which implies a fascination of time traveling and alternative timelines. I also like the many-worlds interpretation just for its story-telling potential. But when it comes to writing and, even more, reading code I do not seek exciting stories to be told: I want it clean, dry, sober – in all dimensions, including the temporal one. ↩︎

  6. I’ve heard people argue that they want to see the history of commits as they have been deployed. Well, first of all, code history is actually linked in the commit itself, not in a branch. Second, code history matters at development time, not at deployment time where the code state of interest is in the one of the given commit, not any of its anccestor commits. But the main point here is that deployment history and code history are two distinct things. Why bloat your version control with branches that only exist to tell deployment history? Instead, just maintain a log of commit hashes and deploy timestamps outside of your version control (somewhere connected to your deployment target) and you have exactly what you need and keep different sets of information separated and where they belong. ↩︎

  7. Maybe it does matter to the commits’ author for some nostalgic reason, but to the whole team or other consumers of the code history (e. g. the public) it doesn’t. ↩︎

  8. How you actually merge your feature branch remains of course a matter of taste. You might be convinced of the fast-forward-only approach. But you might as well insist in keeping feature development details by enforcing merge commits. Beware, though, that merge commits are not the only way to preserve those details: Even after squash-merging a pull request and deleting the feature branch the original commits remain accessible (depending on some platform features). ↩︎

  9. I think, I invented it. 😤 ↩︎

  10. Although, I know that appeal to nature is not a valid reasoning. ↩︎

  11. Technically, merge commits or actually allowing the commit graph to be a DAG rather than just a (directed) tree is a great feature of Git. And there are valid use cases, for sure. But in many day-to-day situations merge commits are rather bloat to your history that could be prevented with a little care. ↩︎

Comments