DEV Community

Cover image for Squashing commits in Git!
Yeganathan S
Yeganathan S

Posted on • Edited on

Squashing commits in Git!

Hello Everyone! back with another topic, Have ever felt that your repository in Github has a lot of commits history in which few don't make sense? for example, fixing typos in the readme in my case. If you're a Beginner then you may think of creating a new repo and push all those pretty useless commits in a single commit message "First commit". Well, then you're in the right place. Actually, there is a way you can squash multiple commits into a single one.

Basically, We squash commits to make our git log more concise.

Prerequisites:

  • Git should be installed.
  • Knowledge of Terminal and Git commands.

⚠️ Disclaimer ⚠️
Before experimenting with these, always have a backup of your files!

All being set let's dive into our topic! For easy understanding, I broke this into pieces and tried explaining in a noob way.

Getting Started 🥳

This is the commits history of my example repo.

Alt Text

So in the above example, I don't want the 5 commits history which is in-between the last and first since it doesn't make sense. let's make those commits history into a single commit!

Shoot up the terminal and go to your repo folder directory

$ cd <repo-folder>
Enter fullscreen mode Exit fullscreen mode

Make sure your repo folder is updated with all those commits you made and Write the following command...

$ git rebase -i HEAD~<n>
Enter fullscreen mode Exit fullscreen mode

In this case we are gonna give n as 6, where n is the number of commits history we need and these commits are from top-bottom. if we give n including the first commit we made, probably it shoots an error.

Experimenting 🧪

Executing the previous step will bring up the below interface with commit history and options to perform the following:

pick d8c1963 Adding contents
pick 5e4ea01 added
pick a3e1b06 removed few content
pick f9d4b67 Added few contents
pick 9e69811 Fixed typos and added content
pick 42dbace final

# Rebase 5f5302b..42dbace onto f9d4b67 (6 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.
Enter fullscreen mode Exit fullscreen mode

Squashing Begins❗️

Now, press S and it goes into editing mode. Replace pick as squash, for the commits you need to squash.

pick d8c1963 Adding contents
squash 5e4ea01 added
squash a3e1b06 removed few content
squash f9d4b67 Added few contents
squash 9e69811 Fixed typos and added content
pick 42dbace final

# Rebase 5f5302b..42dbace onto f9d4b67 (6 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.
#
-- INSERT --
Enter fullscreen mode Exit fullscreen mode

basically, all these 4 commits history is gonna be squashed into the previous one with a commit message which we are going to give in the next step. Always don't include squash in the first commit(Well! in this case, "d8c1963"), probably it shoots an error.

Now save the changes made by pressing Esc + : + w + q and after saving press shift + z two times, which brings you to an interface like this...

# This is a combination of 5 commits.
# This is the 1st commit message:

Adding contents
# This is the commit message #2:

added
# This is the commit message #3:

removed few content
# This is the commit message #4:

Added few contents
# This is the commit message #5:

Fixed typos and added content

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Fri Dec 4 12:14:48 2020 +0530
#
# interactive rebase in progress; onto 5f5302b
"~/Dev-test-repo/.git/COMMIT_EDITMSG" 33L, 839C
Enter fullscreen mode Exit fullscreen mode

here what it does is

Processing 🔄

Now it's our chance to delete the commit messages and create a new commit message

final test

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Fri Dec 4 12:14:48 2020 +0530
#
# interactive rebase in progress; onto 5f5302b
# Last commands done (5 commands done):
#    squash f9d4b67 Added few contents
#    squash 9e69811 Fixed typos and added content
# Next command to do (1 remaining command):
#    pick 42dbace final
# You are currently rebasing branch 'main' on '5f5302b'.
#
# Changes to be committed:
#       modified:   README.md
Enter fullscreen mode Exit fullscreen mode

Again, press Esc + : + w + q and after saving press shift + z two times. Now you will be thrown out that environment.

Takeoff ✈️

All these efforts should be reflected in our repo right!? So, let's push all those to our example repo in Github...

$ git push -f
Enter fullscreen mode Exit fullscreen mode

here, we use -f since we're force pushing it.

Finally 🎉

We squashed multiple commits to a single one!

Alt Text

For Beginners, remember deleting a commit is different from squashing commits.

Hope this article helps you!, Do like and share it if you find it useful. Post your valuable suggestions in the comments below!

Top comments (5)

Collapse
 
recursivefaults profile image
Ryan Latta

I'll give my hot take here.

You almost never want to do a forced push. The reason is that when you do, you've informed git that it's version of history is incorrect and you wish to change it. While that is fine if you work alone, if you work with others you'll be re-writing their history as well.

Under the covers git works a little bit like a linked list. One commit points to the next and so on. A forced push rewrites that list which will like mean everyone else's work no longer exists in that list.

This brings me back to the rebase command. Rebase is the command that rewrites git's history. It isn't so much that it changes commits, it fundamentally changes how they exist. An accident rebasing can be a tricky thing to recover from (Hint: Look at ref-log). The general rule is that you rebase only things you've not pushed or shared. That way you are only manipulating your history and not someone else's.

Collapse
 
yeganathan profile image
Yeganathan S • Edited

First of all thanks for sharing your thoughts!
I would like to add a few tops of it. When we squash commits, our local version would probably be changed so git won't allow us to do push those since both remote and local are different versions. Only force push works in this case, also when we work with others only if the person pulls, the changes come to his local, and if he force pushes directly then his changes would be reflected as before squashing.

Collapse
 
recursivefaults profile image
Ryan Latta

You only need to force push when you have to overwrite a remote's history of commits. If you only rebase commits that you've not pushed, you'll find you never have to force push.

Give it a try. Make a bunch of commits without pushing them. Do your interactive rebase on ONLY those commits. You'll have no problem pushing because you'd be pushing 1 squash commit. The remote repo never knew or cared you have others before the rebase. When you rebased you rewrote that history to never exist. When you push it looks like you only ever had 1 commit.

For fun clone the repo somewhere else. and do it again only this time do a few commits, and push them one at a time. Somewhere in the middle of pushing pull to your 2nd repo. Add a few commits in that 2nd repo. Go back to your original and squash those commits and force push. Go to your 2nd repo and do a pull. See what happens.

Knowing how this works will be a pretty important thing. Someone you work with will mess up a repo at some point. Good to know how it looks and how to fix it. Its a super power.

Collapse
 
slavius profile image
Slavius

Isn't the advice to always have backups of your files a bit useless with Git? I mean you can always reset to any commit, if not local then remote. Even if you mess something up you can recover from local history or reflog even if you rewrite your history unless you try to recover from weeks old mistake or you intentionally garbage collected your local Git repo...

Collapse
 
yeganathan profile image
Yeganathan S • Edited

yeah! What I meant was, if we try to rewrite weeks old history and somehow messed up then recovering that would end up in tragedy.

Thanks for your comment!