Interactive Rebase

Technical Foundation: Commit Graph Transformation

Interactive rebase represents Git’s most sophisticated history manipulation capability, enabling developers to refine commit sequences before integration into shared branches. Unlike standard rebase operations that mechanically replay commits, interactive mode provides granular control over each commit’s fate—allowing reordering, combination, modification, or deletion within a single workflow.

Need deeper context? Check out Understanding HEAD to learn about the HEAD~5 syntax used throughout this guide, and Merge vs Rebase for guidance on when to use rebasing vs merging.

Conceptual Architecture

Standard Rebase (Mechanical Replay):

Feature branch:  A - B - C - D
                         ↓ (rebase onto main)
Main branch:     A - B - E - F - C' - D'

Interactive Rebase (Selective Transformation):

Original:        A - B - C - D - E - F
                         ↓
Interactive:     A - B - CE - F'
                    (C+D combined, E modified, F reordered)

Key Distinction: Interactive rebase treats the commit sequence as an editable document rather than an immutable timeline.


Underlying Mechanics: How Interactive Rebase Works

Command Execution:

git rebase -i HEAD~5
# Or from specific commit:
git rebase -i <base-commit>

Git’s Internal Process:

  1. Checkpoint Creation: Save current HEAD position to reflog
  2. Commit Extraction: Identify commits between base and HEAD
  3. TODO List Generation: Create edit script in .git/rebase-merge/git-rebase-todo
  4. Editor Launch: Open TODO list in configured editor
  5. Sequential Execution: Process each instruction line-by-line
  6. Branch Update: Move branch pointer to final commit
  7. Cleanup: Remove temporary rebase state

Technical Detail: Interactive rebase operates on a detached HEAD state during execution, preventing branch pointer confusion during commit replay.


The Interactive Rebase Command Set

Command Overview Table

CommandShorthandEffectCommit SHA Changes
pickpKeep commit as-is✅ Changes (replay)
rewordrKeep changes, edit message✅ Changes
editePause for amendments✅ Changes
squashsCombine with previous✅ Creates new commit
fixupfCombine, discard message✅ Creates new commit
execxExecute shell command❌ No direct effect
dropdRemove commit entirelyN/A (deleted)
breakbPause rebase (for inspection)N/A (temporary)
labellName current HEAD positionN/A (reference)
resettReset HEAD to labelVaries
mergemCreate merge commit✅ Creates merge

Command 1: pick - Preserve Commit

Purpose: Include commit in rebased history without modification.

Usage: Default command for all commits; typically used when no changes needed.

Example TODO List:

pick abc1234 Implement user authentication
pick def5678 Add JWT token generation
pick ghi9012 Fix authentication bug

Technical Behavior:

  • Applies commit diff to current HEAD
  • Creates new commit with same content
  • New SHA-1 due to different parent reference

Use Case: Keeping most commits unchanged while modifying specific ones.


Command 2: reword - Modify Commit Message

Purpose: Change commit message without altering file changes.

Interactive Process:

# TODO list
pick abc1234 Implement authentication
reword def5678 Add tokens
pick ghi9012 Fix bug

# Git pauses at "reword" commit
# Editor opens with current message:
Add tokens

# Edit to:
Add JWT token generation with RSA-256 signing

Implements secure token-based authentication using industry-standard
JWT format with configurable expiration times.

Technical Behavior:

  • Replays commit changes
  • Opens editor for message modification
  • Creates new commit with updated message
  • Continues rebase automatically after save

Use Case: Improving commit messages before code review or main branch integration.


Command 3: edit - Pause for Amendments

Purpose: Stop rebase at specific commit for arbitrary modifications.

Interactive Workflow:

# TODO list
pick abc1234 Implement authentication
edit def5678 Add token generation
pick ghi9012 Fix bug

# Git pauses:
Stopped at def5678... Add token generation
You can amend the commit now, with

    git commit --amend

Once you are satisfied with your changes, run

    git rebase --continue

Modification Options During Edit:

Option 1: Amend Commit

# Make additional changes
vim src/auth.py

# Amend existing commit
git add src/auth.py
git commit --amend
git rebase --continue

Option 2: Split Commit

# Reset to previous commit (keeps changes unstaged)
git reset HEAD^

# Create multiple commits
git add file1.py
git commit -m "First logical change"

git add file2.py
git commit -m "Second logical change"

