Skip to content

Rebase Squash Example


What Will We Learn?

Practically Learn Commit Squashing Using Rebase Squash.
Don’t Dare to Miss How Commit Squashing Happens.

Commit Squashing

Commit squashing is the process of combining two or more Git commits into a single commit. It is commonly used to clean up a branch before merging it into the main branch, so that the Git history contains fewer and more meaningful commits.

Use Case of Commit Squashing

  • Keeping Git history clean — For example, 10 small commits made while developing a feature can be combined into one Add user login commit.
  • Removing unnecessary commits — Temporary commits such as Fix typo, Debug, or Try again can be combined into the final meaningful commit.
  • Making code reviews easier — Instead of reviewing many small commits, a reviewer can review one complete commit containing the entire feature.
  • Grouping related changes — For example, commits for creating a login page, adding authentication, and fixing login errors can be squashed into one Implement user authentication commit.
  • Making changes easier to revert — If the entire feature needs to be removed later, you can revert the single squashed commit instead of reverting multiple commits.
  • Maintaining a professional commit history — Before merging a feature branch, developers can squash development commits so the main branch contains a clear and understandable history.

Example of Commit Squashing

The Setup

To follow along, setup running the following commands:

mkdir rebase-squash
cd rebase-squash
git init
echo A > file; git add file; git commit -m "A"
echo B > file; git add file; git commit -m "B"
echo C > file; git add file; git commit -m "C"
echo D > file; git add file; git commit -m "D"

Squash Using Interactive Rebase

Identifying Base Commit

Before squashing commits, you need to identify how many commits you want to squash. See the commit history.

git log --oneline

For our example, it looks similar to following:

6ec3da2 (HEAD -> master) D
7b12a8c C
4806848 B
63a7352 A

Start Interactive Rebase For Squashing

Let’s say we want to squash top 3 commits. Hence we need to choose the base commit just below them which is 63a7352. You can also use HEAD~3 that points to the same commit. Actually, for Top N or Last N, HEAD~N reads better:

git rebase -i HEAD~3

Apply squash Command

Interactive rebase opens Git’s default text editor which is actually a way of asking you to choose which actions you want to preform. At first, it should look similar to following:

pick 4806848 B
pick 7b12a8c C
pick 6ec3da2 D

# Rebase 63a7352..6ec3da2 onto 63a7352 (3 commands)

Because we want to squash all three into one, we will pick the first one (to squash onto) and squash the rest. Edit and save file as:

pick 4806848 B
squash 7b12a8c C
squash 6ec3da2 D

# Rebase 63a7352..6ec3da2 onto 63a7352 (3 commands)

Git now apply the commands and squash three commits into one.

Edit Commit Message

Git will again open editor to save the squashed commits with a commit message:

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

B

# This is the commit message #2:

C

# This is the commit message #3:

D

You can keep the default or write more meaningful message.

Squashing Completed

Git commits with the above commit message to end the process.

What Happens During Commit Squshing (Rebase)

The easiest way to understand the rebasing process for squashing is to simulate exactly what Git does, one step at a time.

Your original history is:

63a7352 A
    |
4806848 B
    |
7b12a8c C
    |
6ec3da2 D   <-- HEAD

And the file at each commit is:

Commitfile contains
AA
BB
CC
DD

Your todo is:

pick   4806848 B
squash 7b12a8c C
squash 6ec3da2 D

The crucial thing is that rebase starts by checking out the base (A) and then replays the selected commits as patches.

Step 1 — Rebase identifies the base

You started the rebase:

git rebase -i HEAD~3

Git identifies:

base = 63a7352 A
commits to replay = B, C, D

So conceptually Git starts at A or HEAD~3:

and file contains:

A

Since this is the base commit, there is no conflict yet.

Step 2 — Git takes commit B (first one in the list)

Git doesn’t primarily think:

“Commit B has file content B; let’s put B into the file.”

Instead, it looks at the change introduced by B.

B’s parent is A:

A <- B

So Git calculates approximately:

git diff A B

The result is:

-A
+B

Current state (the content of file in current commit A:):

