Chapter 11: Rewriting History
Introduction to History Rewriting
Git allows you to modify commit history, which can be powerful for cleaning up your work before sharing it with others. However, rewriting history should be done carefully, especially on shared branches.
When to Rewrite History
Safe scenarios (private branches): - Clean up messy commit history before merging - Fix commit messages with typos or unclear descriptions - Combine related commits into logical units - Remove sensitive information accidentally committed - Reorganize commits for better logical flow
Dangerous scenarios (shared branches): - Never rewrite history on public branches (main, develop) - Avoid rewriting commits that others have based work on - Don’t rewrite history on collaborative feature branches
The Golden Rule
Never rewrite history that has been pushed and shared with others unless you have explicit agreement from all collaborators.
Interactive Rebasing
Interactive rebase is the most powerful tool for rewriting history. It allows you to modify, reorder, combine, and delete commits.
Starting Interactive Rebase
# Rebase last 3 commits
git rebase -i HEAD~3
# Rebase from specific commit
git rebase -i abc1234
# Rebase from branch point
git rebase -i main
Interactive Rebase Commands
When you start interactive rebase, Git opens an editor with commands:
pick f7f3f6d Add feature A
pick 310154e Fix typo in feature A
pick a5f4a0d Add feature B
# Rebase abc1234..def5678 onto abc1234 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
Common Rebase Operations
1. Squashing Commits
Combine multiple related commits into one:
# Original commits:
pick f7f3f6d Add user login form
pick 310154e Fix login form validation
pick a5f4a0d Add login form styling
# Change to:
pick f7f3f6d Add user login form
squash 310154e Fix login form validation
squash a5f4a0d Add login form styling
# Result: Single commit with combined changes
2. Reordering Commits
Change the order of commits:
# Original order:
pick f7f3f6d Add feature A
pick 310154e Add feature B
pick a5f4a0d Fix feature A
# Reorder to:
pick f7f3f6d Add feature A
pick a5f4a0d Fix feature A
pick 310154e Add feature B
3. Editing Commits
Stop at a commit to make changes:
# Mark commit for editing:
edit f7f3f6d Add user authentication
pick 310154e Add password validation
# Git stops at the commit, allowing you to:
# Make changes to files
git add modified-file.js
git commit --amend
# Continue rebase
git rebase --continue
4. Dropping Commits
Remove commits entirely:
# Remove unwanted commit:
pick f7f3f6d Add feature A
drop 310154e Add debug logging # This commit will be removed
pick a5f4a0d Add feature B
5. Rewording Commit Messages
Change commit messages:
# Change commit message:
reword f7f3f6d Add user authentication # Will prompt for new message
pick 310154e Add password validation
Amending Commits
Amending the Last Commit
# Change the last commit message
git commit --amend -m "New commit message"
# Add files to the last commit
git add forgotten-file.txt
git commit --amend --no-edit
# Amend with new message and files
git add new-file.txt
git commit --amend -m "Updated commit message"
Squashing Commits
Manual Squashing with Reset
# Reset to 3 commits ago (keeping changes)
git reset --soft HEAD~3
# Create new commit with all changes
git commit -m "Combined commit message"
Squashing During Merge
# Squash merge (combines all commits into one)
git checkout main
git merge --squash feature-branch
git commit -m "Add complete feature X"
Cherry-Picking
Cherry-picking allows you to apply specific commits from one branch to another.
Basic Cherry-Pick
# Apply specific commit to current branch
git cherry-pick abc1234
# Cherry-pick multiple commits
git cherry-pick abc1234 def5678 ghi9012
# Cherry-pick range of commits
git cherry-pick abc1234..def5678
Cherry-Pick Options
# Cherry-pick without committing (stage changes only)
git cherry-pick --no-commit abc1234
# Cherry-pick and edit commit message
git cherry-pick --edit abc1234
# Cherry-pick and sign off
git cherry-pick --signoff abc1234
# Continue after resolving conflicts
git cherry-pick --continue
# Abort cherry-pick
git cherry-pick --abort
Cherry-Pick Use Cases
Hotfix to Multiple Branches
# Fix applied to main branch
git checkout main
git commit -m "Fix critical security issue"
# Apply same fix to release branch
git checkout release/v1.2
git cherry-pick main
# Apply to another release branch
git checkout release/v1.1
git cherry-pick main
Selective Feature Porting
# Pick specific improvements from feature branch
git checkout main
git cherry-pick feature-branch~2 # Pick specific commit
git cherry-pick feature-branch~1 # Pick another commit
Handling Conflicts During Rebase
Conflict Resolution Process
# Start rebase
git rebase -i HEAD~3
# If conflicts occur:
# CONFLICT (content): Merge conflict in file.txt
# error: could not apply abc1234... commit message
# 1. View conflicted files
git status
# 2. Resolve conflicts manually
vim file.txt # Edit and resolve conflicts
# 3. Stage resolved files
git add file.txt
# 4. Continue rebase
git rebase --continue
# Or abort if needed
git rebase --abort
Rebase Conflict Strategies
# Use merge strategy during rebase
git rebase -X ours main # Prefer current branch
git rebase -X theirs main # Prefer target branch
# Skip problematic commit
git rebase --skip
# Edit commit during conflict
git rebase --edit-todo
Advanced History Rewriting
Filter-Branch (Legacy)
# Remove file from entire history (use with caution)
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch secrets.txt' \
--prune-empty --tag-name-filter cat -- --all
Git Filter-Repo (Modern Alternative)
# Install git-filter-repo
pip install git-filter-repo
# Remove file from history
git filter-repo --path secrets.txt --invert-paths
# Remove directory from history
git filter-repo --path sensitive-dir/ --invert-paths
# Change author information
git filter-repo --mailmap mailmap.txt
BFG Repo-Cleaner
# Install BFG
# Download from: https://rtyley.github.io/bfg-repo-cleaner/
# Remove large files
java -jar bfg.jar --strip-blobs-bigger-than 100M my-repo.git
# Remove sensitive data
java -jar bfg.jar --delete-files passwords.txt my-repo.git
# Clean up after BFG
cd my-repo.git
git reflog expire --expire=now --all && git gc --prune=now --aggressive
Best Practices for History Rewriting
Before Rewriting
Backup your work: Create backup branches
git branch backup-before-rebase
Ensure clean working directory:
git status # Should show "working tree clean"
Communicate with team: Inform others about planned changes
During Rewriting
- Work on feature branches: Never rewrite main/develop directly
- Test after rewriting: Ensure code still works
- Review changes carefully: Double-check what you’re changing
After Rewriting
Test thoroughly: Run all tests
Review history: Ensure changes are correct
git log --oneline --graph
Force push carefully: Use
--force-with-lease
git push --force-with-lease origin feature-branch
Recovery from Mistakes
Using Reflog
# View reflog to find lost commits
git reflog
# Recover lost commit
git checkout abc1234 # From reflog
git branch recovery-branch # Create branch to save it
# Reset to previous state
git reset --hard HEAD@{2} # From reflog entry
Recovering from Bad Rebase
# Find the commit before rebase started
git reflog
# Find entry like: abc1234 HEAD@{5}: rebase -i (start)
# Reset to before rebase
git reset --hard HEAD@{5}
# Or create recovery branch
git branch recovery-branch HEAD@{5}
Exercises
Exercise 1: Interactive Rebase Practice
- Create a repository with messy commit history
- Use interactive rebase to clean it up:
- Squash related commits
- Reword unclear messages
- Reorder commits logically
- Compare before and after history
Exercise 2: Cherry-Pick Scenarios
- Create multiple branches with different features
- Practice cherry-picking specific commits between branches
- Handle conflicts during cherry-picking
- Use cherry-pick for hotfix scenarios
Exercise 3: Amending Commits
- Practice amending commit messages
- Add forgotten files to commits
- Change author information
- Understand when amending is appropriate
Exercise 4: Recovery Practice
- Intentionally mess up history with bad rebase
- Use reflog to understand what happened
- Recover lost commits
- Reset to previous state
Common Pitfalls and Solutions
Pitfall 1: Rewriting Public History
# Problem: Rewrote history on shared branch
# Solution: Communicate with team and coordinate reset
# For team members:
git fetch origin
git reset --hard origin/main
Pitfall 2: Lost Commits During Rebase
# Problem: Commits disappeared during rebase
# Solution: Use reflog to recover
git reflog
git branch recovery HEAD@{n} # Where n is the reflog entry
Pitfall 3: Conflicts During Interactive Rebase
# Problem: Complex conflicts during rebase
# Solution: Abort and try different approach
git rebase --abort
# Try smaller chunks or different strategy
Summary
History rewriting is a powerful Git feature that enables:
- Clean commit history: Organize commits logically
- Professional presentation: Clean up before sharing
- Error correction: Fix mistakes in commit messages or content
- Selective changes: Apply specific commits across branches
Key tools covered: - Interactive rebase for comprehensive history editing - Commit amending for quick fixes - Cherry-picking for selective commit application - Recovery techniques for when things go wrong
Remember the golden rule: Never rewrite shared history without team coordination. Use these tools responsibly to maintain clean, professional commit histories while preserving collaboration integrity.
The next chapter will explore advanced Git commands that complement these history rewriting techniques for comprehensive repository management.