Git Rewriting History

History modification: rewriting commits, undoing mistakes, recovering from errors.

Amend Patterns

# AMEND LAST COMMIT MESSAGE
git commit --amend -m "corrected message"
git commit --amend                       # Opens editor

# AMEND WITH ADDITIONAL CHANGES
git add forgotten-file.txt
git commit --amend --no-edit             # Add file, keep message

# AMEND AUTHOR
git commit --amend --author="Name <email@example.com>"

# AMEND DATE
GIT_COMMITTER_DATE="2024-01-15 12:00:00" git commit --amend --date="2024-01-15 12:00:00"

# CRITICAL: ONLY AMEND UNPUSHED COMMITS!
# Check if pushed:
git log --oneline origin/main..HEAD
# If shows the commit, it's NOT pushed - safe to amend
# If commit not shown, it's pushed - DON'T amend

# AMEND RULES
# ✓ Unpushed commits - safe to amend
# ✗ Pushed commits - creates divergent history
# ✗ Shared branch commits - breaks others' work

Reset Operations

# RESET MODES
# --soft:  move HEAD, keep staging and working tree
# --mixed: move HEAD, reset staging, keep working tree (DEFAULT)
# --hard:  move HEAD, reset staging AND working tree

# SOFT RESET (uncommit, keep changes staged)
git reset --soft HEAD~1
# Use case: "I committed too early, want to add more changes"

# MIXED RESET (uncommit, keep changes unstaged)
git reset HEAD~1                         # --mixed is default
# Use case: "I want to reorganize into different commits"

# HARD RESET (uncommit, discard changes)
git reset --hard HEAD~1
# Use case: "I want to completely undo this commit"
# WARNING: Permanently discards changes!

# RESET TO SPECIFIC COMMIT
git reset --hard abc1234                 # Reset to specific SHA
git reset --hard origin/main             # Match remote exactly

# RESET LOCAL BRANCH TO MATCH REMOTE
# This is the "nuclear option" for syncing with remote:
git fetch origin                         # Get latest remote state
git reset --hard origin/main             # Discard all local changes
# Use case: "My local branch is a mess, start fresh from remote"

# RESET BEHIND BY N COMMITS (catch up to remote)
git fetch origin
git log --oneline HEAD..origin/main      # See what remote has
git reset --hard origin/main             # Jump to remote HEAD

# UNSTAGE FILES (partial reset)
git reset HEAD file.txt                  # Unstage single file
git restore --staged file.txt            # Modern equivalent

# RESET VS REVERT
# reset: rewrites history (moves HEAD)
# revert: creates new commit that undoes changes

# SAFE PATTERNS
# Before hard reset, save state:
git branch backup-before-reset
git reset --hard HEAD~3
# If mistake: git checkout backup-before-reset

# RESET SPECIFIC PATHS
git reset HEAD~1 -- file.txt             # Reset file to previous state
git checkout HEAD~1 -- file.txt          # Same effect

# RESET REFLOG (your safety net)
git reflog                               # See all HEAD movements
git reset --hard HEAD@{2}                # Reset to 2 moves ago

Revert Patterns

# BASIC REVERT (creates new commit)
git revert abc1234                       # Revert specific commit
git revert HEAD                          # Revert last commit

# REVERT WITHOUT AUTO-COMMIT
git revert -n abc1234                    # Stage changes only
git revert --no-commit abc1234           # Same, verbose
# Useful for reverting multiple commits as one

# REVERT MULTIPLE COMMITS
git revert HEAD~3..HEAD                  # Revert last 3 commits
git revert -n HEAD~3..HEAD               # As single commit

# REVERT MERGE COMMIT
git revert -m 1 abc1234                  # Revert merge, keep main side
# -m 1: keep first parent (usually main)
# -m 2: keep second parent (merged branch)

# REVERT VS RESET
# REVERT when:
# - Commit is already pushed
# - Want to preserve history
# - Need audit trail
#
# RESET when:
# - Commit is local only
# - Want to reorganize commits
# - Cleaning up before push

# EXAMPLE: UNDO PUSHED COMMIT
git revert HEAD
git push origin main                     # Safe push

