Git rebase - more than just conflict resolution
Rey-Michael Fichter
Posted on January 11, 2021
Resolving git conflicts with rebase
This first section will explain the basics of the git rebase command. You can skip over to the next section if you're already familiar with the git rebase
command.
Say you have worked on a feature in your application for a day and prepared a branch with two new commits for a pull request. In the meanwhile, three new commits have been merged to master and you have potential conflicts.
One of the best strategies to resolve (potential) conflicts is to rebase your branch on top of the latest master.
git fetch --all # Fetch latest git state
git rebase origin/master
This will effectively "move your branch" so it's based on the latest commit (hence the name re*base*).
The best model for understand how this works is to think of git rebase like your own little monkey that checks out a branch new from origin/master
and re-runs all of your branch's commits one by one. Technically you can tell the monkey rebase your branch on top of any commit hash git rebase a48fd9e
, or another branch git rebase release-4.2.0
.
Think of git rebase like your own little monkey that checks out a branch new from
origin/master
and re-runs all of your branch's commits one by one.
Cleaner commit histories with git rebase -i (--interactive)
What is interactive rebasing?
Interactive rebasing, git rebase -i
is useful when you want to modify the git history of your branch by e.g. editing, dropping, combining or changing the code content of commits. In turn this means that git rebase can be used to achieve a combination of the two.
What are some scenarios where you should consider modifying the git history? In order to make your code reviewer fall in love with you you need to write clear changelists. I would argue that you should always run git rebase -i
before making a pull request, improving commit messages, and making the list of changes easier to understand.
Programmable monkeys
Ross could never fully control Marcel, but you can control your rebase monkey
Remember that a git rebase can be understood as your own little monkey replaying the commit history, applying each commit one by one from a given starting point. In an interactive rebase this monkey becomes programmable and you get to take control of how the rebase is done enabling you to for example:
- Reorder commits
- Rename commits
- Change commit contents
- Squash together commits into one
- Run external commands in the middle of the rebase process
Say that you have worked on a timezone handling feature in your Python application and you're ready to push you branch and make a pull request. While you've been busy writing the new timezone feature, several other pull requests have been merged to master and you might have potential conflicts. By using git rebase
onto the latest master before we push and pake the Pull Request we ensure that the branch is conflict-free against master. However, we aren't entirely happy with the commit history for this branch. We've gone back and forth by installing libraries, trying solutions, reverting these changes into new commits and ended up with a jumbled history that's hard to follow.
git fetch --all # Fetch latest git state
git rebase -i origin/master
Yields the following vim prompt (assuming that your default editor is vim)
pick cec3c88ab Install python timezone library
pick fb98b91a1 Implement timezone logic with library
pick 9a9b2cfa6 Use python built-in lib for timezone logic instead
pick ea1728df1 Uninstall python timezone library
# Rebase df72dae9a..cec3c88ab onto df72dae9a (1 command)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
...
and more...
In this prompt you will be able to modify the git history to your liking and execute all the changes, ending up with a clean new commit history on top of the latest master. This article aims to walk you through some interactive rebase commands and by the end of this article you will be equipped with the ability to make clean Pull Requests with an easy-to-follow commit history.
In this example I want to squash all the 4 commits into one single commit. That is simply done by editing the prompt to squash all the commits.
pick cec3c88ab Install python timezone library
squash fb98b91a1 Implement timezone logic with library
squash 9a9b2cfa6 Use python built-in lib for timezone logic instead
squash ea1728df1 Uninstall python timezone library
# Rebase df72dae9a..cec3c88ab onto df72dae9a (1 command)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
...
and more...
When running the rebase I'm then prompted to change the commit message.
Let's go through some of the common interactive rebase commands.
Do nothing - pick
The easiest command to start with is pick. Pick simply uses the commit without changing anhything. When you initiate an interactive rebase you'll be presented with all your commits in order prefixed with a pick command. Executing this rebase will pick each commit in order and place them on top of the target branch of the rebase. Prefixing all commits with pick
without reordering is equivalent to running git rebase
without the interactive flag -i
.
Pick can also be used for reordering commits by simply changing the line orders. Be cautious however. If you reorder commits that change the same blocks of code or depend on one another in some way, you're going to have to resolve a conflict during the rebase.
Rewording Commits - reword
By prefixing your commits with reword
you will get a prompt to reword your commit is applied.
Combining Commits - squash, fixup
The squash
command will meld the selected commit into the previous commit. It will also prompt you for a new message for the combined commit. If you simply want to use the previous commit message, you can use fixup
instead.
Removing Commits - drop
Use drop
when you want to remove a commit entirely. Be careful with this one, if the subsequent commits rely on, or change code that is touched by a commit you've dropped you're going to get conflicts to resolve during the rebase.
Executing Commands before a commit - exec
Need to execute some cli command in the middle of rebasing, add exec
. To ensure this, simply add an exec with your test command before each commit. However, adding the same exec between every commit is not very DRY. In this case, just pass the --exec
flag to your rebase like so git rebase -i --exec "npm run test"
. When your test suite passes and returns exit code 0 the rebase will automatically continue. If your test suite returns exit code 1, the rebase will paus and prompt you to make any desired changes.
Closing Words
This post only scratches the surface and there's plenty more to understand about the git rebase command but these commands will cover 90% of use cases for git rebase.
Posted on January 11, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.