Aruba - Divi-Divi Trees · Photo by roger4336 · CC BY-SA 2.0 · source
Trunkbased is long lived in a casual relationship
TL;DR
Here be dragons – TLDR comes last
Introduction
In FAANG trunkbased development is the thing to do. Developers who adhere to this often think it is the holy grail of development. Because DORA metrics value it. Squashed merges make committing easy, you don’t need to worry about good commit messages, everything gets lumped together and deployed the next minute. Committing at scale blurs the line between best practise and necessary evil.
Long lived branches
Some people think long lived branches who tail the default branch by 50 commits are a danger, difficult to merge or contain a lot of conflicts. 50 behind is a speck of dust, you rebase, and you are back in business again. I dont see why people have issues with it.
I have three modes: The first one is create a branch and keep it at the
fork-point till I’m ready to merge and I’ll rebase prior to submitting
inclusion (pr/mr/patch-files via mail).
The second being: I rebase daily or semi daily, meaning every other day. And a
third one is two on steriods and means I’ll autosquash any fixup commits
present. I’ll be mostly in in mode 2 or 3 for bigger projects. Mode 1 is for
shared branches or really short lived things or things that have one commit
does it.
I have built a sync script that rebases automatically. I can run that and it rebases all branches (remote and local) of mine. When a rebase fails it leaves a marker and the only thing I need to do is look for the markers and solve minor issues and go on. I actually built this script when I first discovered rebase and thought: what if we can do this for all branches.
Why don’t I care? I have test/experimental/concept branches where I test assumptions of code, add features that I might want to incorporate but aren’t paid for yet. See if changing a part of the code makes things better, it’s code that doesn’t need to hit production straight away. Concepts that I want to use. I can work on these things let is sit and mature, keep or throw away. And I’ll probably rebase and the branch is just ahead and not behind anymore.
And sometimes, I’ve had this with submissions to FOSS projects, it takes a
while, so a long running branch is just to wait for approval/inclusion and I’ll
just keep it fresh every whatever time I think about it or check the status. Or
don’t, because.. why would I?
And another reason: I’ll build based on development versions of deps, so I’ll
wait till the version is deployed/released till I submit it.
And yet another reason: dog food the changes first myself before releasing it
or including it.
In short: both short and long living branches are first class citizens in my world. Why would I worry about one or the other?
Squash merging is the enemy
Why? The short version is that I prefer anatomic commits and squash merging removes that granularity. The long version is, squash merging is a way to cope with developers who are bad at git. Because they don’t have commit hygiene and squash merging solves that problem. But you lose context, the ability to remove/revert a singular commit, and surgically spot and fix the issue. Git bisect hates squash merges, and that is because you lose the iterating part of changes that were built. You only get big blob caused the issue.
You can revert a merge commit very easily: git revert -m1.
The individual commit bisect told me that caused the issue or nothing and we can fix it because bisect told us what broke it and the fix is better than reverting.
Long lived branches cause merge conflicts
People seem to conflate time with code proximity. Whether it are 8 or 1 commit it doesn’t really matter if the content in the end is the same. Meaning 8 commits to reflect a change that is done in 1 still means you have the same changes. So I don’t really have an issue with wholesale rebasing.
In the rebase editor I can even rebase based on some other branch if I want.
I’ve had situations where I had to target a branch based on main to a specific
(release)branch, I’m happy to remove commits to just have my feature/bug
commits applied on the new base. In this case I needed to change from
upstream/master to upstream/preprod which is always behind master whereas
my code had upstream/master as a fork-point.
With such an action you can easily remove 80+ commits from your branch and be done with it, 8 or 1 commit of mine doesn’t change a whole lot.
The mental overhead of a long term branch
Some people claim a long term branch causes mental load because they need to remember things.
To this I say: I will violate my own rules. I’m not always creating the best anatomic commits ever. I also add additional things in a commit that aren’t technically the way you want an anatomic commit. It happens, proud, no, happens, yes.
You can merge those commits and it is essentially what you are doing in squash merge context but in mine it has less surface area (smaller diff on the commit itself).
The true-er response: I code in “blocks”, I’ll add a feature, eg, right now I’m adding a payment unit to a registration site:
- Add payment processor code
- Add payment processor screen
- Add payment confirmation code
Three commits that are “anatomical”, anatomical in my book can be SUPER
specific and a little broader. I’ll commit things once I have what I want, and
it sorta works. And then continue with the next step. If in the next step I
might see I made a mistake: I’ll fix it up (commit --fixup) or I’ll add it to
the next step. The point being is, anatomic is a weebit flexible.
Do I split commits because I think they are too big? Yes, I’ll happily do that if I think it benefits and if I have time or I really think I should have done it differently. It’s not an afterthought or constant planning it is how you work. If you work scattered it’s gonna be difficult.
Trunk based development does the same thing. Their one commit per MR is ensuring something that works and than ship it. The mental overhead is in this case shared with the team. Feature flags now populate the code base because parts of the code are not enabled and others are. Everyone now has to deal with that deviation. In a so called long-lived branch this overhead is limited to one person. Or a subset of people depending on how shared the branch is.
In addition, I now need to decide in advance how I’ll develop my features across multiple commits to get it working in trunk-based development. Compare it to a branch where I can take my time to structure the commits in such a way that the feature can be delivered in segments. Long-lived branches offer the same while doing it in isolation without having a team of developers having to deal with the feature flags.
Trunkbased
I’m not knocking on trunk based development. I use it at times. I’m just not convinced it is the best way to develop with or in. Trunk based development depends on how your release model works, how risk averse your business or clients are. FAANG has different metrics to which they describe success, moving fast is a KPI for them. For my previous employers fast wasn’t an option. Clients in governement require different speed and thus way of working.
In the end. Trunk based development is just a hi-speed version of long term branches. It is the equivalent of Tinder to a long term relationship in real life.
Swipe right if you liked this article.