← All posts

Git Merge, Rebase & Squash — Part 4: Interactive Rebase and Advanced Rebase

Data-Sci & Digital HealthBasic InfoTech & Computing Nexus
Git Merge, Rebase & Squash — Part 4: Interactive Rebase and Advanced Rebase
On this page

Abstract

Preparing a clean commit history before integrating feature branches is essential for maintaining a comprehensible project repository. However, raw development histories are often messy, requiring developers to rewrite and reorganize the commit graph. Interactive rebase provides granular control over this process through commands like pick, reword, edit, squash, fixup, and drop, allowing developers to meticulously tidy individual commits. Workflows can be streamlined using autosquash for automatic fixups. Developers can isolate specific commit sequences using rebase --onto to detach stacked branches, or preserve the original merge topology by applying the --rebase-merges flag. Furthermore, combining a rebase with a fast-forward merge achieves a linear history without sacrificing individual commits, whereas executing an interactive rebase prior to a merge facilitates a controlled manual squash. Because these operations fundamentally rewrite history, they require careful execution on private branches alongside --force-with-lease when updating remote repositories. This article outlines these advanced rebase techniques to equip developers with precise commands for curating repository history.


Infographic visual summary of Git Merge, Rebase & Squash — Part 4: Interactive Rebase and Advanced Rebase
Visual summary · ภาพสรุป

Part 4 builds on the basic rebase from Part 3 and moves into advanced territory. We start with interactive rebase, which lets you tidy commits one at a time (reword, combine, delete, reorder), then cover autosquash, rebase --onto for moving only part of a branch onto a new base, preserving merge topology with --rebase-merges, and finish with two patterns you will use constantly: rebase + fast-forward merge and rebase + squash.

By the end you will be able to clean up a feature branch's history before opening a PR, and understand how each advanced rebase command rewrites the commit graph. Everything in this part rewrites history, so do it on a private branch and use git push --force-with-lease when you push over what was there before.

Interactive rebase: tidy commits before opening a PR

Interactive rebase is used to organise commits before you open or merge a PR.

git rebase -i HEAD~4

Git opens an editable todo list showing your last four commits:

pick a111111 Add login form
pick b222222 Fix typo
pick c333333 Fix validation
pick d444444 Change button text

You change the command in front of each commit to tell Git what to do with it. There are six core commands to know.

pick

Keep the commit exactly as it is; change nothing.

pick a111111 Add login form

reword

Keep the changes, but edit only the commit message.

reword a111111 Add login form

edit

Stop at that commit so you can change its contents (edit the actual files).

edit a111111 Add login form

When the rebase pauses at that commit, edit the files and then run:

git add .
git commit --amend
git rebase --continue

squash

Merge this commit into the one before it, and open an editor to combine the two commit messages.

pick a111111 Add login form
squash b222222 Fix typo
squash c333333 Fix validation

The result is a single commit.

fixup

Like squash, but discard the commit message of the commit being folded in.

pick a111111 Add login form
fixup b222222 Fix typo
fixup c333333 Fix validation

The result keeps only the message of a111111.

drop

Remove the commit from history entirely — both its changes and its message.

drop b222222 Debug logging

The six interactive-rebase commands at a glance

CommandKeeps the changes?Keeps the message?Typical use
pickYesYesKeep the commit as-is
rewordYesEdit itFix only the commit message
editYes (pauses to amend)YesChange the file contents of that commit
squashYes (folded into previous)Combines bothMerge into the previous commit, keeping both messages
fixupYes (folded into previous)Discards its ownMerge into the previous commit, ignoring its message
dropNoNoRemove the commit completely

Reordering commits

Besides changing commands, you can also reorder the lines in the todo list.

Original:

pick a111111 Add UI
pick b222222 Add tests
pick c333333 Add API

Changed to:

pick c333333 Add API
pick a111111 Add UI
pick b222222 Add tests

Git will replay the commits in the new order you arranged. Conflicts can arise if the commits depend on one another (for example, a later commit edits a file an earlier commit created).

Autosquash: automatic fixups

If you already know at commit time that a new commit should be folded into a specific older commit, create a fixup commit tied to that target commit up front.

git commit --fixup=<commit-hash>

Example:

git commit --fixup=a111111

Then run the rebase with --autosquash:

git rebase -i --autosquash HEAD~5

Git moves the fixup commit to sit right after its target commit and automatically sets that line's command to fixup, so you never have to move lines in the todo list yourself.

A real-world workflow:

git add .
git commit --fixup=a111111
git rebase -i --autosquash main

Rebase --onto: move only part of a branch

Use this when you want to move only part of a branch onto a new base, not the whole line.

Suppose you have this structure, where feature-b was branched off feature-a in turn:

A B C D E F G H main feature-a feature-b
Before rebase --onto: feature-b (G–H) is stacked on feature-a (E–F), which branches off main at C.

Now you want to detach feature-b from feature-a and place it directly after main.

git rebase --onto main feature-a feature-b

As a result, commits G and H are re-created as G' and H' after main, while feature-a stays where it was:

A B C D G′ H′ E F feature-b main feature-a
After git rebase --onto main feature-a feature-b: G and H are re-created as G′ and H′ on top of main (at D), while feature-a stays where it was.

The general form of the command:

git rebase --onto <new-base> <old-base> <branch>

Which means:

Take the commits that lie after old-base on branch and place them onto new-base.

In the example above, new-base is main, old-base is feature-a (the point where feature-b split off), and branch is feature-b. So the commits after feature-a (namely G and H) are lifted onto main.

Rebasing with merge commits: --rebase-merges

A normal rebase can flatten an existing merge structure into a straight line, because Git only replays ordinary commits and drops the merge commits.

If you want to try to preserve the original merge topology:

git rebase --rebase-merges main

Or the short form:

git rebase -r main

This suits a branch that already has sub-branches or internal merge commits, and where you want the merge structure to keep its shape after the rebase.

Rebase + fast-forward merge

This is the pattern that combines rebase with a fast-forward merge to get a linear history while keeping every one of the feature's individual commits.

Start with a feature branch split off at C while main has moved ahead to G:

          D---E
         /
A---B---C---F---G  main

Do it in two steps — rebase the feature up to date with main first, then fast-forward:

git switch feature
git rebase main

git switch main
git merge --ff-only feature

The result is linear, with D and E re-created as D' and E' after G:

A B C F G D′ E′
Rebase + fast-forward merge: one straight line A–B–C–F–G–D′–E′, with D and E re-created as D′ and E′ — no merge commit.

This gives you three things at once:

Rebase + squash

This pattern tidies the history inside the branch with an interactive rebase first, then merges it in. Unlike a squash merge, you control how the commits are combined on the branch itself.

Tidy the branch first with an interactive rebase onto main:

git switch feature/login
git rebase -i main

Fold the small commits into the main one with fixup:

pick   Add login feature
fixup  Fix typo
fixup  Fix CSS
fixup  Fix tests

Then fast-forward merge into main:

git switch main
git merge --ff-only feature/login

On main you are left with a single commit, S:

A B C S main
Rebase + squash: after tidying the branch with rebase -i and fast-forwarding, main carries a single commit S.

The outcome resembles a squash merge, but the difference is that the branch's history was cleaned up first and then the pointer was fast-forwarded in — rather than Git collapsing the whole branch into one commit at merge time.

Key takeaways

0
Message for International and Thai ReadersUnderstanding My Medical Context in ThailandRead more →Message for International and Thai ReadersUnderstanding My Broader Content Beyond MedicineRead more →

Comments

No comments yet. Be the first to share your thoughts.

Sign in to comment