Git Rebase for Beginners

·

8 min read

Image for post

In this post, let us learn one of the git intermediate concepts that most developers aren't using right or misunderstood for git merge. I’m talking about the git rebase command.

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

Rebasing can also be helpful to squash multiple commits into one commit without messing up the history in your branch and can also be used to split a single commit into multiple commits too.

Let's dive into understanding the basics of rebasing with the help of the below diagram

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

Assume that the letters (A-F) indicate the commits. The branch fix/align-div-vertically was created on top of the commit A from the master and you have continued working on it, adding commits B, C, and D over the course of time (let's assume that you commit once a day, and is a good practice to do so). Now you have fixed the issue and want to merge the changes to the master, creating a pull request (PR) as a single commit or keeping all the commits on top of the master branch; and the same can be achieved with the help of option squashing which we will see next.

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 all the commits of your branch to be on top of the master's latest commit, F, to have the all the commits of the master on your branch we need to make use of rebase 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 check out to master and pull the latest changes from the remote so that your local master has all the commits to rebase.

$(master) git fetch -p 

$(master) git pull origin master

Branch name 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 with the help of aliases, check out my previous post git tips

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

$(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 so that the current branch is on top of the master’s latest commit.

Rebase applies each commit of your current branch B, C, and D one at a time, while rebasing you may encounter git stopping at each commit (could be because of merge conflicts, otherwise it won't stop in between), at most 3 times in this case because we have 3 commits in fix/align-div-vertically branch; resolve merge conflicts if any, after the conflicts are resolved, make sure to add the changes to staging using git add . and continue the rebase from the point where it stopped, using,

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

If git rebase stops at each commit asking you to resolve conflicts, then you need to execute the above command three times.

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

$(fix/align-div-vertically) git log --oneline -10 
D message-d 
C message-c 
B message-b 
F message-f 
E message-e 
A message-a

You can also abort the rebasing process if you are stuck on resolving conflicts at any commit.

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

Note that it keeps all the commits in your branch as is.

In the next section, let's discuss how to squash multiple commits into one commit so that we can merge our work as a single commit to the master.

Squash commits into a single commit using interactive rebase

If you don’t want to flood the master branch with so many commits and want a single commit to be merged in to master, you can squash all the commits into one in your fix/align-div-vertically branch using interactive rebase before rebasing with the master.

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

$(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 commit or you can also specify the branch name too (make sure to pull the latest changes to master before applying this on top of master branch)

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

It opens another window and the content of the interactive rebase would look like this

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 editor, it listed in the order you committed, B, C, and D in the current branch but it doesn't list the commit hash A because that is the base commit on which we are applying this change.

Content in the editor gives a guide to what each command you can use and what they do, pick option before each commit has been 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 leave the remainder of the content as is.

Once done, save and quit the editor, git may stop at each commit if there are any merge conflicts, resolve conflicts, add them to staging and continue the rebase

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 after squashing git assigns a new commit hash to the commit.

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 it as is.

Now the commit tree will look like this, still, you haven’t rebased with the 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 on top of the master’s latest commit hash, F.

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

Make sure you have switched to the master branch and pulled the latest changes from the remote to the local master branch before rebasing with the master.

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

As I’ve mentioned, git rebase will re-write the history and you can’t go back through history to see what you have coded or committed part of each commit, B, C, or D. But still, you can get those commits back, using yet another powerful git command called reflog, reflog can be used to get back the history or reset the history in your local branch as it was before squashing multiple commits into one, please leave a comment if you want me to write an article about the same.

Please note that it is only possible if you have the local copy not deleted.

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

Please share it with your friends if you find it useful.