Git to know this before you do Trunk Based Development (TBD)

Fernando Villalba
Contino Engineering
7 min readApr 1, 2019

--

Introduction

Trunk Based Development (TBD) is often the best branching model and most recommended for continuous delivery workflows.

In this article I will explain some git concepts that you must know before you work in a TBD project so you can maximise collaboration with your peers. If you are unfamiliar with TBD, I recommend you read my other blog post where I compare it with other branching models and explain what it is. You can also visit this site for extensive information about TBD.

The workflow to follow in TBD is simple enough.

  1. You create a short-lived feature branch from trunk.
  2. You do your work and commits there, ideally these should be as thoroughly tested as possible locally and in CI server.
  3. When ready to merge to trunk, rebase it against your short lived branch to make sure any new changes to it don’t conflict with yours and that you can fast forward merge.
  4. Merge and squash (or rebase squash and then merge) your feature branch to trunk.

You do the above ideally one or more times a day. The features you integrate into trunk must not break the application (I am assuming you have thorough testing for every stage, or working to get there), every commit in trunk should represent a working state, if your changes are not ready use feature flags to disable them until they are.

Rebase and fast forward merge

When you merge a branch with another you are pushing in your commit at the point in history where you wrote yours in relation to the other branch. So let’s say we have we have our short lived branch we are working on with commits 1, 2 and 3,

8153364 (HEAD -> short-lived) 3
538a31d 2
ec8c427 1
...

However since we branched from trunk (master), someone has made two commits, a, b and c.

36d7dfc (HEAD -> master) c
ffefa92 b
cd675d5 a
...

Because we are doing TBD we want to make sure we are incorporating the latest changes from the trunk into our short lived branch before we actually do a final integration of a short lived branch into our main trunk (master). So in this case you may think, okay, so what I am going to first merge the changes from the master into mine, test them and then merge that back into master, right? Well, let’s see that in action, first we merge master into our short-lived branch and this is the result:

a9b2682 (HEAD -> short-lived) Merge branch 'master' into short-lived - This is a merge commit
36d7dfc (master) c
8153364 3
538a31d 2
ec8c427 1
ffefa92 b
cd675d5 a
...

Do you see what happened here? Commits 1, 2 and 3 are merged in your branch exactly as they occurred chronologically. Also notice how a merge commit gets added to the history, essentially marking the spot where merge was made - great, right?

Okay so now we pass all the tests and we are ready to merge this with master, fun times.

But wait… someone has just done another commit, commit d, annoying, but no matter, we can merge again, do another test, and finally merge, this time we succeed and here is the result:

*   b39f273 (HEAD -> master, short-lived) Merge branch 'master' into short-lived - merge commit
|\
| * 6719540 d
* | a9b2682 Merge branch 'master' into short-lived - merge commit
|\ \
| |/
| * 36d7dfc c
| * ffefa92 b
| * cd675d5 a
* | 8153364 3
* | 538a31d 2
* | ec8c427 1
|/
* c0a1e59 some previous commit
...

We did it! But look, now we have two merge commits instead of one, and look how the graph became a little convoluted because of this. Now imagine this happening over and over again with multiple developers, you will probably end up with something like this:

Source: https://www.endoflineblog.com/gitflow-considered-harmful

This happens when you do many merges and you are unable to fast forward them, or you run merges with the --no-ff flag in git. Fast forwarding occurs when the merge you are trying to merge has an identical history to your branch except for the changes you are trying to bring in, so instead of merging to the target branch, all the target branch has to do is add the changes from the source branch on top of its head like so:

Source: https://stackoverflow.com/a/52157972

When doing TBD, merge commits are largely redundant because it doesn’t help that much to know when merges occurred, we already know that we are working with short lived feature branches that will later be merged — pushing to master directly is disallowed — all we care about is seeing commits as logical units that represent a meaningful working change and keeping the history nice, clean and tidy.

So what can we do to solve this particular situation?

Rebase to the rescue

When you rebase a branch you replay your changes on top of the target branch like so:

Source: https://stackoverflow.com/a/52157972

What rebase does differently is replay your changes on top of the tip of master branch. So going back to the example above, if we were to rebase master against our branch, instead of getting a merge commit we would get this:

f693c9a (HEAD -> short-lived) 3
e8d80cf 2
15c98aa 1
d5ce550 (master) d
566512b c
ffefa92 b
cd675d5 a
...

As you can see, all of our changes from the short-lived branch were played on top of master, now we are ready to fast forward merge this short lived branch to master and we get this result.

f693c9a (HEAD -> master, short-lived) 3
e8d80cf 2
15c98aa 1
d5ce550 d
566512b c
ffefa92 b
cd675d5 a
...

Squash your branch before merging to trunk

One of the main advantages of working with short lived feature branches is that you can do pretty much anything you want in that branch and commit as many times as you want. However, once you merge your code to the trunk, you should only create commits that are functional increments.

This is because you may need to revert to older versions of your code and you want to have a reasonable degree of assurance that whatever you are regressing to is going to work.

You also may have a CI tool that tests your short-lived branch on a server whenever you push your code to it and you may want to do it multiple times while coding. In this case it can get very exhausting having to be creative with commit messages for every small change you do. But then again you wouldn’t want to merge all that into trunk and have loads of meaningless commits for everyone to read and get confused about.

The solution to this is to squash your commits when merging to trunk.

Source: https://stackoverflow.com/a/52157972
$ git merge --squash short-livedUpdating d5ce550..f693c9a
Fast-forward
Squash commit -- not updating HEAD
short-lived | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 short-lived

When running that command from your master branch your changes are added to staging and you are ready to commit them.

$ git commit -m "numbers"[master ba6dc9d] numbers
1 file changed, 3 insertions(+)
create mode 100644 short-lived

If you want to pick and chose the messages that you created for your commits in the other branch you can just run git commit without any arguments and an editor will open for you to select what you want as part of the commit message.

Note that your repository may have a way for you to make merge requests that require approvals and you can configure to squash your changes as well and disallow not fast forwarding. This is often preferable, and may be the only way for you to do it as that way your colleagues also get visibility of the merges and can see when something breaks.

Best Practices

  • Only fast forward merges to trunk.
  • Disallow pushing to trunk
  • Merge squash your multiple changes in your short live branches
  • Rebasing master against your short lived branch to keep it up to date is best, do NOT merge master back to your short lived branch and then back again to trunk, effectively creating multiple merge commits and confusing history.
  • Feature branches must be short lived.
  • If you are working from a ticketing system, make sure to first mention the ticket number in your commit, and dynamically link them if possible in your repo tool.
  • Keep your commit messages as concise as possible, insofar as they make sense.
  • In my opinion you shouldn’t do hotfixing in this day and age if you can avoid it, it’s best to fix forward and not go back, if there has been another feature released since then that you don’t want included, then disable it with feature flags. Remember, you want to keep the flow of updates and releases as short and constant as possible, hotfixing will only complicate your life and your repo.
  • Try and understand git commands and internals first before using graphic tools like git kraken. There is nothing wrong with them, and in many cases they can be very helpful, especially when looking at history graphs. However they are not a replacement for you understanding how git works and learning things like the difference between revert and reset.
  • Don’t ever commit secrets of any kind to the repository — EVER (Although sealed secrets could be okay).

--

--