Git Rebase Tips and Tricks
28 Oct 2025I don’t use a rebase flow on every project, but when I do, here are the habits that help keep things smooth.
The Basic Command
I use this formulation of the git rebase command
(1) The commit where we branched from
|
| (2) The branch we want to rebase on top of
| |
| | (3) The flag to keep merge bubbles
| | |
.------. .-------------. .-------------.
git rebase old_base --onto new_base --rebase-merges
- By providing the
old_baseexplicitly we avoid scenarios where git gets confused1. - The branch we want to replay all of our commits on top of.
- This keeps the empty commits that create merge bubbles.
Keeping merge bubbles is seemingly another contentious topic but I find them valuable.
For example with this listing using git lol2 I can see that feature1 was potentially less complicated than feature2 because it required less commits.
* cb08e97c1a Merge pull request #2 from feature2
|\
| * a5c310e392 Implement feature2
| * e07178d052 Refactor to make feature possible
|/
* 3fe7557433 Merge pull request #1 from feature1
|\
| * 07b845a110 Implement feature1
|/
*
* The branch names/commit messages in these examples are not good examples of naming/describing but I’ve kept them short to keep the example small.
This view also allows me to know what commits would need reverting if I want to back out a feature.
Verifying the Result
Clean Rebase
If the rebase was clean and there were no conflicts that I had to resolve, I tend to verify that the result is good by diffing between my local branch and the remote.
For this I have another alias git dfr3 (short for diff remote).
A successful result would essentially just contain a diff showing the changes that went into the base branch after the point the current branch was forked.
This breaks down when rebasing a branch that has gotten quite out of date with the new base. Keep in mind that the diff includes all the changes that went into the new base branch. This can produce a lot of output, and if you weren’t the one who made those changes, it can be tricky to reason about.
Rebase that had merge conflicts
When I’ve had to resolve merge conflicts during the rebase the above diff isn’t very helpful because the changes dealing with the merge conflicts are mixed in with the changes that went into the new base branch.
To get a better view of things I reach for git range-diff old_base..old_head new_base..new_head.
What this command does is it tries to find the same commit in both ranges using heuristics like commit message.
It then creates a diff between each pair of commits.
The output of this command is a little hard to read because there are potentially two levels of ± indicators in the gutter.
Persevere and it will make sense especially if you have coloured output in your terminal.
Fixing when it goes wrong
Using the verification steps above, I sometimes discover that I’ve messed up a merge conflict. I’d rather try and fix the broken commit itself over adding a new commit. To achieve this I reach for an interactive rebase following these steps:
- Find the SHA of the parent for the broken commit
- Run
git rebase --interactive parent_sha --rebase-merges - In the text editor find the sha I want to edit and change its option to
e/edit - Follow the usual process of a rebase to step through the reapplication of commits
If you’ve ever used git rebase -i before you’ll notice that adding the --rebase-merges flag really steps up the difficulty level.
For simple edits it’s easy enough to just ignore the commands like label, reset, merge etc and concentrate on the normal options you may be used to.
Starting again
Sometimes stuff really goes wrong and it’s time to admit defeat (for now) and call git rebase --abort.
Even after years of rebasing, I still end up here a lot. It’s not a sign of failure - usually it just means the first attempt went wrong and I’ve learned what to do differently next time.
Pushing changes
I always use the --force-with-lease flag when pushing changes as it’s slightly safer than plain --force.
Essentially --force-with-lease bails out if git notices that your copy of the remote is not up to date.
This reduces the chances of you clobbering someone else’s work because you’d need to do a git fetch to get the latest changes and then resolve any conflicts locally.
Preventing pain
To reduce painful rebases it’s good practice to rebase early and often. The longer you leave branches to diverge the more chances you have of getting conflicts so integrating as early as possible is beneficial.
Conclusion
The tips and tricks above have taken years to figure out. As well as knowing the commands I think practicing and trial/error are really the only way you get better at this stuff so don’t be afraid to get stuck in.
-
When doing a rebase flow if you get a bit behind you can find yourself needing to rebase branches on top of bases that have themselves been rebased. In this case git tends to pick the wrong base because it has to find the first commit in common. This can result in duplicate commits ending up in the output and potentially more merge conflicts to handle. ↩
-
The alias is configured in
~/.gitconfigin an[alias]section[alias] lol = log --graph --decorate --pretty=oneline --abbrev-commit -
The alias is configured in
~/.gitconfigin an[alias]section[alias] dfr = !sh -c 'git diff origin/$(git symbolic-ref --short HEAD)..$(git symbolic-ref --short HEAD) "$@"'