Advanced Git Rebasing Techniques: Interactive Guide

Why Interactive Rebasing?

Interactive rebasing allows you to rewrite your commit history before sharing it with others. It's like editing a draft before publishing – you can clean up, reorganize, and polish your work.

Key Benefits: Cleaner history, logical commit grouping, fixing commit messages, removing unwanted commits, and reordering work flow.

When to Use Interactive Rebase

  • Before Pull Requests: Clean up your feature branch before merging
  • After Code Review: Incorporate feedback into existing commits
  • Historical Cleanup: Fix old commit messages or split large commits
  • Branch Maintenance: Keep long-running branches tidy
  • Learning/Experimentation: Safe way to practice Git manipulation

Golden Rule: Never rebase commits that have been shared with others (pushed to remote). Rebasing rewrites history, which can cause serious issues for collaborators.

Interactive Rebase Interface

When you run git rebase -i, Git opens an editor with a list of commits and available actions:

pick a1b2c3d Add user authentication module
pick b2c3d4e Fix login validation bug
pick c3d4e5f Add password reset feature
pick d4e5f6g Update documentation

# Rebase a1b2c3d..d4e5f6g onto f5g6h7i (4 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.

Available Commands

  • pick (p): Keep commit as-is (default)
  • reword (r): Keep commit but edit its message
  • edit (e): Pause at commit for modifications
  • squash (s): Combine with previous commit
  • fixup (f): Squash and discard message
  • drop (d): Remove commit entirely
  • exec (x): Run shell command during rebase

Step-by-Step Interactive Rebase Guide

Step 1: Start Interactive Rebase

Decide how many commits you want to edit:

# Edit last 3 commits
git rebase -i HEAD~3

# Edit all commits since branching from main
git rebase -i main

# Edit specific commit range
git rebase -i a1b2c3d

Pro Tip: Use git log --oneline -10 to see recent commits and their hashes before starting.

Step 2: Edit the Rebase Plan

In the editor, change commands for each commit:

reword a1b2c3d Add user auth module
pick b2c3d4e Fix login validation bug
squash c3d4e5f Add password reset feature
fixup d4e5f6g Update documentation

Save and close the editor. Git will proceed through each command.

Step 3: Handle Each Action

Rewording a Commit

Git opens an editor with the current message. Edit it:

# Original message
Add user auth module

# Better message
feat: implement user authentication

- Add JWT token generation
- Implement login endpoint
- Add password hashing
- Write authentication tests

Squashing Commits

Git combines commits and asks for a new message:

# This is a combination of 2 commits:
# This is the 1st commit message:
Add password reset feature

# This is the commit message #2:
Update documentation

# Please enter the commit message for your changes...

Best Practice: When squashing, write a comprehensive message that covers all changes.

Editing a Commit

Git pauses at the commit. Make your changes:

# Git tells you:
Stopped at a1b2c3d... Add user auth module
You can amend the commit now, with
  git commit --amend

Once you are satisfied with your changes, run
  git rebase --continue

Step 4: Complete the Rebase

After all actions are processed, verify the result:

# Check new history
git log --oneline -5

# Verify nothing is broken
git status
npm test

Advanced Squashing Strategies

1. Grouping Related Changes

Combine commits that belong together logically:

pick a1b2c3d Add user model
squash b2c3d4e Add user controller
squash c3d4e5f Add user routes
squash d4e5f6g Add user tests
pick e5f6g7h Fix typo in README

Result: One cohesive "Add user management feature" commit.

2. Separating Fixes from Features

Keep bug fixes separate from feature development:

pick a1b2c3d Implement payment gateway
fixup b2c3d4e Fix currency formatting
fixup c3d4e5f Update API key config
pick d4e5f6g Add payment analytics

Result: Clean feature commits with fixes incorporated.

3. Splitting Large Commits

Use edit to split one commit into multiple:

# Mark commit for editing
git rebase -i HEAD~5
# Change to 'edit' for the commit to split

# When Git pauses:
git reset HEAD~
git add -p # Stage changes piece by piece
git commit -m "First logical change"
git commit -m "Second logical change"
git rebase --continue

Rebasing vs Merging: When to Choose

Aspect Rebasing Merging
History Linear, clean timeline Shows actual branching/merging
Complexity Higher risk if not careful Safer, preserves all history
Use Case Feature branches before PR Long-lived branches, releases
Team Size Small teams, experienced users Large teams, beginners
Conflict Resolution Resolve once per commit Resolve once at merge

Modern Approach: Many teams use rebase for feature branches (keeps history clean) and merge for main branch integration (preserves merge context).

Practical Workflows

1. Pre-Pull Request Cleanup

