Skip to content

Squash

Overview

The squash command allows you to combine multiple contiguous commits in a single one.

You may ask: why should I do many commits if at the end I merge them all?

As a tool, Git supports multiple use cases. Depending on how you use it, and where, we can highlight at least this two:

  1. Within a shared repository, it's a way to exposes the evolution of code to other developers.
  2. Within a private repository, such as your local clone, you can draft your work with more confidence by following the "commit small, commit often" principle.

A publicly shared change log should be clean and reflect the expected result of each development task.

On the other side, in your private repository, you are free to commit whatever you want, whenever you need.

You are not required to wait to complete your task to commit changes.

But when times come to deliver, you might not want to show many small commits when a single one is enough.

That's where you will use squash to merge multiple commits in a single one.

  • You cleanup your mess before delivery.
  • People who will read your code later can have a clear view on the final result and not dive into your development process.

Setup playground

From the course repository root, run the following commands:

bash
./scripts/create-playground.sh rebase-int_squash

Check that you have a proper history in this playground repository.

bash
cd playgrounds/rebase-int_squash ; git log --oneline
9551b8a (HEAD -> main) fix bug
a20f417 add debug to enqueue/dequeue
03ff782 implement size
014299b implement dequeue
6424380 implement enqueue
8367e5c add constructor and inner collection
687db8b class skeleton
f5f44f3 add test for queue size
0cc0411 add test for dequeue on empty queue
0719b96 add more tests for `dequeue()`
d1a3e0e api use example for `dequeue()`
a71d052 api use example for `enqueue()`
1399621 create file with draft specifications

Activity goal

In this exercise we will simplify the history to something like the following:

  • fix: bad computation
  • debug: added logs to follow queue content
  • feat: implementation of the queue logic
  • test: definition of behavior through test scenarios

Enabler

We are able to use squash because all commits related to implementation, and those for test writing, are next to each other.

This was made possible thanks to the reorder activity.

Now that we have set a goal, let's get started.

From many to few

We need to launch an interactive rebase from the very first commit.

bash
git rebase --interactive --root

This will open a rebase command file in your editor and your shell is now blocked, waiting for you to close the editor.

The edited file should look like (I removed the commented lines):

git-rebase
pick 1399621 # create file with draft specifications
pick a71d052 # api use example for `enqueue()`
pick d1a3e0e # api use example for `dequeue()`
pick 0719b96 # add more tests for `dequeue()`
pick 0cc0411 # add test for dequeue on empty queue
pick f5f44f3 # add test for queue size
pick 687db8b # class skeleton
pick 8367e5c # add constructor and inner collection
pick 6424380 # implement enqueue
pick 014299b # implement dequeue
pick 03ff782 # implement size
pick a20f417 # add debug to enqueue/dequeue
pick 9551b8a # fix bug

WARNING

As playground rebuild a new repository each time you create one, the SHA1 in this course are only example. Only consider those in your local repository as reference.

Remember that commits are shown in chronological order, the oppositive of the git log command.

To squash a commit with the one above it, you need to replace the "pick" command by a "squash" one. In the example bellow I used the abbreviated "s" for this command:

git-rebase
pick 1399621 # create file with draft specifications
s a71d052 # api use example for `enqueue()`
s d1a3e0e # api use example for `dequeue()`
s 0719b96 # add more tests for `dequeue()`
s 0cc0411 # add test for dequeue on empty queue
s f5f44f3 # add test for queue size
pick 687db8b # class skeleton
s 8367e5c # add constructor and inner collection
s 6424380 # implement enqueue
s 014299b # implement dequeue
s 03ff782 # implement size
pick a20f417 # add debug to enqueue/dequeue
pick 9551b8a # fix bug

Save the file and close the editor.

A "COMMIT_EDITMSG" file will now open in your editor:

git-commit
# This is a combination of 6 commits.
# This is the 1st commit message:

create file with draft specifications

# This is the commit message #2:

api use example for `enqueue()`

# This is the commit message #3:

api use example for `dequeue()`

# This is the commit message #4:

add more tests for `dequeue()`

# This is the commit message #5:

add test for dequeue on empty queue

# This is the commit message #6:

add test for queue size

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sun Oct 26 22:28:47 2025 +0100
#
# interactive rebase in progress; onto d2e8e4d
# Last commands done (6 commands done):
#    squash 0cc0411 # add test for dequeue on empty queue
#    squash f5f44f3 # add test for queue size
# Next commands to do (7 remaining commands):
#    pick 687db8b # class skeleton
#    squash 8367e5c # add constructor and inner collection
# You are currently rebasing branch 'main' on 'd2e8e4d'.
#
#
# Initial commit
#
# Changes to be committed:
#	new file:   queue-example.ts
#

