As I’ve promised in my previous post https://medium.com/@kpunith8/git-cli-tips-and-tricks-to-be-more-productive-8e01c77fd83 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 the 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 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
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 https://medium.com/@kpunith8/git-cli-tips-and-tricks-to-be-more-productive-8e01c77fd83 for more info, it would look like
$(master) git cm && git f && git pom
Using short-circuiting (&& between multiple commands) works with
MacOS
and inLinux
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
the 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 the 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 ofB
once you squashedC
,D
andB
into one commit, that is why I represented it asB'
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 the 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.