You are viewing a preview of this course. Sign in to start learning

Lesson 6: Undoing Things — Fixing Mistakes

Master Git's undo mechanisms. Learn checkout, restore, reset (soft/mixed/hard), and revert to confidently fix mistakes without fear.

Lesson 6: Undoing Things — Fixing Mistakes 🔄

Introduction

Everyone makes mistakes. The difference between a Git novice and a Git master isn't that masters don't make mistakes—it's that they know how to fix them confidently. 💪

In this lesson, you'll learn Git's powerful undo mechanisms. We'll explore the differences between checkout, restore, reset, and revert, and most importantly, when to use each one. By the end, you'll handle common panic situations like "I committed to the wrong branch!" or "I need to undo my last commit!" with ease.

🤔 Did you know? Git's ability to undo almost anything is one reason it won the version control wars. Early competitors like CVS and SVN made mistakes much harder to fix, leading to actual lost work.

Core Concepts

The Three Git States (Revisited) 📍

Before we talk about undoing, let's refresh the three areas where your files can live:

+------------------+     +------------------+     +------------------+
|  Working Tree    | --> | Staging Area     | --> |  Repository      |
|  (Your files)    |     | (Index/Stage)    |     | (.git commits)   |
+------------------+     +------------------+     +------------------+
       ^                        ^                        ^
       |                        |                        |
    restore                 restore --staged           reset/revert

Different undo commands target different areas. Understanding this is crucial to choosing the right tool.

The Undo Command Family 👨‍👩‍👧‍👦

Git has multiple undo commands because it handles different situations:

+-------------------+---------------------------+----------------------+
| Command           | What It Does              | Safe?                |
+-------------------+---------------------------+----------------------+
| git restore       | Undo working tree changes | Yes (but loses work) |
| git restore       | Unstage files             | Yes                  |
|   --staged        |                           |                      |
| git reset --soft  | Move HEAD, keep changes   | Yes                  |
| git reset --mixed | Move HEAD, unstage        | Yes                  |
| git reset --hard  | Move HEAD, discard all    | ⚠️ DESTRUCTIVE       |
| git revert        | Create new undo commit    | Yes (preserves       |
|                   |                           | history)             |
| git checkout      | Old multi-purpose command | Depends              |
+-------------------+---------------------------+----------------------+

git restore — Modern File Recovery 🆕

git restore was introduced in Git 2.23 (2019) to clarify file operations. It replaces some uses of the confusing git checkout.

Basic syntax:

# Restore working tree file (discard changes)
git restore <file>

# Unstage file (keep working tree changes)
git restore --staged <file>

# Both: unstage AND discard changes
git restore --staged --worktree <file>

💡 Tip: Think of restore as "restore this file to how it was in..." You're bringing back an old version.

git reset — Time Travel for Commits ⏰

git reset moves the branch pointer (HEAD). It has three modes that differ in what they do to your staging area and working tree:

The Three Reset Modes:

                    HEAD    Staging   Working
                    moves?  affected? affected?
+----------------+--------+---------+-----------+
| reset --soft   |  YES   |   NO    |    NO     |  Uncommit, keep staged
| reset --mixed  |  YES   |  YES    |    NO     |  Uncommit, unstage (default)
| reset --hard   |  YES   |  YES    |   YES     |  Uncommit, discard all ⚠️
+----------------+--------+---------+-----------+

🧠 Mnemonic: S-M-H = Soft-Medium-Hard = Less-More-Most destructive.

Common reset patterns:

# Undo last commit, keep changes staged
git reset --soft HEAD~1

# Undo last commit, unstage changes (most common)
git reset HEAD~1
# or
git reset --mixed HEAD~1

# Undo last commit, discard all changes ⚠️
git reset --hard HEAD~1

# Move back 3 commits
git reset --hard HEAD~3

# Reset to specific commit
git reset --hard abc123

⚠️ Warning: --hard is destructive. It throws away uncommitted work. Use with extreme caution!

git revert — Safe Public Undo 🛡️

git revert creates a new commit that undoes a previous commit. It doesn't rewrite history, making it safe for shared branches.

Before revert:
A -- B -- C -- D  (main)
              ^
              HEAD

After git revert C:
A -- B -- C -- D -- C'  (main)
                    ^
                    HEAD (new commit undoing C)

Syntax:

# Revert the last commit
git revert HEAD

# Revert a specific commit
git revert abc123

# Revert without auto-commit (review first)
git revert --no-commit abc123

💡 Key difference: reset rewrites history (use locally), revert adds history (use on shared branches).

git checkout — The Old Multi-Tool 🔧

Before git restore and git switch, git checkout did everything:

  • Switch branches
  • Restore files
  • Create branches