The squash command is, by default, preserving each commit message.

You now have the ability to edit the final result that contain all messages merged in a single one.

TIP

At that point in time you can open the source file and check the content. It will reflect the result of the merge, so you can double check what should go in your updated commit message.

Let simplify to something like:

git-commit
test: definition of behavior through test scenarios
Test for API use:
- `enqueue()`
- `dequeue()` with use case on empty queue
- check usage of `size()``

Save and close "COMMIT_EDITMSG"

  1. In your shell rebase will display some progress:
console
[detached HEAD b23f365] test: definition of behavior through test scenarios Test for API use: - `enqueue()` - `dequeue()` with use case on empty queue - check usage of `size()``
 Date: Sun Oct 26 22:28:47 2025 +0100
 1 file changed, 46 insertions(+)
 create mode 100644 queue-example.ts
hint: Waiting for your editor to close the file...
  1. A new "COMMIT_EDITMSG" is now opened for the second squash result commit.
git-commit
# This is a combination of 5 commits.
# This is the 1st commit message:

class skeleton

# This is the commit message #2:

add constructor and inner collection

# This is the commit message #3:

implement enqueue

# This is the commit message #4:

implement dequeue

# This is the commit message #5:

implement size

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sun Oct 26 22:34:08 2025 +0100
#
# interactive rebase in progress; onto d2e8e4d
# Last commands done (11 commands done):
#    squash 014299b # implement dequeue
#    squash 03ff782 # implement size
# Next commands to do (2 remaining commands):
#    pick a20f417 # add debug to enqueue/dequeue
#    pick 9551b8a # fix bug
# You are currently rebasing branch 'main' on 'd2e8e4d'.
#
# Changes to be committed:
#	modified:   queue-example.ts
#

Replace this second commit message with something like:

git-commit
feat: implementation of the queue logic
- add constructor and inner collection
- implement `enqueue()`
- implement `dequeue()`
- implement `size()`

The rebase will complete in your shell:

console
[detached HEAD b23f365] test: definition of behavior through test scenarios Test for API use: - `enqueue()` - `dequeue()` with use case on empty queue - check usage of `size()``
 Date: Sun Oct 26 22:28:47 2025 +0100
 1 file changed, 46 insertions(+)
 create mode 100644 queue-example.ts
[detached HEAD 1f4c958] feat: implementation of the queue logic - add constructor and inner collection - implement `enqueue()` - implement `dequeue()` - implement `size()`
 Date: Sun Oct 26 22:34:08 2025 +0100
 1 file changed, 21 insertions(+)
Successfully rebased and updated refs/heads/main.

You can check the result:

bash
git log
commit 454783363e4111a5afcc3e027f2d2ed2f1457efc (HEAD -> main)
Author: Sylvain Gamel <code@sylvaingamel.fr>
Date:   Sun Oct 26 22:45:22 2025 +0100

    fix bug

commit 7c9aef44ca8d71497076ab6a2f230fbb294bdfea
Author: Sylvain Gamel <code@sylvaingamel.fr>
Date:   Sun Oct 26 22:44:26 2025 +0100

    add debug to enqueue/dequeue

commit 1f4c958f3de7d615f7851b434c4d9661336a81da
Author: Sylvain Gamel <code@sylvaingamel.fr>
Date:   Sun Oct 26 22:34:08 2025 +0100

    feat: implementation of the queue logic
    - add constructor and inner collection
    - implement `enqueue()`
    - implement `dequeue()`
    - implement `size()`

commit b23f36510d3e0dbadda91b2de6fdef1c37743102
Author: Sylvain Gamel <code@sylvaingamel.fr>
Date:   Sun Oct 26 22:28:47 2025 +0100

    test: definition of behavior through test scenarios
    Test for API use:
    - `enqueue()`
    - `dequeue()` with use case on empty queue
    - check usage of `size()``

What we learned

Thanks to squash command, you can use interactive rebase to reduce the number of commits in your log. Something valuable before delivering your code for a review.

Key takeaway:

  • Squash will, by default, preserve each commit message and merge them in a single one.
  • As this command only merge a commit with the preceeding one, you probqbly will have to reorder commits in your log to use it.
  • You have the option to rewrite the text of the resulting commit message during the interactive rebase process, so you can reword each message.