refactor: rewrite the merge-revert section

It is an advanced and difficult revert to accomplish and should probably
be done through a reset instead, which means that we're modifying
history which is dangerous and so should be handled by someone who
understands these dangers.
This commit is contained in:
Bjarke Sporring
2026-01-15 16:11:24 +01:00
parent aa24c50b45
commit 575e083f33
4 changed files with 186 additions and 282 deletions

View File

@@ -1,8 +1,8 @@
# Module 05: Git Revert - Safe Undoing
# Module 06: Git Revert - Safe Undoing
## About This Module
Welcome to Module 05, where you'll learn the **safe, team-friendly way to undo changes** in Git. Unlike destructive commands that erase history, `git revert` creates new commits that undo previous changes while preserving the complete project history.
Welcome to Module 06, where you'll learn the **safe, team-friendly way to undo changes** in Git. Unlike destructive commands that erase history, `git revert` creates new commits that undo previous changes while preserving the complete project history.
**Why revert is important:**
- ✅ Safe for shared/pushed commits
@@ -17,19 +17,18 @@ Welcome to Module 05, where you'll learn the **safe, team-friendly way to undo c
By completing this module, you will:
1. Revert regular commits safely while preserving surrounding changes
2. Revert merge commits using the `-m` flag
3. Understand merge commit parent numbering
4. Handle the re-merge problem that occurs after reverting merges
5. Revert multiple commits at once
6. Know when to use revert vs. other undo strategies
1. Revert commits safely while preserving surrounding changes
2. Revert old commits in the middle of history
3. Understand how revert preserves commits before and after the target
4. Revert multiple commits at once
5. Know when to use revert vs. other undo strategies
## Prerequisites
Before starting this module, you should be comfortable with:
- Creating commits (`git commit`)
- Viewing commit history (`git log`)
- Understanding branches and merging (Module 03)
- Understanding branches (Module 03)
## Setup
@@ -41,7 +40,7 @@ Run the setup script to create the challenge environment:
This creates a `challenge/` directory with three branches demonstrating different revert scenarios:
- `regular-revert` - Basic commit reversion
- `merge-revert` - Merge commit reversion
- `middle-revert` - Reverting a commit in the middle of history
- `multi-revert` - Multiple commit reversion
## Challenge 1: Reverting a Regular Commit
@@ -120,140 +119,108 @@ main.py (initial) → multiply (good) → divide (BAD) → modulo (good) → rev
The revert commit adds a new point in history that undoes the divide changes.
## Challenge 2: Reverting a Merge Commit
## Challenge 2: Reverting a Commit in the Middle
### Scenario
Your team merged a `feature-auth` branch that added authentication functionality. After deployment, you discovered the authentication system has critical security issues. You need to revert the entire merge while the security team redesigns the feature.
You're working on improving your calculator application. Several commits were made in sequence:
1. Added input validation (good)
2. Added output formatter (BAD - has bugs!)
3. Added configuration module (good - but came after the bad commit)
**This is different from reverting a regular commit!** Merge commits have **two parents**, so you must tell Git which parent to keep.
The formatter has critical bugs and needs to be removed, but you want to keep both the validation module (added before) and the configuration module (added after).
### Understanding Merge Commit Parents
**This demonstrates an important Git principle:** Revert works on ANY commit in history, not just recent ones!
When you merge a feature branch into main:
### Understanding History Preservation
Here's what the commit history looks like:
```
feature-auth (parent 2)
C---D
/ \
A---B-----M ← Merge commit (has TWO parents)
parent 1 (main)
A (initial) → B (validation) → C (formatter BAD) → D (config)
We want to remove THIS
```
The merge commit `M` has:
- **Parent 1**: The branch you merged INTO (main)
- **Parent 2**: The branch you merged FROM (feature-auth)
When you revert commit C:
When reverting a merge, you must specify which parent to keep using the `-m` flag:
- `-m 1` means "keep parent 1" (main) - **Most common**
- `-m 2` means "keep parent 2" (feature-auth) - Rare
```
A (initial) → B (validation) → C (formatter BAD) → D (config) → E (revert C)
Removes formatter, keeps validation & config
```
**In practice:** You almost always use `-m 1` to keep the main branch and undo the feature branch changes.
**Key insight:** The revert creates a NEW commit (E) that undoes commit C, but leaves B and D completely intact!
### Your Task
1. **Switch to the merge-revert branch:**
1. **Switch to the middle-revert branch:**
```pwsh
git switch merge-revert
git switch middle-revert
```
2. **View the commit history with the graph:**
2. **View the commit history:**
```pwsh
git log --oneline --graph
git log --oneline
```
Look for the merge commit message: "Merge feature-auth branch"
- Note the commit hash
- Write it down or copy it
You should see three commits after the initial:
- "Add input validation module"
- "Add broken formatter - needs to be reverted!"
- "Add configuration module"
3. **Revert the merge commit using `-m 1`** (replace `<merge-commit-hash>` with actual hash):
3. **Find the broken formatter commit:**
- Look for the message: "Add broken formatter - needs to be reverted!"
- Note the commit hash (the 7-character code)
- Write it down
4. **Revert that middle commit** (replace `<commit-hash>` with actual hash):
```pwsh
git revert -m 1 <merge-commit-hash>
git revert <commit-hash>
```
**What `-m 1` means:**
- `-m 1` tells Git to keep parent 1 (the main branch side)
- This undoes all changes from the feature-auth branch
- Creates a new "revert merge" commit
4. **Visual Studio Code will open** with the revert commit message:
5. **Visual Studio Code will open** with the revert commit message:
- The default message is fine
- Close the editor window to accept it
- Git will create the revert commit
5. **Check the result:**
6. **Check the result:**
```pwsh
# View files - auth.py should be gone
# View files - formatter.py should be gone
ls
# You should see calculator.py but NOT auth.py
# You should see validation.py and config.py but NOT formatter.py
# Verify calculator.py no longer imports auth
cat calculator.py
# Should NOT see "from auth import" anywhere
# View the history
git log --oneline
# You should see the new revert commit at the top
```
### What Happens Without -m?
### What to Observe
If you try to revert a merge commit without the `-m` flag:
After reverting, notice:
```bash
git revert <merge-commit-hash>
# Error: commit <hash> is a merge but no -m option was given
```pwsh
# Check which files exist
ls
# You should see:
# - calculator.py (from initial commit)
# - validation.py (from commit BEFORE bad one) ✅
# - config.py (from commit AFTER bad one) ✅
# - formatter.py is GONE (reverted) ❌
```
Git doesn't know which parent you want to keep, so it refuses to proceed.
**Important:** Git successfully removed the bad formatter while keeping everything else!
### The Re-Merge Problem
### Why This Matters
**Important gotcha:** After reverting a merge, you **cannot simply re-merge** the same branch!
This scenario demonstrates revert's power in real-world situations:
- You discover a bug in code committed days or weeks ago
- Many commits have been made since then
- You can't just delete the old commit (that would break history)
- Revert lets you surgically remove just the bad commit
Here's why:
```
Initial merge:
A---B---M (merged feature-auth)
All changes from feature-auth are now in main
After revert:
A---B---M---R (reverted merge)
Changes removed, but Git remembers they were merged
Attempting to re-merge:
A---B---M---R---M2 (try to merge feature-auth again)
Git thinks: "I already merged these commits,
nothing new to add!" (Empty merge)
```
**Solutions if you need to re-merge:**
1. **Revert the revert** (recommended):
```bash
git revert <revert-commit-hash>
```
This brings back all the feature-auth changes.
2. **Cherry-pick new commits** from the feature branch:
```bash
git cherry-pick <new-commits>
```
3. **Merge with --no-ff** and resolve conflicts manually (advanced).
### When to Revert Merges
Revert merge commits when:
- ✅ Feature causes production issues
- ✅ Need to temporarily remove a feature
- ✅ Discovered critical bugs after merging
- ✅ Security issues require immediate rollback
Don't revert merges when:
- ❌ You just need to fix a small bug (fix it with a new commit instead)
- ❌ You plan to re-merge the same branch soon (use reset if local, or revert-the-revert later)
**Revert is your "undo button" for shared history!**
## Challenge 3: Reverting Multiple Commits
@@ -311,7 +278,7 @@ Two separate commits added broken mathematical functions (`square_root` and `log
**Reverting a range of commits:**
```bash
```pwsh
# Revert commits from A to B (inclusive)
git revert A^..B
@@ -321,7 +288,7 @@ git revert HEAD~3..HEAD
**Reverting without auto-commit:**
```bash
```pwsh
# Stage revert changes without committing
git revert --no-commit <commit-hash>
@@ -353,8 +320,7 @@ The script checks that:
- ✅ Revert commits were created (not destructive deletion)
- ✅ Bad code is removed
- ✅ Good code before and after is preserved
- ✅ Merge commits still exist in history
- ✅ Proper use of `-m` flag for merge reverts
- ✅ Revert works on commits in the middle of history
## Command Reference
@@ -371,14 +337,17 @@ git revert HEAD
git revert HEAD~1
```
### Merge Commit Revert
### Reverting Old Commits
```pwsh
# Revert a merge commit (keep parent 1)
git revert -m 1 <merge-commit-hash>
# Revert a specific commit from any point in history
git revert <commit-hash>
# Revert a merge commit (keep parent 2) - rare
git revert -m 2 <merge-commit-hash>
# Revert a commit from 5 commits ago
git revert HEAD~5
# View what a commit changed before reverting
git show <commit-hash>
```
### Multiple Commits
@@ -450,7 +419,7 @@ Consider alternatives when:
Sometimes reverting causes conflicts if subsequent changes touched the same code:
```bash
```pwsh
# Start revert
git revert <commit-hash>
@@ -463,47 +432,24 @@ git revert <commit-hash>
1. Open conflicted files and fix conflicts (look for `<<<<<<<` markers)
2. Stage resolved files:
```bash
```pwsh
git add <resolved-files>
```
3. Continue the revert:
```bash
```pwsh
git revert --continue
```
Or abort if you change your mind:
```bash
```pwsh
git revert --abort
```
## Common Mistakes
### 1. Forgetting -m for Merge Commits
### 1. Using Reset on Pushed Commits
```bash
# ❌ Wrong - will fail
git revert <merge-commit>
# ✅ Correct
git revert -m 1 <merge-commit>
```
### 2. Trying to Re-Merge After Revert
```bash
# After reverting a merge:
git revert -m 1 <merge-commit>
# ❌ This won't work as expected
git merge feature-branch # Empty merge!
# ✅ Do this instead
git revert <the-revert-commit> # Revert the revert
```
### 3. Using Reset on Pushed Commits
```bash
```pwsh
# ❌ NEVER do this with pushed commits
git reset --hard HEAD~3
@@ -511,11 +457,11 @@ git reset --hard HEAD~3
git revert HEAD~3..HEAD
```
### 4. Reverting Commits in Wrong Order
### 2. Reverting Commits in Wrong Order
When reverting multiple related commits, revert from newest to oldest:
```bash
```pwsh
# If you have: A → B → C (and C depends on B)
# ✅ Correct order
@@ -530,7 +476,7 @@ git revert C
## Best Practices
1. **Write clear revert messages:**
```bash
```pwsh
git revert <hash> -m "Revert authentication - security issue #1234"
```
@@ -561,15 +507,6 @@ git revert C
## Troubleshooting
### "Commit is a merge but no -m option was given"
**Problem:** Trying to revert a merge commit without `-m`.
**Solution:**
```bash
git revert -m 1 <merge-commit-hash>
```
### "Empty Revert / No Changes"
**Problem:** Revert doesn't seem to do anything.
@@ -580,7 +517,7 @@ git revert -m 1 <merge-commit-hash>
- Wrong commit hash
**Solution:**
```bash
```pwsh
# Check what the commit actually changed
git show <commit-hash>
@@ -601,24 +538,22 @@ git log --grep="Revert"
Or consider fixing forward with a new commit instead of reverting.
### "Can't Re-Merge After Reverting Merge"
### "Reverting Old Commit Breaks Something"
**Problem:** After reverting a merge, re-merging the branch brings no changes.
**Problem:** After reverting an old commit, something else stops working.
**Solution:** Revert the revert commit:
```bash
# Find the revert commit
git log --oneline
**Why:** The old commit might have been a dependency for later commits.
# Revert the revert (brings changes back)
git revert <revert-commit-hash>
```
**Solution:**
1. Check what changed: `git diff HEAD~1 HEAD`
2. Either fix the issue with a new commit, or
3. Revert the revert if needed: `git revert <revert-commit-hash>`
## Advanced: Revert Internals
Understanding what revert does under the hood:
```bash
```pwsh
# Revert creates a new commit with inverse changes
git revert <commit-hash>
@@ -645,9 +580,8 @@ You've learned:
- ✅ `git revert` creates new commits that undo previous changes
- ✅ Revert is safe for shared/pushed commits
- ✅ Merge commits require `-m 1` or `-m 2` flag
- ✅ Parent 1 = branch merged into, Parent 2 = branch merged from
- ✅ Can't simply re-merge after reverting a merge
- ✅ Revert works on any commit in history, even old ones
- ✅ Commits before and after the reverted commit are preserved
- ✅ Multiple commits can be reverted in one command
- ✅ Revert preserves complete history for audit trails