It's still widely used but confusing. Modern Git splits its functionality:

+---------------------------+------------------------+
| Old Command               | New Command            |
+---------------------------+------------------------+
| git checkout <branch>     | git switch <branch>    |
| git checkout -b <branch>  | git switch -c <branch> |
| git checkout -- <file>    | git restore <file>     |
+---------------------------+------------------------+

Still useful for:

# Checkout specific commit (detached HEAD)
git checkout abc123

# Checkout file from another branch
git checkout other-branch -- file.txt

Real-World Examples 🌍

Example 1: "I Made Changes I Don't Want" 🗑️

Scenario: You edited config.js but want to discard the changes.

# Check status first
$ git status
On branch main
Changes not staged for commit:
  modified:   config.js

# Restore the file to last commit state
$ git restore config.js

# Alternative (old way)
$ git checkout -- config.js

What happened:

Working Tree:     config.js (modified) → config.js (restored)
Staging Area:     (empty) → (empty)
Repository:       (unchanged)

⚠️ This discards your changes. They're gone forever!

Example 2: "I Staged the Wrong File" 📤

Scenario: You accidentally added secrets.txt to the staging area.

# You accidentally did this
$ git add secrets.txt

# Check status
$ git status
Changes to be committed:
  new file:   secrets.txt

# Unstage the file (keep your changes in working tree)
$ git restore --staged secrets.txt

# Alternative (old way)
$ git reset HEAD secrets.txt

# Verify
$ git status
Untracked files:
  secrets.txt

What happened:

Staging Area:   secrets.txt (staged) → (empty)
Working Tree:   secrets.txt (still exists with changes)

💡 The file is still there, just not staged!

Example 3: "I Committed to the Wrong Branch" 🎯

This is incredibly common. You made commits on main that should be on a feature branch.

Scenario: You committed twice on main but wanted a feature branch.

# Current state: you're on main with 2 unwanted commits
$ git log --oneline
ab12cd3 Add feature part 2
ef34gh5 Add feature part 1
901ij23 Last good commit

# Step 1: Create a new branch with these commits
$ git branch feature

# Step 2: Move main back to where it should be (2 commits back)
$ git reset --hard HEAD~2

# Step 3: Switch to the new branch
$ git switch feature

# Verify: feature has the commits, main doesn't
$ git log --oneline --all --graph
* ab12cd3 (feature) Add feature part 2
* ef34gh5 Add feature part 1
* 901ij23 (HEAD -> main) Last good commit

Visual representation:

Before:
  901ij23 -- ef34gh5 -- ab12cd3  (main, HEAD)

After git branch feature:
  901ij23 -- ef34gh5 -- ab12cd3  (main, HEAD)
                               \
                                (feature)

After git reset --hard HEAD~2:
  901ij23  (main, HEAD)
       \
        ef34gh5 -- ab12cd3  (feature)

🧠 Why it works: Branches are just pointers. Creating feature saves the commits, then reset moves main back.

Example 4: "I Need to Undo a Commit Someone Else Saw" 🔄