git rebase --continue

Option 3: Insert New Commits

# Create new commit before continuing
vim new_feature.py
git add new_feature.py
git commit -m "Add new feature"

git rebase --continue

Use Case: Complex commit restructuring requiring arbitrary modifications.


Command 4: squash - Combine with Message Editing

Purpose: Merge commit into previous commit, combining both messages.

Interactive Process:

# TODO list
pick abc1234 Implement authentication
squash def5678 Add token generation
squash ghi9012 Fix typo in auth

# Git combines commits and opens editor:
# This is a combination of 3 commits.
# The first commit's message is:

Implement authentication

# This is the 2nd commit message:

Add token generation

# This is the 3rd commit message:

Fix typo in auth

# Edit combined message:
Implement complete authentication system

- Core authentication logic
- JWT token generation with RSA-256
- Session management

Result: Three commits become one with edited combined message.

Technical Behavior:

  • Applies all diffs sequentially
  • Combines commit messages
  • Creates single new commit
  • New SHA-1 reflecting combined changes

Use Case: Consolidating related commits (feature + subsequent fixes) into coherent unit.


Command 5: fixup - Combine Without Message

Purpose: Merge commit into previous, discarding this commit’s message.

Comparison with Squash:

# Using squash (message editor opens)
pick abc1234 Add feature
squash def5678 Fix typo
# Editor opens for combined message

# Using fixup (automatic)
pick abc1234 Add feature
fixup def5678 Fix typo
# No editor; keeps "Add feature" message

Result: Same file changes as squash, but automatic message handling.

Use Case: Fixing commits with “Fix typo” or “Oops, forgot file” messages that don’t deserve preservation.


Command 6: exec - Execute Shell Commands

Purpose: Run arbitrary commands between commit replays.

Use Cases:

Automated Testing:

pick abc1234 Add feature A
exec npm test
pick def5678 Add feature B
exec npm test
pick ghi9012 Add feature C
exec npm test

Result: Rebase aborts if any test fails, pinpointing problematic commit.


Build Verification:

pick abc1234 Refactor module A
exec npm run build
pick def5678 Refactor module B
exec npm run build

Result: Ensures each commit builds successfully.


Code Quality Gates:

pick abc1234 Add new endpoint
exec npm run lint
exec npm run type-check
pick def5678 Add tests
exec npm test

Result: Multi-stage verification for each commit.


Technical Behavior:

  • Pauses rebase after previous commit
  • Executes command in shell
  • Continues if exit code 0
  • Aborts if exit code non-zero

Recovery from Failed Exec:

# If command fails
git rebase --continue  # Skip this exec
# Or
git rebase --abort     # Cancel entire rebase

Command 7: drop - Remove Commit

Purpose: Delete commit from history entirely.

Explicit vs. Implicit Deletion:

# Explicit (recommended)
pick abc1234 Add feature
drop def5678 Debug logging
pick ghi9012 Add tests

# Implicit (dangerous)
pick abc1234 Add feature
# def5678 deleted from list
pick ghi9012 Add tests

Why Explicit is Better:

  • Clear intent in TODO list
  • Recoverable via reflog
  • Prevents accidental deletion

Use Case: Removing experimental commits or debug code before integration.

Warning: If dropped commit is depended on by later commits, conflicts will occur.


Advanced Interactive Rebase Patterns

Pattern 1: The “Clean Up Before PR” Workflow

Scenario: Feature branch with messy development history needs refinement before code review.

Initial History:

git log --oneline
abc1234 Implement user profile endpoint
def5678 Fix typo
ghi9012 Add validation
jkl3456 WIP: testing approach
mno7890 Remove debug logging
pqr1234 Add tests
stu5678 Fix test typo

Interactive Rebase Strategy:

git rebase -i HEAD~7

# TODO list:
pick abc1234 Implement user profile endpoint
fixup def5678 Fix typo                    # Merge typo fix
squash ghi9012 Add validation             # Combine validation
drop jkl3456 WIP: testing approach        # Remove WIP
fixup mno7890 Remove debug logging        # Clean up debug
pick pqr1234 Add tests
fixup stu5678 Fix test typo               # Merge test fix

Resulting History:

git log --oneline
vwx9012 Implement user profile endpoint with validation
yza3456 Add tests

Benefit: Two clear, reviewable commits instead of seven messy ones.


