Git Interactive Rebase Guide | Squash, Fixup, Reorder & Clean History

Git Interactive Rebase Guide | Squash, Fixup, Reorder & Clean History

이 글의 핵심

Interactive rebase lets you rewrite commit history before merging — squash WIP commits, fix messages, reorder changes, and split large commits. This guide covers every rebase command with real workflow examples.

Why Interactive Rebase?

Before rebase:                         After rebase:
  abc123 WIP                           abc789 Add user authentication
  def456 WIP: actually works             ↑ clean, single commit
  ghi789 Fix typo                        ready to merge
  jkl012 Add user authentication

Interactive rebase lets you clean up messy development history before it becomes permanent in main.

Common use cases:

  • Combine WIP commits into meaningful commits before PR
  • Fix a bad commit message from 3 commits ago
  • Remove a commit that shouldn’t be in the branch
  • Reorder commits for logical clarity
  • Split one large commit into focused changes

Basic Syntax

# Rebase last N commits interactively
git rebase -i HEAD~3    # Rebase last 3 commits

# Rebase from a specific commit (exclusive — that commit is not included)
git rebase -i abc1234

# Rebase onto another branch (rebase feature onto main)
git rebase -i main

# Rebase from the point where branch diverged from main
git rebase -i $(git merge-base HEAD main)

The Rebase Editor

When you run git rebase -i HEAD~4, an editor opens:

pick a1b2c3d Add user model
pick e4f5a6b Add auth routes
pick 7c8d9e0 WIP: half-working login
pick 1f2a3b4 Fix login endpoint

# Rebase a1b2c3d..1f2a3b4 onto 9f8e7d6 (4 commands)
#
# Commands:
# p, pick   = use commit
# r, reword = use commit, but edit commit message
# e, edit   = use commit, but stop for amending
# s, squash = use commit, meld into previous commit (keep message)
# f, fixup  = like squash, but discard this commit's log message
# x, exec   = run command (the rest of the line) using shell
# b, break  = stop here
# d, drop   = remove commit
# l, label  = label current HEAD with a name

Commands in Detail

squash / fixup — Combine Commits

# Before
pick a1b2c3d Add user model
pick e4f5a6b Add auth routes
pick 7c8d9e0 WIP: half-working login
pick 1f2a3b4 Fix login endpoint

# After editing — squash/fixup into one commit
pick a1b2c3d Add user model
pick e4f5a6b Add auth routes
pick 7c8d9e0 WIP: half-working login
f    1f2a3b4 Fix login endpoint     # fixup: combine, discard message

# squash will open another editor to combine messages
# fixup (f) silently drops the commit message — use for minor fixes
# Squash all WIP commits before pushing
git rebase -i HEAD~5
# Change all "pick" except the first to "squash" or "fixup"

reword — Fix a Commit Message

# Change "pick" to "reword" (or "r")
pick a1b2c3d Add user model
reword e4f5a6b fix thing         # ← message needs fixing
pick 7c8d9e0 Add auth routes

# Git will stop at the reword commit and open editor for the message

edit — Stop to Amend a Commit

pick a1b2c3d Add user model
edit e4f5a6b Add auth routes     # ← stop here
pick 7c8d9e0 Add login

# During the rebase, git stops at 'edit' commits:
# You can then:
git add forgotten-file.ts
git commit --amend               # Add to the commit
git rebase --continue            # Resume

drop — Remove a Commit

pick a1b2c3d Add user model
drop e4f5a6b Accidental debug logs   # ← delete this commit
pick 7c8d9e0 Add auth routes

# Or just delete the line — same effect

Reorder Commits

# Before (in rebase editor — just move lines)
pick a1b2c3d Add user model
pick e4f5a6b Add auth routes
pick 7c8d9e0 Add tests

# After (reordered — tests before auth routes)
pick a1b2c3d Add user model
pick 7c8d9e0 Add tests            # moved up
pick e4f5a6b Add auth routes

