Table of contents
It is quite possible in your early days with Git that when you are working on a feature branch on your specific task/idea you do run into a situation where you have a bunch of commits which effectively can be a single commit. This might be because as no one cares about your entire series of commits on a feature branch. On a serious note it makes the commit history cleaner and more manageable.
Squashing basically allows to combine a bunch of commits together. This comes in handy when all the commits in question are related to each other and you’d really rather just have all of those related commits under one roof. In addition, this also helps in having a a cleaner commit history before pushing to the main branch.
Visual Idea
Take a look at the above gitflow diagram. It has a main branch with two commits prior to creating a feature branch. Since not all bug fixes are trivial and can be solved within the scope of a single commit, it is pretty common to have multiple commits to complete a bug fix.
Now there are two ways to get code onto the main branch - Git Merge & Git Rebase. However with either of these approaches the feature branch commits would be preserved. This means when you get the feature branch code to the main branch there would now be a total of 5 commits (3 commits from feature branch). Those 3 commits are kinda unnecessary and we would rather have a single additional commit on the main branch that gets all the feature branch commits on to main. Enters Squashing! As seen in the below gitflow image above, the 3 commits are squashed into a single commit - UI bug Fix Complete. When merged or rebased to the main there would just be a single additional commit.
In Action: Practical Scenario
I have two branches in this repository and I'm currently checked out to my feature branch
monk@345463fgd Webservice % git branch
* feature
mainline
Lets take a look at the commit history of this branch. This can be done via the command git log -3 which returns the last three commit activites on this branch. It seems I have three commits. I want the changes made on feature branch moved to the mainline but I don't wan't all these three commits instead I would like a single commit with the latest changes.
monk@345463fgd Webservice % git log -3
commit 6d2b36f0669ae2f9b816a57581f38bd685b1c5fd (HEAD -> feature)
Author: Monk <monk@demo.com>
Date: Fri Feb 17 22:58:34 2023 -0800
UI bug fix complete
commit a7757371ff95d47c04f1ebd1a1b767c49d32fda8
Author: Monk <monk@demo.com>
Date: Fri Feb 17 22:58:05 2023 -0800
Unit tests added
commit 67ec28a477850ae4b11a5ea48b685e00dae2b6a7
Author: Monk <monk@demo.com>
Date: Fri Feb 17 22:57:11 2023 -0800
Screen Rendering Fix
Now enter the git rebase -i HEAD~3. This runs the rebase command in an interactive mode and also lets Git know I want to operate on the last three commits back from HEAD.
git rebase -i HEAD~3
This will open up your default terminal text editor (most likely vim) and present you with a list of commits that you could play with
pick 6d2b36f UI bug fix complete
pick a775737 Unit tests added
pick 67ec28a Screen Rendering Fix
# Rebase 8ec9e51..6d2b36f onto 8ec9e51 (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 [-C | -c] <commit> = like "squash" but keep only the previous
In my case, I would like to use the UI bug fix complete so I enter the "p" command next to it which denotes that I would like to pick this commit. As you might have guessed I have squash "s" command next to the other two commits. Now save the changes and exit (Esc key followed by : wq)
p 6d2b36f UI bug fix complete
s 67ec28a Screen Rendering Fix
s a775737 Unit tests added
Git will pop open another file where you can find the summary of actions you had performed. This file can be saved and exited.
UI bug fix complete
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Fri Feb 17 23:07:37 2023 -0800
#
# interactive rebase in progress; onto a2868f7
# Last command done (1 command done):
# pick 6d2b36f UI bug fix complete
# Next commands to do (2 remaining commands):
# squash 67ec28a Screen Rendering Fix
# squash a775737 UI bug fix complete
# You are currently editing a commit while rebasing branch 'feature' on 'a2868f7'.
#
# Changes to be committed:
# modified: Config
Git opens another file where it provides the option to rename the commit message of the new commit and comment out the parts that you would rather not have part of the commit message.
# This is a combination of 3 commits.
# This is the 1st commit message:
UI bug fix complete
# This is the commit message #2:
# Unit tests added
# This is the commit message #3:
# Screen Rendering Fix
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Fri Feb 17 23:07:37 2023 -0800
Now on saving the above file in the state I have left wherein I want the new commit message to be same as my last commit message (UI bug fix complete) and I have commented out the other two commits will result in a single commit.
Finally the moment of truth. Let's run the git log -3 command to see the last three commit activities on my feature branch. It just returns a new single commit which denotes that all the other commits have been squashed!
monk@345463fgd Webservice % git log -3
commit afd5ca00d7c21d1ddf552fde14e948d7c116ba7b (HEAD -> feature)
Author: Monk <monk@demo.com>
Date: Fri Feb 17 23:07:37 2023 -0800
UI bug fix complete
There we go! Now this can be merged or rebased back to mainline
Common Issue people run into: https://stackoverflow.com/questions/39595034/git-cannot-squash-without-a-previous-commit-error-while-rebase#:~:text=This%20is%20the%20commit%20group%20I%20wanted%20to%20squash%3A