Pattern 2: Commit Reordering for Logical Flow

Scenario: Commits created in implementation order don’t reflect logical feature progression.

Original Order (Implementation):

abc1234 Add database schema
def5678 Implement API endpoint
ghi9012 Add frontend component
jkl3456 Add database migrations
mno7890 Add API tests

Logical Order (Feature Flow):

pick jkl3456 Add database migrations      # 1. Database first
pick abc1234 Add database schema          # 2. Schema second
pick def5678 Implement API endpoint       # 3. Backend third
pick mno7890 Add API tests                # 4. Tests fourth
pick ghi9012 Add frontend component       # 5. Frontend last

Why Reorder:

  • Logical progression: data layer → business logic → presentation
  • Easier bisect debugging
  • Clearer code review narrative

Conflict Risk: Reordering commits that modify same files may create conflicts.


Pattern 3: Splitting Monolithic Commits

Scenario: One large commit contains multiple logical changes.

Original Commit:

abc1234 Update user authentication system
  - Add JWT support
  - Refactor password hashing
  - Update session management
  - Fix security vulnerability

Split Strategy:

git rebase -i HEAD~1

# Change to edit
edit abc1234 Update user authentication system

# Reset to parent (keeps changes unstaged)
git reset HEAD^

# Create focused commits
git add src/jwt.py
git commit -m "Add JWT token support"

git add src/password.py
git commit -m "Refactor password hashing to use bcrypt"

git add src/session.py
git commit -m "Update session management"

git add src/security_fix.py
git commit -m "Fix authentication bypass vulnerability"

git rebase --continue

Result: Four focused commits, each representing single logical change.

Benefit:

  • Security fix can be cherry-picked independently
  • Easier to revert specific changes
  • Clear audit trail

Pattern 4: Autosquash Workflow

Concept: Mark fixup commits during development for automatic squashing during rebase.

Development Workflow:

# Initial feature commit
git commit -m "Add payment processing"

# Later: Find bug in payment processing
vim payment.py
git commit --fixup abc1234  # abc1234 = payment processing commit

# Later: Another fix
vim payment.py
git commit --fixup abc1234

# Even later: Add feature B
git commit -m "Add invoice generation"

Current History:

abc1234 Add payment processing
def5678 fixup! Add payment processing
ghi9012 fixup! Add payment processing
jkl3456 Add invoice generation

Autosquash Rebase:

git rebase -i --autosquash HEAD~4

# Git automatically generates:
pick abc1234 Add payment processing
fixup def5678 fixup! Add payment processing
fixup ghi9012 fixup! Add payment processing
pick jkl3456 Add invoice generation

Result: Fixes automatically squashed into original commits.


Rebase Configuration for Better Workflows

Configure Git’s rebase behavior to streamline your workflow and reduce friction:

Essential Rebase Settings

# Enable autosquash by default
git config --global rebase.autosquash true

# Auto-stash changes before rebase, restore after
git config --global rebase.autoStash true

# Use --rebase-merges by default (preserves merge commits)
git config --global rebase.rebaseMerges true

# Update commit dates to current time when rebasing
git config --global rebase.updateRefs true

Recommended Rebase Configuration

For Interactive Workflow:

# Show abbreviated commit hashes in rebase todo list
git config --global rebase.abbreviateCommands false  # Show full commands (pick, squash, etc.)

# Customize instruction format
git config --global rebase.instructionFormat "[%an - %ar] %s"
# Results in: [Alice - 2 days ago] Fix authentication bug

For Conflict Handling:

# Automatically apply fixup commits during conflicts
git config --global rebase.autosquash true

# Enable --rebase-merges to preserve merge commits
git config --global pull.rebase merges

Verifying Rebase Settings

# Check all rebase-related configuration
git config --global --get-regexp rebase

# Example output:
# rebase.autosquash true
# rebase.autostash true
# rebase.instructionformat [%an - %ar] %s

Workflow with Auto-Configuration

With these settings, your rebase workflow becomes:

# Before configuration:
git stash
git rebase -i --autosquash HEAD~4
git stash pop

# After configuration:
git rebase -i HEAD~4
# Auto-stash, autosquash, and unstash happen automatically!

Conflict Resolution During Interactive Rebase

Understanding Rebase Conflicts

Why Conflicts Occur:

Interactive rebase replays commits sequentially. Each transformation may conflict with subsequent commits.