# Note: conflicts are possible if commits depend on each other

Common Workflows

Clean Up Before a PR

# You have 6 messy commits on your feature branch
git log --oneline origin/main..HEAD
# 1a2b3c4 fix typo
# 5d6e7f8 WIP 2
# 9g0h1i2 WIP: almost
# 3j4k5l6 add tests
# 7m8n9o0 add auth
# 1p2q3r4 setup

# Clean them up
git rebase -i origin/main

# In the editor:
pick 1p2q3r4 setup
squash 7m8n9o0 add auth        # combine with setup? No — fixup/squash adjacent
pick 7m8n9o0 add auth
squash 9g0h1i2 WIP: almost
squash 5d6e7f8 WIP 2
pick 3j4k5l6 add tests
fixup 1a2b3c4 fix typo
# Result: clean 3-commit history

Fix a Commit Message Mid-History

git rebase -i HEAD~5
# Change "pick" to "reword" on the commit with the bad message
# Git stops, opens editor — fix the message
# git rebase --continue

Split One Large Commit

git rebase -i HEAD~3
# Mark the large commit as "edit"

# When git stops:
git reset HEAD~1           # Unstage the commit (keep changes)
git add src/models/        # Stage part 1
git commit -m "Add user model"
git add src/routes/        # Stage part 2
git commit -m "Add auth routes"
git rebase --continue      # Continue

git commit —fixup for Automated Squash

# Make a fixup commit for a specific earlier commit
git commit --fixup=abc1234    # Creates: "fixup! <original message>"

# Automatically squash all fixup commits
git rebase -i --autosquash HEAD~5
# fixup commits are automatically placed after their target and marked 'fixup'

Handling Conflicts During Rebase

# When a conflict occurs:
git status                    # See conflicting files
# Edit files to resolve conflicts
git add resolved-file.ts      # Stage resolved files
git rebase --continue         # Continue to next commit

# Skip a commit that causes unresolvable conflict (rare)
git rebase --skip

# Abort entirely and return to pre-rebase state
git rebase --abort

Rebase vs Merge

# Workflow: keep feature branch up to date with main
# Option 1: Merge (creates merge commit)
git checkout feature
git merge main
# History: f1 → f2 → merge(f2, m3) → f3

# Option 2: Rebase (linear history)
git checkout feature
git rebase main
# History: m1 → m2 → m3 → f1' → f2' → f3'
# (commits are replayed on top of main)

# Most teams use:
git fetch origin
git rebase origin/main    # Update feature branch
# Then: create PR → squash merge or regular merge into main

Safe Rebase Rules

# ✅ Safe: rebase commits that are only on your local branch
git rebase -i HEAD~5          # Local commits only

# ✅ Safe: rebase your feature branch onto main (before others pull it)
git rebase origin/main

# ⚠️ Risky: rebase commits you've pushed to a shared branch
# Anyone who pulled those commits will have diverged history
# If you must:
git push --force-with-lease   # Safer than --force (fails if remote was updated)

# ❌ Never: rebase main, develop, or any shared branch
git checkout main
git rebase feature            # DO NOT DO THIS

Undo a Rebase

# git reflog shows all recent HEAD positions — even after rebase
git reflog
# HEAD@{0}: rebase finished
# HEAD@{1}: rebase: Add auth routes
# HEAD@{2}: checkout: moving from main to feature
# HEAD@{5}: commit: last good state   ← find this

# Reset to pre-rebase state
git reset --hard HEAD@{5}

Quick Reference

CommandWhat it does
pickKeep commit as-is
rewordKeep commit, edit message
editStop here to amend
squashCombine with previous, keep both messages
fixupCombine with previous, discard this message
dropDelete this commit
--autosquashAuto-arrange fixup! commits
--abortCancel rebase, restore original state
--continueAfter resolving conflict, continue
--skipSkip problematic commit

Related posts: