W hen a programmer is first learning
git the typical commands that are picked up include
merge. After those foundational commands are learned, I think that the
rebase command should be understood.
Rebasing is often used as an alternative to merging. Rebasing a branch updates one branch with another by applying the commits of one branch on top of the commits of another branch. For example, if working on a
feature branch that is out of date with a
dev branch, rebasing the
feature branch onto
dev will allow all the new commits from
dev to be included in
feature. Here’s what this looks like visually:
For the above example, this is what it would look like from the command line:
git rebase feature dev
However, it is more common to first checkout a branch and then run the rebase command with the name of the branch you wish to rebase on to:
git checkout feature
git rebase dev
Typical Rebase Use Cases
Updating a Feature Branch
Lets say you’re working away on a
feature branch, minding your own business.
Then you notice some new commits on
dev that you’d like to have in your feature branch, since the new commits may affect how you implement the feature.
You decide to run
git rebase dev from your
feature branch to get up-to-date with
However when you run the rebase command, there are some conflicts between the changes you made on
feature and the new commits on
dev. Thankfully, the rebase process goes through each commit one at a time and so as soon as it notices a conflict on a commit, git will provide a message in the terminal outlining what files need to be resolved. Once you’ve resolved the conflict, you
git add your changes to the commit and run
git rebase --continue to continue the rebase process. If there are no more conflicts, you will have successfully rebased your
feature branch onto
Now you can continue working on your feature with the latest commits from
dev included in
feature and all is well again in the world. This process can repeat itself if the
dev branch is updated with additional commits.
Updating a Feature Branch Prior to Merge
Another popular use for rebasing is to rebase a
feature branch, just prior to merging into a
dev branch. Let’s say that the current state of a
dev branch is as follows:
feature is complete, it should be merged into the
dev branch. First, the
feature branch should be updated with
git rebase dev.
dev. This is usually done through a Pull Request, so others can review the work done in the
feature branch. Once this is done, the result is as follows:
Notice how all the commits from the
feature branch are added to the end of the
dev branch. This will always be the case if rebasing is immediately done prior to merging. The reason this happens is that rebasing rewrites the git history and so the timestamp of the commits on the rebased branch will all be the moment at which the rebase command was run. To be clear, rewriting history with rebase recreates commits that contain the same changes and commit message, but with a new hash and timestamp. Therefore, when merging a recently rebased
feature branch into
dev, all the
feature branch commits will be added at the end of
dev since merging orders commits in chronological order.
Github actually has an option on Pull Requests to rebase a branch prior to merging.
Renaming & Consolidating Commits in Feature Branches
Odds are, when nearing the completion of a feature in a
feature branch, the commit history could be improved upon by renaming some commits to something more appropriate, combining closely related commits, and/or reordering commits in a more logical order. Rebasing allows for all of this!
Since rebasing rewrites the git history, it provides some tools to specify exactly how to rewrite the history. The best way to take advantage of these extra rebase features is by running rebase in interactive mode by passing the
-i flag with the command (i.e.
git rebase -i feature dev).
When running an interactive rebase, a list of commits that will be rebased along with the word
pick at the beginning of each commit message is presented in the default git editor configured on your machine (in my case it’s Visual Studio Code). The word
pick can be changed with any of the following options (all of which are listed when running an interactive rebase):
pick = use commit
reword = use commit, but edit the commit message
edit = use commit, but stop for amending
squash = use commit, but meld into previous commit
fixup = like "squash", but discard this commit's log message
exec = run command (the rest of the line) using shell
drop = remove commit
For the examples mentioned earlier here’s how you would tackle them:
- A commit message can be reworded using the
rewordoption for a commit.
- Combining commits is done with the
fixupoption on a commit that should be combined with another commit. Ensure that the commit specified with
fixupimmediately follows the commit with which it should be combined.
- Reordering commits is as simple as moving around the commit messages in the desired order.
What’s So Great About Rebasing?
No Merge Commits
Merging two branches together always requires a merge commit. If a feature branch is continually being updated with a dev branch using a merge, there will be several merge commits resulting in a less clear git history.
These merge commits can be completely mitigated if rebase is used to update the
feature branch instead of a merge.
Linear & Sequential Git History
When rebasing a branch prior to a merge, all the the
feature branch commits are grouped together at the end of the
dev branch whereas the
feature branch commits are interspersed in the
dev branch when a rebase is not used prior to merge. This is because merging orders commits chronologically. Grouping feature commits together, as is the case with a rebase, makes it simple to understand/analyze the git history afterwards.
Simpler Time Resolving Conflicts
When rebasing, git applies each commit one at a time and so conflicts can be resolved progressively. When resolving conflicts for a merge, all conflicts must be resolved at once which can make it a bit more difficult to handle.
If a conflict is encountered while rebasing, git will indicate which files are conflicting and need to be modified. After changes have been made, the changes need to be staged to the commit and then the rebase can resume using
git rebase --continue. There is also the option of running
git rebase --abort while resolving conflicts in a rebase, which will cancel the rebase and leave the branch unchanged.
Well Organized and Clean Git History
With all the benefits above in addition to the ability to change commit messages, combine commits, and reorder commits, rebasing makes for a very clean git history that is easy to understand. Having an understandable git history is great when trying to pinpoint when a certain feature or bug was introduced in your codebase.
When Not To Rebase
Ok, rebasing is pretty sweet, but there are certainly cases where you should not rebase. The golden rule of rebasing is to not rebase public branches. The reason you shouldn’t rebase a public branch is that you are rewriting the git history for that branch and this will result in your version of the public branch to differ from the version other users are working with. This will result in a bunch of conflicts and can be a tedious problem to resolve once it’s done, so make sure to only rebase branches you, and only you, are working on.
git rebase to your git workflow can be an extremely valuable way of cleaning up your git history and making the overall git workflow easier to work with. The way
rebase works may not be completely intuitive at first, but after using the command a few times on feature branches to better familiarize yourself with it, you’ll hopefully end up seeing some of the benefits mentioned in this article.