DEV Community

Cover image for How to use git rebase effectively
Punith K
Punith K

Posted on

How to use git rebase effectively

As I've promised in my previous post git tricks that
I'm going to explain to you the intermediate/advanced concepts that most of the developers are not using it frequently or not aware of.

I'm going to talk about the rebase command in this post which is used relatively less by developers.

Let's get started,

Rebasing

Technically, rebasing helps to combine or move the commits on top of the base branch, so that the history of the current branch is up to date with the base branch (master branch - most of the time).

Rebasing can be helpful to squash the commits into one commit and can also be used to split into multiple commits if you want the clean history of each fix that you want to track.

Let me explain you the basics of rebasing with the help of the below diagram

    B---C---D fix/align-div-vertically
   /
--A---E---F master

Let's assume that the letters (A-F) indicate the commit hashes. The branch fix/align-div-vertically was created on top of commit hash A from the master branch and you have continued working on it, adding commits with the hashes B, C, and D for a couple of days (let's assume you commit once a day). Now you have fixed the issue and want to merge the changes to master, creating a pull request (PR) as a single commit; it can be achieved with the help of squashing which I'm going to explain in the next section.

Meanwhile, the master branch has been updated by your fellow developers with the commits E and F as shown in the diagram, before you want to create a PR to the master branch you need to have the all the commits of your branch to be on top of master's latest commit, F, to have the all the commits of the master on your branch we need to make use of rebase git command.

After the rebase, the commit-tree should look like this,

            B---C---D fix/align-div-vertically
           /
--A---E---F master

Before starting the rebasing you must checkout to master and pull the changes from the remote so that your local master updated with the remote repository.

$(fix/align-div-vertically) git checkout master 

$(master) git fetch -p 

$(master) git pull origin master

Branch point is enclosed within the parenthesis, for eg, $(master) indicates that we are currently in the master branch

The above command can be executed with less typing the help of aliases, check out my previous blog git tricks for more info, it would look like

$(master) git cm && git f && git pom

Using short-circuiting (&& between multiple commands) works with MacOS and in Linux distribution, if you are on windows make sure you execute each command separately.

For the remainder of this article I'll use full commands to avoid confusion with aliases and to deviate from what I want to explain here.

Once you updated your local master branch with remote, checkout to fix/align-div-vertically and execute the following command

$(master) git checkout fix/align-div-vertically

$(fix/align-div-vertically) git rebase master

The above command will look up the commit history of the current branch and rebases with the master commits so that the current branch is on top of the master's last commit. While rebasing you may get merge conflicts if any of the files changed by you have been updated by other developers and git was not able to merge them automatically, you end up with merge conflicts you may need to resolve manually or with the help of kdiff3 a visual tool to resolve conflicts.

Rebase applies each commit of your current branch B, C, and D one at a time, while merging you may encounter git stopping at each commit, at most 3 times in this case because we have 3 commits in fix/align-div-vertically branch; to resolve merge conflicts if there are any.

Git CLI is intelligent enough to resolve most of the conflicts for you, there were the times when git was not intelligent enough to resolve all the merge conflicts, during my initial days I had to manually resolve most of the merge conflicts for more files than with the latest Git CLI, we have a more robust CLI now to deal with most of the merge conflicts.

If it stops at each commit you need to resolve merge conflicts and execute the below command to continue the rebasing(at most 3 times or maybe only once if you don't have any merge conflicts)

$(fix/align-div-vertically) git rebase --continue

After successful rebasing, if you check git log, history would look like this,

$(fix/align-div-vertically) git log --oneline -10
# commit history should look like this
D Commit message-d
C Commit message-c
B Commit message-b
F Commit message-f
E Commit message-e
A Commit message-a

You all know how the commit hash looks, it shows the first 7 chars in hexadecimal format, for eg, 62ef387

If you think you can't resolve all the merge conflicts or confused and want to do it later, you can abort the whole rebase process with the help of

$(fix/align-div-vertically) git rebase --abort

Squash commits into a single commit using interactive rebase

If you don't want rebase to stop at each commit if you get any merge conflicts and want it to stop only once during the rebase process, you can squash all the commits into one in your fix/align-div-vertically branch by squashing them using interactive rebase before rebasing with the master.

Interactive rebase can be done as follows, with option --interactive or -i in short,

$(fix/align-div-vertically) git rebase -i A

Here commit hash A is considered because we want to squash all the commits in the current branch to a single commit considering commit hash A as a base so that we are keeping the B commit as the base which is the first commit on top of master's commit hash A

It opens another window, by default it opens a vi/vim editor, you must be aware of switching modes(ESC) and how to insert(i) the text and save the content and quit(:wq), you can set your favorite visual text editor as a default one with the help of git config --global core.editor (please refer the git documentation) if you are not a big fan of vi/vim editor.

The content of the interactive rebase would look like this, Git CLI version used is 2.26.2 at the time of writing this post

pick B Commit message-b
pick C Commit message-c
pick D Commit message-d

# Rebase A..D onto A (3 commands)
#
# 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
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

If you check the order of commits listed in the vi editor, it listed in the order you committed, B, C, and D in the current branch but it doesn't list commit hash A because that is the base to squash into one.

Content in the editor gives a guide to what each command you can use and what they do, pick before each commit hash is added by git by default, you need to choose from available options, reword, squash, fixup and so on, see the commented section in the editor.

In this case, we need to use squash or s in short; the commits C and D by picking B as base commit, after changing the pick to squash for commits C and D it should look like this,

pick B Commit message-b
squash C Commit message-c
squash D Commit message-d

# And the remainder of the content, don't change anything, leave as it is.

Once done, save and quit the editor, git may stop at each commit if there are any possible merge conflicts (its very rare to get merge conflicts, if you don't merge with master every day using merge. There are drawbacks using git merge, I'll talk about it in another post if you want it). Make sure you use rebase instead of merge to have a clean history on your branch.

You know what should be done if rebase stops and asks you to resolve conflicts

$(fix/align-div-vertically) git rebase --continue

Once rebase is successful, it opens another window asking for a new commit message for the squashed commit, you can update or leave the commit message as is, once done, you will have a single commit with commit hash, let's say B', not B.

Be aware that the git rebase will re-write the commit history once it is successful, you won't be able to see the same commit hash as that of B once you squashed C, D and B into one commit, that is why I represented it as B' in the below diagram

If you want to keep the commit message for B' like that of B's commit message for a squashed commit, replace squash with fixup or f in short, it would look like this, once rebase is successful, it will not open another window asking for a commit message.

pick B Commit message-b
f C Commit message-c
f D Commit message-d

# And the remainder of the content, don't change anything, leave as it is.

Now the commit tree will look like this, still, you haven't rebased with the latest master commit yet to see on top of the latest commit hash F

    B' fix/align-div-vertically
   /
--A---E---F master

Once successful, you can go ahead and rebase with the master so that you are top of the master's latest commit hash, F.

$(fix/align-div-vertically) git rebase master

Make sure you have switched to master branch and pulled the latest changes from remote to local master branch before rebasing with the master, I've mentioned this already in the top.

Now during the rebase with the master, it stops only once to resolve merge conflicts if there are any because we have a single commit in our branch fix/align-div-vertically.

Please note that we can also pass the master branch to interactive rebase as a base while squashing commits, but it needs a bit more careful thoughts or actions that you should be aware of. Don't pull the changes for the master branch before you want to squash, so that your local master's commit hash still points to A, after pulling the remote changes your local master will have commit hash F.

$(fix/align-div-vertically) git rebase -i master

Now you can pass master as param instead of commit hash A while doing the interactive rebase.

Once done, you go back to master, pull the changes from remote, and rebase with your branch.

There is a way to split a single commit into multiple commits using interactive rebase which is not the most wanted feature for any developers in my opinion and I never encountered a situation where I needed to use that feature at least once in 6 years of experience but I did use squshing over thousands of times.

As I've mentioned, git rebase will re-write the complete history and you can't go back through history to see/check what you have coded or committed part of each commit, B, C, or D, don't worry git provides another powerful tool reflog to get back the history or reset the history in your local branch as it was before squashing multiple commits into one. Please note that it is only possible if you have the locally copy un-altered meaning not deleted, once you have merged your changes to master creating a PR. You don't have to worry if you have deleted the branch in GitHub UI but you have the local copy and still have the local branch in your file system undeleted.

I'll explain how you can do that in reflog in the next post. If you want it in less explained or in the raw form you can access the content in my github-repo but it is the hand note I use every day and doesn't have the explanation that I'm writing in the blogs and haven't given much attention to typos and grammar. It can be a reference for you as well, star it if you find it useful and it has other useful content too, please share it with your friends.

This is getting bigger already and has explained so many things about rebase, please give a try, if you have not tried it before. This will really save a lot of time.

That's it for now. Please leave a comment and share it with your friends if you find it useful. Please let me know if you want me to cover any topic related to git or front/back end related stuff.

Happy Coding and keep smiling.

Top comments (0)