How to squash all commits in a branch?

You have been working for a while on a feature branch, but before merging it to main you want to squash all commits into a single one. How do you do that?

Let’s suppose you are on a branch b1 off the main branch and you have 5 commits on that feature branch:

alina

11aeb4d241f8a13bd401994e4c6fd5d5fb0c6bb564db2758db1mainHEAD

The commits have modified the file file.txt:

alina

alina git:( b1 ) cat file.txt

Wed Jun 14 07:35:37 PDT 2023

1

2

3

4

5

alina git:( b1 )

You have multiple ways to squash those commits, for instance, by using a git rebase: git rebase -i HEAD~5

With an interactive git rebase, you need to know the number of commits you want to squash, which may not be convenient if you have a lot of commits.

Another solution is to perform a git reset --soft to the first commit of the branch, and then commit again.

The question then becomes: how do you find the first commit of the branch?

The answer is to use the git merge-base command:

alina

alina git:( b1 ) git merge-base main HEAD

c6bb5647a2db709e1d5dde845c1d35a60217f348

alina git:( b1 )

This command returns the hash of the first commit of the branch. However, you still need to remember that the branch was created from the main branch.

The git reset --soft will reset the branch to the given commit but will keep the changes in the working directory and the staging area.

alina

alina git:( b1 ) git reset --soft $(git merge-base main HEAD)

alina git:( b1 )

The git status command will show you all the changes that are already staged for commit:

alina

alina git:( b1 ) git status

On branch b1

Changes to be committed:

  (use "git restore --staged <file>..." to unstage)

        modified:   file.txt

alina git:( b1 )

Your working directory contains all the changes that were in the 5 commits:

alina

alina git:( b1 ) cat file.txt

Wed Jun 14 07:35:37 PDT 2023

1

2

3

4

5

alina git:( b1 )

If you compare the staging area with the HEAD branch, you’ll see that the changes of those 5 commits are already staged:

alina

alina git:( b1 ) git diff --staged

diff --git a/file.txt b/file.txt

index b1c2a1b..39fc041 100644

--- a/file.txt

+++ b/file.txt

@@ -1 +1,6 @@

 Wed Jun 14 07:35:37 PDT 2023

+1

+2

+3

+4

+5

alina git:( b1 )

You can now commit again, and you will have a single commit on your branch:

alina

alina git:( b1 ) git commit -m 'commits squased

[b1 7ca520c] commits squashed

 1 file changed, 5 insertions(+)

alina git:( b1 )

And your commit tree will look like this:

alina

7ca520c11aeb4d241f8a13bd401994e4c6fd5d5fb0c6bb564db2758db1mainHEAD

The commit tree above still shows the 5 commits, but they are not part of the branch anymore. They are still in the git repository, but they are not reachable from any branch and they will be garbage collected at some point.