# EXAMPLE: UNDO MULTIPLE PUSHED COMMITS
git revert -n HEAD~2..HEAD               # Stage all reversions
git commit -m "Revert: undo broken changes from commits X, Y"
git push origin main

Interactive Rebase

# START INTERACTIVE REBASE
git rebase -i HEAD~5                     # Edit last 5 commits
git rebase -i main                       # Edit since divergence from main
git rebase -i --root                     # Edit all commits (careful!)

# INTERACTIVE COMMANDS
# pick   = keep commit as-is
# reword = keep commit, change message
# edit   = pause at commit for changes
# squash = combine with previous, keep messages
# fixup  = combine with previous, discard message
# drop   = remove commit entirely
# exec   = run shell command

# EXAMPLE: REWORD COMMIT
# Change pick to reword:
#   reword abc1234 Old message here
# Save, editor opens for new message

# EXAMPLE: SQUASH COMMITS
# Original:
#   pick abc1234 Add feature
#   pick def5678 Fix typo
#   pick ghi9012 More fixes
# Change to:
#   pick abc1234 Add feature
#   squash def5678 Fix typo
#   squash ghi9012 More fixes
# Result: Single commit with combined messages

# EXAMPLE: FIXUP (silent squash)
#   pick abc1234 Add feature
#   fixup def5678 typo fix
#   fixup ghi9012 another fix
# Result: Single commit, only first message kept

# EXAMPLE: REORDER COMMITS
# Move lines in editor to reorder
# Original order: A, B, C
# New order: C, A, B

# EXAMPLE: EDIT COMMIT
#   edit abc1234 Commit to change
# Save, rebase pauses. Make changes:
git add fixed-file.txt
git commit --amend
git rebase --continue

# EXAMPLE: DROP COMMIT
#   drop abc1234 Bad commit
# Or delete the line entirely

# EXAMPLE: EXEC (run command)
#   pick abc1234 Feature
#   exec make test
#   pick def5678 More work
# Rebase runs tests after first commit

# ABORT REBASE
git rebase --abort                       # Cancel, restore original

# CONTINUE REBASE
git rebase --continue                    # After resolving conflicts

# SKIP COMMIT
git rebase --skip                        # Drop problematic commit

Cherry-Pick Patterns

# BASIC CHERRY-PICK
git cherry-pick abc1234                  # Apply single commit
git cherry-pick abc1234 def5678          # Multiple commits
git cherry-pick abc1234..def5678         # Range (exclusive start)
git cherry-pick abc1234^..def5678        # Range (inclusive)

# CHERRY-PICK OPTIONS
git cherry-pick -n abc1234               # Stage only, no commit
git cherry-pick --no-commit abc1234      # Same
git cherry-pick -x abc1234              # Add "(cherry picked from...)" to message
git cherry-pick -e abc1234               # Edit message

# CHERRY-PICK MERGE COMMIT
git cherry-pick -m 1 abc1234             # From merge, keep main parent

# CONFLICT HANDLING
git cherry-pick abc1234
# CONFLICT...
# Fix conflict, then:
git add resolved-file.txt
git cherry-pick --continue
# Or abort:
git cherry-pick --abort

# COMMON WORKFLOWS

# Backport fix to release branch
git checkout release/v1.0
git cherry-pick abc1234                  # Apply fix from main

# Pull specific feature commit
git cherry-pick feature-branch~3         # Third-to-last from feature

# Create PR from specific commits
git checkout -b cherry-picked-feature
git cherry-pick abc1234 def5678 ghi9012
git push -u origin cherry-picked-feature

# CHERRY-PICK VS MERGE
# Cherry-pick: copy specific commits
# Merge: bring all branch history
# Use cherry-pick when:
# - Need specific commits only
# - Backporting fixes
# - Avoiding branch merge

Reflog and Recovery

# REFLOG BASICS
git reflog                               # All HEAD movements
git reflog show                          # Same
git reflog show HEAD                     # Same
git reflog show main                     # Branch-specific reflog

# REFLOG OUTPUT
# abc1234 HEAD@{0}: commit: Latest commit message
# def5678 HEAD@{1}: checkout: moving from feature to main
# ghi9012 HEAD@{2}: reset: moving to HEAD~1
# jkl3456 HEAD@{3}: commit: Previous commit message