Example Scenario:

# Original commits
A: Add feature (modifies file.py lines 10-15)
B: Refactor feature (modifies file.py lines 12-20)
C: Fix bug (modifies file.py lines 14-16)

# Rebase operation: squash B into A
# New combined commit A+B modifies lines 10-20 differently
# Commit C expects old line 14 content → CONFLICT

Conflict Resolution Workflow

Step 1: Conflict Detection

git rebase -i HEAD~3

# Git applies changes, encounters conflict:
CONFLICT (content): Merge conflict in src/file.py
error: could not apply abc1234... Fix bug
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: git rebase --skip
Or abort: git rebase --abort.

Step 2: Examine Conflict

git status
# both modified:   src/file.py

vim src/file.py
# Shows conflict markers:
<<<<<<< HEAD (current rebased state)
def process(data):
    return validate_v2(data)  # New combined version
=======
def process(data):
    return validate_v1(data)  # Original C commit expected
>>>>>>> abc1234 (Fix bug)

Step 3: Resolve Conflict

# Edit file to resolve
vim src/file.py
# Choose correct resolution

# Stage resolution
git add src/file.py

# Continue rebase
git rebase --continue

Step 4: Iterate

# May encounter more conflicts
# Repeat resolution process until complete

Conflict Resolution Strategies

Strategy 1: Accept Rebased Version

# Keep changes from combined commits
git checkout --ours src/file.py
git add src/file.py
git rebase --continue

Strategy 2: Accept Original Commit

# Keep changes from commit being replayed
git checkout --theirs src/file.py
git add src/file.py
git rebase --continue

Strategy 3: Manual Merge

# Combine both changes intelligently
vim src/file.py
# Manual editing
git add src/file.py
git rebase --continue

Strategy 4: Skip Problematic Commit

# If commit no longer needed
git rebase --skip

Strategy 5: Abort and Reconsider

# If conflicts too complex
git rebase --abort

# Try different approach:
# - Smaller squashes
# - Different commit order
# - Split commits first

Minimizing Rebase Conflicts

Best Practice 1: Frequent Rebasing

# Rebase feature branch regularly
git fetch origin
git rebase origin/main

# Smaller diffs = fewer conflicts

Best Practice 2: Logical Commit Organization

# Group related changes in same commit
# Reduces interdependencies between commits

Best Practice 3: Test After Each Major Operation

pick abc1234 Refactor module A
exec npm test
squash def5678 Continue refactoring
exec npm test

💡 Useful Aliases

git config --global alias.rbi 'rebase -i'
git config --global alias.rbc 'rebase --continue'
git config --global alias.rba 'rebase --abort'

These aliases streamline interactive rebase workflows, especially during conflict resolution where you may need to continue or abort multiple times.


Advanced Techniques and Edge Cases

Technique 1: Rebase with --onto

Purpose: Change the base of commit sequence without including intermediate commits.

Scenario: Move feature branch from old-main to new-main.

# Current state:
old-main:  A - B - C
                   \
feature:            D - E - F

new-main:  A - B - G - H

# Goal: Rebase feature onto new-main
git rebase --onto new-main old-main feature

# Result:
new-main:  A - B - G - H
                       \
feature:                D' - E' - F'

Interactive Variant:

git rebase -i --onto new-main old-main feature
# Opens editor for commit manipulation during transplant

Technique 2: Preserving Merge Commits

Problem: Standard rebase linearizes history, losing merge structure.

Solution: --rebase-merges (formerly --preserve-merges)

# Feature branch with internal merges:
A - B - C - M - E
    |       |
    D ------+

# Standard rebase flattens:
A - B - C' - D' - M' - E'

# With --rebase-merges:
git rebase -i --rebase-merges main

# Preserves merge structure:
A - B - C' - M' - E'
    |        |
    D' ------+

Use Case: Complex feature branches with meaningful internal integration points.


Technique 3: Rebase with Exec for Continuous Validation

Pattern: Ensure every commit in history passes validation.

git rebase -i HEAD~10 --exec "npm test"

# Equivalent TODO list:
pick abc1234 Commit 1
exec npm test
pick def5678 Commit 2
exec npm test
pick ghi9012 Commit 3
exec npm test
# ... etc

Benefit: Guarantees bisectable history—every commit is valid.


Technique 4: Interactive Rebase with Custom Editor Commands

