← Recipes

阅读中文版本

Clean Up Local Commits with Interactive Rebase

Rewrite local commit history using git rebase --interactive to squash, reorder, or edit commits before sharing. Use this when preparing a clean pull request from messy WIP commits.

~10 mingitworkflowhistoryversion-control

Task

Squash multiple WIP commits into a single logical commit with a clean message before opening a pull request.

When to use this

You've been working on a feature branch with commits like 'wip', 'fix typo', 'actually fix bug', 'address review'. You want to present a single well-structured commit.

Prerequisites

  • Git 2.20+ installed and configured
  • Unpushed commits on your current feature branch
  • No one else is working on the same branch

Steps

1. Identify how many commits to rebase. Count back from HEAD to the commit before your changes began.

git log --oneline -n 10

Expected output: A list of recent commits with 7-character SHA and subject lines. Identify the number of commits to squash.

2. Start interactive rebase for the last N commits (e.g., 4 commits).

git rebase -i HEAD~4

Expected output: An editor opens listing 4 commits with 'pick' prefix, with instructions at the bottom about available commands.

3. In the editor, change all but the first commit from 'pick' to 'squash' (or 's') to merge their changes into the first commit. Alternatively, use 'fixup' (or 'f') to discard their commit messages entirely. Save and close the editor.

Expected output: Another editor opens combining all commit messages. Edit to write a single clear commit message. Save and close.

4. Verify the new commit history shows a single consolidated commit.

git log --oneline -n 5

Expected output: The messy commits are gone, replaced by one commit with your new message. Note: commit SHA has changed (hash rewritten).

Troubleshooting

Problem

CONFLICT during rebase

Solution

Resolve the conflict in the affected files, stage them with `git add .`, then run `git rebase --continue`. To abort: `git rebase --abort`.

Problem

Rebase already-pushed commits

Solution

Never rebase pushed shared commits. Only use this on local-only work. If already pushed, coordinate with team before force-pushing.

Problem

Editor opens in vi and you can't exit

Solution

Type `:wq` and Enter to save and quit. To change default editor: `git config --global core.editor 'code --wait'` (VS Code).

Limitations

  • Never rebase commits that have been pushed to a shared branch — it rewrites history and breaks collaborators' local copies
  • Interactive rebase creates new commit SHAs, so it's not safe for commits that other branches reference
  • Complex multi-branch histories may require rerere (reuse recorded resolution) to avoid resolving the same conflict repeatedly

Why Clean Up Local Commits

Messy WIP commits ('fix', 'wip', 'really fix this time') make code review harder and pollute the repository history. Interactive rebase lets you rewrite local-only commits before sharing them safely.

When to Use Interactive Rebase

  • You have multiple small WIP commits that form one logical change
  • You want to reorder commits to tell a better story
  • You need to fix a typo in an older commit’s content
  • You want to edit a commit message before pushing

Step-by-Step Example

# View recent commits
git log --oneline -n 5
# a1b2c3d Add user authentication
# d4e5f6g fix typo
# h7i8j9k actually implement auth
# l0m1n2p wip
# q3r4s5t (origin/main) Recent shared commit

# Start interactive rebase for the last 4 commits
git rebase -i HEAD~4

In the editor that opens:

pick l0m1n2p wip
squash h7i8j9k actually implement auth
squash d4e5f6g fix typo
squash a1b2c3d Add user authentication

Rebase Commands Reference

Command Short Effect
pick p Keep this commit as-is
reword r Keep changes, edit message
edit e Pause to amend this commit
squash s Merge changes into previous commit, combine messages
fixup f Merge changes into previous, discard this message
drop d Remove this commit entirely

Fixup vs Squash

Squash: Combines messages — you edit the final message from all combined commits:

# This is a combination of 4 commits.
# The first commit's message is:
wip
# This is the 2nd message:
actually implement auth

Fixup: Discards all other messages — only the first commit’s message is kept:

pick l0m1n2p wip    # keep this message
fixup h7i8j9k        # discard "actually implement auth"
fixup d4e5f6g        # discard "fix typo"

Recovery: If Something Goes Wrong

# Abort the rebase (return to pre-rebase state)
git rebase --abort

# After resolving a conflict, continue the rebase
git add .
git rebase --continue

# If you made a mistake, use the reflog to find the original state
git reflog
git reset --before-rebase-state

Safety Rules

  1. Never rebase pushed commits on shared branches
  2. Create a backup branch before major rewrites: git branch backup-my-feature
  3. Use exec in interactive rebase to run tests after each pick
  4. Configure merge.conflictStyle to diff3 for 3-way conflict view
# Add this to .gitconfig for better conflict resolution
git config --global merge.conflictStyle diff3
  • --autosquash: Mark commits as fixup! or squash! during creation, then git rebase -i --autosquash arranges them automatically
  • git commit --amend: Quick single-commit edit (equivalent to reword for the last commit)