You have a “feature branch” in git that you’ve been working on for a while but
now main
or master
has moved on. You know of merge
and rebase
, but
which one should you use? And what can you do to avoid being in this position
in the first-place?
TLDR
Try rebase. If that dissolves into conflict-resolution-hell then give up, merge master into your branch and move on.
The Options
You need to bring your feature branch up to date with with master to flush out any incompatibilities and deal with any merge conflicts.
You have two common choices:
- Merge
origin/master
into your branch. - Rebase your branch onto
origin/master
and force-push.
The Trade-offs
A blanket rule here either for merge or rebase is unhelpful because there are trade-offs to be made that vary depending on the specific circumstances. (Isn’t that always the answer with git?! “It depends!”)
Should You Merge?
A merge from master
is done like this:
git fetch
git merge origin/master
git push
Merge - The Good
- 👍 Reliable no-brainer that anyone can follow by rote.
- 👍 Resolve conflicts only once.
- 👍 Accurate representation of what happened over time.
- 👍 Avoids retrospectively introducing bugs and test failures into commits that used to be valid.
- 👍 Avoids re-writing previously shared branch, which can confuse less experienced git users if they are working with you on the branch.
Merge - The Bad
- 👎 Doing this repeatedly makes for a messy history for little or no benefit.
- 👎 Significant merges from master makes it harder/impossible to then go back and
clean your branch’s commits with a
git rebase --interactive
. - 👎 Tends to generate wide tramlines in the commit
history
that can be very hard to follow when looking back to find out when/why
something was done. (mitigated by
git log --first-parent
, until you need to dig into a branch).
Should You Rebase?
A rebase onto master is done like this:
git fetch
git rebase origin/master
git push --force-with-lease
Rebase - The Good
- 👍 Avoids tramlines generated by long-lived feature branches branch
- 👍 Makes resultant history in
master
much easier to follow - 👍 Reflects the intention more clearly of “merge these commits into master” as opposed to “here’s how I flailed my way to a working thing”
Rebase - The Bad
- 👎 Can confuse less experienced git users if they are working with you on the
branch (the answer is usually for them to run
git pull --rebase
) - 👎 Results in resolving conflicts multiple times (screencast)
- 👎 Loses chronological order of creation of code (personally I think this is
less important than a series of clean intentional patches to be applied to
the codebase when merged to
master
) - 👎 Could in somewhat rare circumstances retrospectively introduce bugs and test failures into commits that used to be valid
Heuristics To Use
Try rebase. If that dissolves into conflict-resolution-hell then give up, merge master into your branch and move on.
“Try rebase. If that dissolves into conflict-resolution-hell then give up, merge master into your branch and move on.”
~ Tim Abell
Rebase is my preferred approach until:
- Rebase becomes too costly to fix up due to conflicts with
master
, or - I become aware of an incompatibility with
master
that changes the meaning of the previous commits and needs serious work to resolve.
You can usually make a difficult rebase work, and I’ve hunkered down and tackled probably more than I should have in the name of perfect history graphs.
The problem with a tricky rebase is that if you are doing this for business and not just for fun then there is a major time cost for only a marginal benefit.
How to make it through rebase conflicts unscathed
If you decide to battle on with rebase in-spite of conflicts then my tip for you is:
Don’t jump straight to the “correct” code when fixing each commit’s conflict, as that guarantees the next commit won’t apply.
Instead as you work through the rebase make each commit apply with its original meaning and nothing more.
It’s worth remembering that each commit on your branch describes how to change the source code from a before-state to an after-state; so if you change the after-state of one patch, then the next patch will no longer apply.
How to avoid the pain of rebases and merges entirely
Pain around this topic is likely a symptom of not breaking down your stories /
pull requests / features into small enough chunks. On a fast moving team
master
is very fluid and any large & long-running branches will be hard to
review and merge. Try to chip off smaller increments and ship those, maybe
using feature flags or hidden features (those with no visible way of getting to
them).
More Resources
In general merge vs rebase generates much debate, such as that found on stackoverflow: https://stackoverflow.com/questions/804115/when-do-you-use-git-rebase-instead-of-git-merge but it is often lacking context.
There are many other articles on the merge/rebase topic such as https://derekgourlay.com/blog/git-when-to-merge-vs-when-to-rebase/ but I couldn’t see anything that matched my heuristic for tackling feature branch updates so I wrote this one.
“Semantic merge conflicts” are where git reports no conflict but nonetheless the code is broken.
I also wrote “GitHub rebase and squash considered harmful” which address a github specific horror.
Get in touch
Hey there! Thanks for reading!
This post gets far more traffic than anything else on my blog. I’d love to know what brought you here and if the above was helpful.
Please take a moment to fire an email to me at tim@timwise.co.uk and tell me a bit about yourself.