# REFLOG FILTERING
git reflog --since="2 hours ago"
git reflog --until="yesterday"
git reflog -n 20                         # Last 20 entries

# RECOVERY PATTERNS

# Recover from bad reset
git reset --hard HEAD~3                  # Oops, too far!
git reflog                               # Find the commit you want
git reset --hard HEAD@{1}                # Go back to before reset

# Recover deleted branch
git branch -D feature                    # Oops, had uncommitted work!
git reflog                               # Find last commit on feature
git checkout -b feature abc1234          # Recreate branch

# Recover after rebase disaster
git rebase -i main                       # Made a mess!
git reflog                               # Find pre-rebase state
git reset --hard HEAD@{5}                # Restore

# Recover lost commit
git commit -m "important work"
git reset --hard HEAD~1                  # Oops!
git reflog                               # Find the commit
git cherry-pick abc1234                  # Recover it

# REFLOG EXPIRY
# Default: 90 days for reachable, 30 for unreachable
# Don't rely on reflog for long-term recovery

# FSCK FOR DEEPER RECOVERY
git fsck --lost-found                    # Find ALL orphaned commits
ls .git/lost-found/commit/               # View found commits
git show <sha>                           # Examine each

# STASH REFLOG
git reflog show stash                    # Stash history
# Can recover recently dropped stashes!

Rewriting Gotchas

# WRONG: Amending pushed commits
git commit -m "feature"
git push origin main
git commit --amend -m "better message"
git push origin main                     # Error: rejected!

# CORRECT: Only amend unpushed
git log --oneline origin/main..HEAD      # Check first
# If commit shown, safe to amend

# WRONG: Hard reset pushed commits
git reset --hard HEAD~3
git push origin main                     # Error: non-fast-forward

# CORRECT: Use revert for pushed commits
git revert HEAD~2..HEAD
git push origin main                     # Clean history

# WRONG: Rebase public branches
git checkout main
git rebase feature                       # DON'T!
# Rewrites main, breaks everyone

# CORRECT: Rebase private branches only
git checkout feature
git rebase main                          # OK, feature is private

# WRONG: Force push without checking
git push --force origin main
# May overwrite others' work!

# CORRECT: Use --force-with-lease
git push --force-with-lease origin main
# Fails if remote changed since fetch

# WRONG: Forgetting reflog expires
git reset --hard HEAD~5
# ... 3 months pass ...
git reflog                               # Commits gone!

# CORRECT: Backup important work
git branch backup-before-reset
# Or: git stash, git tag

# WRONG: Interactive rebase across merge boundary
git rebase -i main
# Gets confusing with merge commits

# CORRECT: Use --rebase-merges if needed
git rebase -i --rebase-merges main

# WRONG: Cherry-picking merge commits without -m
git cherry-pick abc1234                  # If abc1234 is a merge
# Error: commit is a merge but no -m option

# CORRECT: Specify parent
git cherry-pick -m 1 abc1234

Quick Reference

# AMEND (unpushed only!)
git commit --amend -m "new message"      # Change message
git commit --amend --no-edit             # Add staged files

# RESET
git reset --soft HEAD~1                  # Uncommit, keep staged
git reset HEAD~1                         # Uncommit, keep unstaged
git reset --hard HEAD~1                  # Uncommit, discard all
git reset --hard origin/main             # Match remote exactly

# REVERT (safe for pushed)
git revert abc1234                       # Create undo commit
git revert -n abc1234                    # Stage undo only

# INTERACTIVE REBASE
git rebase -i HEAD~5                     # Edit last 5
# pick/reword/edit/squash/fixup/drop

# CHERRY-PICK
git cherry-pick abc1234                  # Copy commit
git cherry-pick -n abc1234               # Stage only
git cherry-pick -m 1 abc1234             # For merges

# REFLOG (recovery)
git reflog                               # See all HEAD moves
git reset --hard HEAD@{2}                # Go back 2 moves

# SAFETY RULES
# ✓ Amend/reset unpushed commits
# ✓ Rebase private branches
# ✓ Revert pushed commits
# ✗ Amend/reset pushed commits
# ✗ Rebase public branches
# ✗ Force push without --force-with-lease

See Also