# On your feature branch
git fetch origin
git rebase origin/main
git rebase -i HEAD~10 # Clean up last 10 commits
# Squash WIP commits, reword messages, reorder logically
git push -f # Force push cleaned branch

2. Incorporating Code Review Feedback

# After receiving review comments
git rebase -i main
# Mark relevant commits as 'edit'
# At each edit stop, make changes
git add .
git commit --amend
git rebase --continue

3. Fixing Historical Issues

# Find the problematic commit
git log --oneline --grep="typo"

# Rebase from before that commit
git rebase -i a1b2c3d^ # ^ means parent
# Change 'pick' to 'reword' or 'edit'
git push -f # Only if not shared!

Advanced Techniques

1. Using Exec for Automation

Run commands during rebase for consistency checks:

# In rebase plan:
pick a1b2c3d Add feature A
exec npm test
pick b2c3d4e Add feature B
exec npm run lint
pick c3d4e5f Add feature C
exec npm test

2. Reordering Commits

Simply change the order in the rebase plan:

pick c3d4e5f Add tests
pick a1b2c3d Implement feature
pick b2c3d4e Fix bug

3. Rebase with Merge Conflicts

Handle conflicts during rebase:

# When conflict occurs:
# 1. Resolve conflicts in files
git status # See conflicted files
git add resolved-file.js

# 2. Continue rebase
git rebase --continue

# 3. Or skip/skip this commit
git rebase --skip

# 4. Or abort entirely
git rebase --abort

Command Reference

Basic Rebase Commands

# Rebase current branch onto main
git rebase main

# Interactive rebase last 5 commits
git rebase -i HEAD~5

# Rebase with auto-stashing
git rebase --autostash main

# Continue after conflict resolution
git rebase --continue

Advanced Options

# Rebase without checkout (new in Git 2.23)
git rebase main feature-branch

# Keep empty commits
git rebase --keep-empty

# Preserve merges during rebase
git rebase -p main

# Verify signatures
git rebase --verify-signatures

Recovery Commands

# Abort rebase in progress
git rebase --abort

# View rebase status
git status

# Find original branch tip
git reflog
git reset --hard ORIG_HEAD

Frequently Asked Questions

How do I recover if I mess up a rebase?

Git provides several recovery options:

  1. During rebase: Use git rebase --abort to return to pre-rebase state
  2. After bad push: Use git reflog to find the previous state, then git reset --hard HEAD@{n}
  3. For shared branches: Coordinate with team, may need to revert and re-apply
  4. Automatic backup: Git keeps original ref in ORIG_HEAD: git reset --hard ORIG_HEAD

Prevention: Always backup branches with git branch backup-branch before complex rebases.

When should I use fixup vs squash?

Both combine commits, but differently:

  • fixup (f): Discard the commit message entirely. Use for typo fixes, minor adjustments, or follow-up commits that don't need separate documentation.
  • squash (s): Keep the commit message for editing. Use when combining related feature commits where you want to preserve and merge their messages.

Example:

pick a1b2c3d Implement login feature
fixup b2c3d4e Fix typo in variable name
squash c3d4e5f Add login tests
fixup d4e5f6g Update package.json

Result: One commit with message combining "Implement login feature" and "Add login tests", with the minor fixes incorporated silently.

Can I rebase a branch that's already been pushed?

Short answer: Yes, but with extreme caution.

When it's safe:

  • You're the only one working on the branch
  • No open pull requests reference it
  • You can force push without affecting others

Procedure:

# 1. Warn team members
# 2. Make sure you have latest
git fetch origin
git rebase origin/main
# 3. Force push with lease (safest)
git push --force-with-lease
# 4. Notify team push is complete

Never rebase: main/master branches, release branches, or any branch others are actively using.

How do I rebase only specific commits?

Use git cherry-pick with rebase or selective interactive rebase:

# Method 1: Interactive rebase with drop
git rebase -i HEAD~10
# Change unwanted commits to 'drop'

# Method 2: Cherry-pick specific commits to new branch
git checkout -b new-feature main
git cherry-pick a1b2c3d b2c3d4e c3d4e5f

# Method 3: Rebase with --onto for complex cases
git rebase --onto main feature-a~3 feature-a

--onto explanation: Takes commits from feature-a starting after the 3rd commit back, and rebases them onto main.

What's the difference between git commit --amend and rebase?

Both modify history, but at different scales:

Aspect git commit --amend git rebase -i
Scope Only the most recent commit Multiple commits (any number)
Use Case Fix typo in last commit, add forgotten file Clean up series of commits before sharing
Complexity Simple, single operation Complex, multi-step process
History Impact Changes only HEAD commit Rewrites all commits after chosen point

Best Practice: Use --amend for immediate fixes, use rebase -i for periodic cleanup before pushing.

Previous: Git Rebasing Basics Next: Git Cherry-picking