A

Patch:

-A
+B

Git looks at the current file and says:

“I need to find A and replace it with B.”

It finds exactly that:

current:
A

Therefore:

A
↓ apply -A +B
B

No conflict.

At this point the rebase has effectively produced:

A → B'

Notice that B' is a new commit, not the original 4806848.

Step 4 — Now Git processes squash C (second on the list)

Your todo says:

squash 7b12a8c C

Git looks at the original C commit.

C’s parent is B:

B <- C

So Git calculates C’s patch:

-B
+C

What’s the current state?

It’s:

B

because we just applied B.

So Git applies:

current:
B

patch:
-B
+C

The B matches perfectly.

Therefore:

B
↓ apply -B +C
C

Still no conflict.

But because this was squash, Git doesn’t create a separate final commit for C. It combines C’s change with the commit being constructed from B.

Step 5 — Now Git processes squash D

D’s parent is C:

C <- D

Therefore D’s patch is:

-C
+D

What’s the current state?

C

So Git applies:

current:
C

patch:
-C
+D

Again, exact match.

Result:

C
↓ apply -C +D
D

No conflict.

What does the final result look like?

You’ve effectively done:

A
 ↓ B's patch
B
 ↓ C's patch
C
 ↓ D's patch
D

But because C and D were marked squash, Git combines everything into one new commit.

So your final history becomes conceptually:

A ── B+C+D

And file contains:

D

The final squashed commit’s overall diff relative to A is:

-A
+D

The Important Mental Model

Think of each commit as a patch between its parent and itself:

A → B

-A
+B
B → C

-B
+C
C → D

-C
+D

During the rebase, Git applies those patches sequentially:

Start:
A

Apply B:
-A
+B
B

Apply C:
-B
+C
C

Apply D:
-C
+D
D
    flowchart LR
    subgraph Start
        A
    end
    subgraph After_B
        B
    end
    subgraph After_C
        C
    end
    subgraph After_D
        D
    end

    Start -.->|"Apply<br>-A<br>+B"| After_B
    After_B -.->|"Apply<br>-B<br>+C"| After_C
    After_C -.->|"Apply<br>-C<br>+D"| After_D
  

Every patch is being applied to exactly the state that its original parent had.

That’s why there is no conflict.

Now here’s when a conflict WOULD happen

Suppose your history were:

A
|
X
|
B

where X changed the file to:

X

Now suppose you tried to replay B’s patch:

-A
+B

onto X.

Git would have:

current file:
X

but the patch is looking for:

-A
+B

There is no A to replace.

That’s where Git may say:

CONFLICT (content): Merge conflict in file

So the key question isn’t:

“Does the commit I’m replaying modify the same file as the base?”

Instead ask:

“Can Git apply this commit’s patch cleanly to the current state?”

In your case:

B's patch expects A → current is A    ✓
C's patch expects B → current is B    ✓
D's patch expects C → current is C    ✓

Therefore:

NO CONFLICT

And that’s also why changing pick to squash doesn’t itself introduce a conflict. squash still applies the commit’s patch; it just tells Git to combine the resulting commit with the previous one.

Little BUT Important Nuances

Squashing commits can make your Git history cleaner, but there are a few things to watch out for:

  • You might lose useful history

    • Squashing combines several commits into one, so the smaller steps and their individual messages are no longer visible.
    • If those commit messages explain important changes, it may be better to keep them.
    • Before squashing, ask yourself whether you’ll need that detailed history later.
  • You may run into conflicts

    • A rebase can sometimes cause merge conflicts, especially when the commits touch similar parts of the code.
    • If Git stops because of a conflict, fix the affected files first.
    • Then stage the resolved files and continue the rebase.
    • If there are several conflicts, the process can take a bit of extra time, so don’t rush through it.
  • Squashing lots of commits can get messy

    • Interactive rebase isn’t always easy to manage when you’re dealing with a huge number of commits.
    • If you have many commits to combine, it can be easier to do them in smaller batches.
    • You can also use Git tools or IDE features that make rebasing easier to handle.
Last updated on