Advanced TODO List Manipulation:

# Set custom editor with pre-populated commands
git config sequence.editor "vim"

# Create custom script for common patterns
#!/bin/bash
# rebase-script.sh
echo "pick $1" > /tmp/rebase-todo
echo "exec npm test" >> /tmp/rebase-todo

Recovery and Safety Protocols

Recovery Technique 1: Abort Mid-Rebase

Simple Abort:

# At any point during rebase
git rebase --abort

# Returns to pre-rebase state
# No changes applied

Use Case: Conflicts too complex; want to restart with different strategy.


Recovery Technique 2: Using Reflog

Scenario: Completed rebase, but result is wrong.

# View reflog
git reflog

# Output:
abc1234 HEAD@{0}: rebase -i (finish): returning to refs/heads/feature
def5678 HEAD@{1}: rebase -i (squash): Commit message
ghi9012 HEAD@{2}: rebase -i (start): checkout main
jkl3456 HEAD@{3}: commit: Original feature work

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

# Original commits restored

Recovery Technique 3: ORIG_HEAD Reference

Immediate Post-Rebase Recovery:

# Git automatically sets ORIG_HEAD before rebase
git reset --hard ORIG_HEAD

# Faster than searching reflog

Limitation: ORIG_HEAD only preserved until next operation that sets it.


Safety Practice: Backup Branch Before Rebase

Recommended Workflow:

# Create backup
git branch backup-feature

# Perform rebase
git rebase -i HEAD~10

# If successful, delete backup
git branch -D backup-feature

# If problems, restore from backup
git reset --hard backup-feature

Performance Considerations

Large Rebase Operations

Performance Characteristics:

  • Small rebases (< 10 commits): Milliseconds
  • Medium rebases (10-50 commits): Seconds
  • Large rebases (50-200 commits): Minutes
  • Very large rebases (200+ commits): Tens of minutes

Optimization Strategies:

Strategy 1: Incremental Rebasing

# Instead of:
git rebase -i HEAD~100

# Do:
git rebase -i HEAD~25
git rebase -i HEAD~25
git rebase -i HEAD~25
git rebase -i HEAD~25

Strategy 2: Limit Scope

# Only rebase necessary commits
git rebase -i <specific-base-commit>

Conflict Resolution Performance

Time Complexity: O(n × m)

  • n = number of commits
  • m = average conflict complexity per commit

Worst Case: Rebasing 50 commits where 40 have conflicts can take hours.

Mitigation: Abort and use merge instead if conflicts exceed 5-10 commits.


Integration with Team Workflows

Pre-PR Interactive Rebase Checklist

Before Creating Pull Request:

# 1. Update with latest main
git fetch origin
git rebase origin/main

# 2. Clean up commit history
git rebase -i origin/main

# 3. Verify tests pass for each commit
git rebase -i --exec "npm test" origin/main

# 4. Force push to feature branch
git push --force-with-lease origin feature-branch

# 5. Create pull request

Team Guidelines for Interactive Rebase

Safe Rebase Zones:

  • ✅ Personal feature branches (before PR)
  • ✅ Local commits not yet pushed
  • ✅ Feature branches with single developer

Dangerous Rebase Zones:

  • ❌ Main/develop branches
  • ❌ Shared feature branches (multiple developers)
  • ❌ Released branches
  • ❌ Any commits others have based work on

Summary: Interactive Rebase Mastery

Core Capabilities:

  1. Commit Combination: Squash and fixup for clean history
  2. Commit Reordering: Logical narrative over chronological
  3. Commit Splitting: One commit → multiple focused commits
  4. Message Refinement: Improve documentation before sharing
  5. Selective Deletion: Remove unwanted commits
  6. Automated Validation: Exec commands for quality gates

Strategic Application:

  • Use before pull requests for clean, reviewable commits
  • Apply to feature branches before main integration
  • Leverage autosquash for iterative development
  • Combine with exec for ensuring bisectable history

Safety Protocols:

  • Always create backup branches
  • Understand reflog recovery
  • Use --force-with-lease not --force
  • Abort early if conflicts become overwhelming

Technical Excellence: Interactive rebase transforms raw development history into polished, maintainable project documentation. Master this tool to craft commit sequences that tell clear stories, enabling efficient code review, debugging, and project understanding.


Perfect your Git history Explore Advanced Rewriting Techniques