Merge projects seamlessly with our NEW GitLab integrationLearnmore
Software Development

Git tips for trunk-based development

Alessandro Diaferia

Trunk-based development is a source code branching model aimed at mitigating code integration and delivery risk. If implemented successfully, it helps reducing lead time in delivering value to production.

In this post, I'm not going to focus on the benefits of trunk-based development but rather, I'll try to outline a few examples of how git can be leveraged to achieve better results when implementing this kind of branching model.

git pull -r

With trunk-based development the master (or trunk) branch will likely update pretty often while you're off implementing your little feature in your own branch. In order to keep in sync, make sure your change-set always gets applied on top of the latest version of master:

  1. Make sure you're on your feature branch:
$ git branch
master
* my-awesome-feature

2a. Now let's replay our changes on top of master on your local branch:

$ git pull -r origin master

This command makes sure the latest changes from origin are fetched before rebasing on top of them.

2b. Another option, if you want more control over your commits is to rebase interactively:

$ git pull --rebase=interactive

This will list all the commits that are going to be applied on top of master on your local branch:

pick 1ff6000 Brilliant commit
pick f144bad Even better commit
pick dc69aa1 Average commit

You can follow the instructions included in the editor to fine-tune your list of commits and maybe change something like squashing the last two commits.

pick 1ff6000 Brilliant commit
pick f144bad Even better commit
fixup dc69aa1 Average commit

NOTE: fixup will discard the commit message of the commit being fixed up

Temporarily parking your changes

You might have started developing something new locally when somebody from the team needs you to review their pull request. In order not to lose your progress you have to make sure you store it somewhere before checking out the changes to review.

git stash

git-stash helps you with this by stashing your changes locally.

$ git stash -u -m"Exploring a possible AI-based blockchain compiler 🤯"

The -u switch ensures that also untracked files get stashed, which is pretty neat.
Stashing does not require a message but I find it pretty useful when resuming my parked work.

Now you can make sure your changes are stashed by doing:

$ git stash list
stash@{0}: On ai-spike: Exploring a possible AI-based blockchain compiler 🤯

You're now ready to check out someone else's code. When you're ready to get back to what you were working on you can:

$ git stash pop

which will automatically apply the latest stashed change and remove it from the stashes collection (if no conflicts occur, otherwise you will have to manually git stash drop it once you resolve the conflicts).

Or, you can use

$ git stash apply

which will do the same as pop but without automatically dropping the changes from the stashes collection.

Both commands accept a stash reference (in the form of stash@{<index>}) which you can use to unstash specific changes.

git commit -m"WIP"

Another option, especially if you need to push your temporary changes to origin (maybe because you want to finish work from a different machine) might be temporarily committing everything and coming back later to clean up the branch history.

What you can do is:

$ git branch
* my-temp-branch
master
$ git add --all
$ git commit -m"[WIP] Current progress on AI-based blockchain compiler 🤯"

I like to prefix my commits with a common label like WIP (work in progress) when this is the case.

When you're ready to resume your work you can do:

$ git checkout my-temp-branch
$ git reset HEAD~1 # assuming you've stored everything in the last commit

reset will restore your current work directory state to how it was just before the commit, keeping the modifications of your WIP commit (unless you specify --hard).

git rebase

Even though I have mentioned rebase as part of the pull section, I'll use the pure git rebase command here to cover a slightly different case.

If you think the commit history of your current feature branch could use a little clean up git rebase might come in handy.

Some times you might have a couple of commits that could be squashed into one as well as maybe reordering a few other commits to give more clarity to the overall branch history. All of this can be achieved pretty easily using git rebase --interactive.

Let's say you want to review all the 7 commits you've got on your branch since master. (PRO TIP: If you don't remember how many commits you have diverging from master you can do: `git log master...` which will visualize only the relevant ones)

$ git branch
* lovely-branch
master
$ git rebase -i HEAD~7

Your configured editor will open up with your 7 commits waiting to be taken care of:

pick 30d730d It begins...
pick abd7151 Made some progress...
pick ba722fd Tests pass!
pick 06566b7 Added one more test case
pick 6bc75a2 Refactored class names
pick cb81607 Added docs
pick 2f9b368 Fixed typo in docs

We could rewrite history as follows:

pick 30d730d It begins...
sqaush abd7151 Made some progress...
fixup ba722fd Tests pass!
fixup 06566b7 Added one more test case
reword 6bc75a2 Refactored class names
reword cb81607 Added docs
fixup 2f9b368 Fixed typo in docs

Upon the first squash we will be requested to amend the commit message: we can specify something more descriptive like "New compilation target: WebAssembly". The subsequent `fixup`s will lose their commit message.
Then, the first reword item will require us to specify a new commit message: "Renamed WASM* classes to WA* classes". Finally, the last rewordwill become: "Added missing documentation section for the WebAssembly target".

b981780 New compilation target: WebAssembly
16cde4c Renamed WASM* classes to WA* classes
451eaf2 Added missing documentation section for the WebAssembly target

A much cleaner branch history.

Use the --force (with lease)

If your branch had already been pushed to your remote before rewriting its history you will have to be explicit about overwriting it.

$ git push --force-with-lease lovely-branch:lovely-branch

This will overwrite the remote history of the current branch if it is as expected (e.g. it matches the local ref of origin).

"What --force-with-lease does is refuse to update a branch unless it is the state that we expect; i.e. nobody has updated the branch upstream. In practice this works by checking that the upstream ref is what we expect, because refs are hashes, and implicitly encode the chain of parents into their value."

Unfortunately, --force-with-lease will not be able to protect you if you (or your IDE) does fetch in the background because git won't be able to detect any differences with the expected remote ref.

In general, with trunk-based development, the chances somebody else works on the same feature branch in parallel to you are quite low, if not non-existent. This is because with this branching model you are supposed to work on small changes at a time, enabling all the developers to work on different things (hence different branches) at the same time.

Conclusion

The commands I have presented are part of my usual development workflow. I hope this overview has been helpful to you and please, leave feedback and let me know how you use git to achieve trunk-based development.