Scenario: You pushed a commit to a shared branch and need to undo it. Use revert (don't rewrite public history!).

# You pushed this commit
$ git log --oneline
aa11bb2 (HEAD -> main, origin/main) Broken feature
cc33dd4 Working commit

# Don't use reset! Use revert instead
$ git revert HEAD
# Git opens an editor for the revert commit message
# Default: "Revert 'Broken feature'"
# Save and close

# Check result
$ git log --oneline
ee55ff6 (HEAD -> main) Revert "Broken feature"
aa11bb2 (origin/main) Broken feature
cc33dd4 Working commit

# Push the revert
$ git push

Why revert, not reset?

Using reset (WRONG for shared branches):
main:        C -- D  (you reset, D disappears)
origin/main: C -- D  (others still have D - conflicts!)

Using revert (CORRECT):
main:        C -- D -- D'  (D' undoes D)
origin/main: C -- D  (can cleanly pull D')

💡 Rule of thumb: If you've pushed, use revert. If it's local only, use reset.

Common Recovery Patterns 🔧

Here are solutions to frequent panic situations:

"I Need to Undo My Last Commit" (Local Only)

# Keep changes, redo the commit
git reset --soft HEAD~1
# Make more changes
git add .
git commit -m "Better commit message"

"I Committed Sensitive Data"

# If NOT pushed yet
git reset HEAD~1
git add .  # Re-add files (excluding sensitive ones)
git commit -m "Fixed commit"

# If already pushed - more complex!
# Need git filter-branch or BFG Repo-Cleaner
# (Beyond scope - see Further Study)

"I Deleted a File and Committed"

# Find the commit before deletion
git log --diff-filter=D --summary | grep delete

# Restore from commit before deletion
git checkout abc123~1 -- path/to/file.txt
git commit -m "Restore deleted file"

"I Did git reset --hard and Lost My Work" 😱

Don't panic! Git keeps commits for ~30 days in the reflog.

# View reflog (history of HEAD movements)
git reflog
# Output:
ee55ff6 HEAD@{0}: reset: moving to HEAD~2
aa11bb2 HEAD@{1}: commit: My lost work!
cc33dd4 HEAD@{2}: commit: Previous commit

# Recover by resetting to the lost commit
git reset --hard HEAD@{1}
# or
git reset --hard aa11bb2

💡 The reflog is your safety net! It records every HEAD movement.

Common Mistakes ⚠️

Mistake 1: Using reset --hard on Uncommitted Work

# ❌ WRONG: You have uncommitted changes
$ git reset --hard HEAD
# All your work is GONE and unrecoverable!

# ✅ RIGHT: Commit first, or use stash
$ git stash
$ git reset --hard HEAD
$ git stash pop  # Restore your work

Mistake 2: Resetting a Shared Branch

# ❌ WRONG: Resetting a pushed branch
$ git reset --hard HEAD~3
$ git push --force  # Causes problems for teammates!

# ✅ RIGHT: Use revert for shared branches
$ git revert HEAD~2..HEAD  # Revert last 3 commits
$ git push

Mistake 3: Confusing restore and reset

# restore = files (working tree, staging)
# reset = commits (branch pointer)

# ❌ Trying to undo commits with restore (doesn't work)
$ git restore HEAD~1  # Error!

# ✅ Use reset for commits
$ git reset HEAD~1

Mistake 4: Forgetting About the Reflog

The reflog is local only and expires after ~30 days. If you need to recover something:

# Check reflog immediately
git reflog

# Recover lost commits
git cherry-pick <commit-hash>
# or
git reset --hard <commit-hash>

Decision Tree: Which Undo Command? 🌳

                  What do you want to undo?
                           |
          +----------------+----------------+
          |                                 |
    Working tree                        Commit(s)
      changes                                |
          |                    +-------------+-------------+
          |                    |                           |
     Staged or              Local only                 Pushed?
     unstaged?                 |                           |
          |              +-----+-----+                     |
    +-----+-----+        |           |                     |
    |           |    Keep changes? Discard?           Use revert
 Staged    Unstaged      |           |              (preserves history)
    |           |      reset     reset --hard
 restore     restore  --soft/mixed  (⚠️ careful!)
 --staged

Key Takeaways 🎯

  1. git restore = for files (working tree and staging)
  2. git reset = for commits (moves branch pointer)
  3. git revert = safe undo for shared branches (adds new commit)
  4. --soft keeps everything, --mixed unstages, --hard discards ⚠️
  5. If you pushed it, revert it; if it's local, reset it
  6. The reflog is your safety net - commits are recoverable for ~30 days
  7. Modern Git: Use restore and switch instead of checkout's multiple personalities

🔧 Try this: Create a test repository and practice these commands. You can't really understand undo commands until you've "broken" things safely!

Quick Reference Card 📋

+---------------------------+-----------------------------+
| Situation                 | Command                     |
+---------------------------+-----------------------------+
| Discard file changes      | git restore <file>          |
| Unstage file              | git restore --staged <file> |
| Undo last commit (local)  | git reset HEAD~1            |
| Undo last commit, discard | git reset --hard HEAD~1     |
| Undo pushed commit        | git revert HEAD             |
| Move commits to new       | git branch new-branch       |
|   branch                  | git reset --hard HEAD~N     |
| Recover lost commit       | git reflog                  |
|                           | git reset --hard HEAD@{N}   |
+---------------------------+-----------------------------+

Reset Modes Quick Reference:
--soft  = Move HEAD only (changes stay staged)
--mixed = Move HEAD, unstage (DEFAULT)
--hard  = Move HEAD, unstage, discard ⚠️

Further Study 📚

  1. Official Git Documentation - Reset Demystified: https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified Comprehensive explanation of reset's three modes with clear diagrams.

  2. Oh Shit, Git!?!: https://ohshitgit.com/ Practical solutions to common Git mistakes in plain language.

  3. Git Reflog Documentation: https://git-scm.com/docs/git-reflog Learn how Git keeps your safety net of recoverable commits.

You now have the knowledge to fix virtually any Git mistake! The key is understanding what you're undoing (files, staging, or commits) and whether it's been shared (local = reset, pushed = revert). Practice these commands in a safe test repository until they become second nature. 🚀