Undoing Changes — reset, revert, restore
The Safety Net Mindset
One of the most liberating things about Git is that mistakes are almost always recoverable. This is not an accident — Git was designed with recovery in mind. The object database is append-only: objects are added but never removed (until explicit garbage collection). Even when you think you have lost a commit, Git almost certainly still has it.
But "almost always recoverable" is not the same as "always recoverable." There are a handful of operations that genuinely destroy data. This lesson teaches you the full spectrum: from completely safe undo operations to ones that require caution. By the end, you will know which tool to reach for in any situation, and you will understand the risks of each.
The Undo Decision Tree
Before diving into individual commands, here is a mental model for choosing the right undo operation:
git restore — Discarding Changes in the Working Directory
git restore is the safe, focused command for discarding changes. It was introduced in Git 2.23 alongside git switch to replace the confusing overloaded behaviors of git checkout.
Discard Unstaged Changes in a File
This replaces the working directory version of taskr.sh with the version from the last commit (HEAD). Your changes are permanently discarded — they do not go to the staging area or anywhere else. There is no undo for this operation.
Restore from a Specific Commit
By default, git restore uses HEAD (the last commit) as the source. You can restore from any commit:
This is useful for recovering a specific file version without affecting the rest of your working directory.
git restore --staged — Unstaging Files
If you have staged changes that you do not want in the next commit, unstage them:
This removes README.md from the staging area but leaves the changes in the working directory. Run git status and you will see:
The staging area has been cleaned up; the working directory still has your README changes.
git reset — Rewinding History
git reset is more powerful and more dangerous than git restore. It moves the HEAD pointer (and the current branch pointer) to a different commit. Depending on the mode, it also affects the staging area and working directory.
There are three modes: --soft, --mixed, and --hard.
Understanding the Three Modes
Think of these three modes as controlling how far the reset "cascades down" through the three areas.
git reset --soft — Undo the Commit, Keep Everything Staged
After this:
HEADand the branch now point to2a3b4c5- The changes from
c3d4e5fare in the staging area (as if you had staged them but not committed) - The working directory is unchanged
Use --soft when: you want to rewrite the commit message, combine this commit with the next one, or split this commit into multiple smaller commits.
git reset --mixed — Undo the Commit and Unstage, Keep Working Directory
This is the default mode when you run git reset without a flag.
After this:
HEADand the branch move back one commit- The changes from the undone commit are in the working directory (unstaged)
- The staging area is cleared
Use --mixed when: you want to recommit differently but need to re-review and re-stage the changes. It is the safest reset mode because you never lose any work — everything is still in your working directory.
git reset --hard — Undo Everything
After this:
HEADand the branch move back one commit- The staging area is cleared
- The working directory is reset to the commit's state
The changes from the undone commit are permanently discarded. They are gone from your working directory. This is the most dangerous Git command for data loss.
Use --hard when: you are absolutely certain you want to discard all changes and return to a specific earlier state.
HEAD~N Notation
HEAD~1 means "the commit one before HEAD." More generally:
git revert — Safe Undo for Shared History
git reset rewrites history — it changes which commit HEAD points to. This is safe on local branches that have not been shared. But once you have pushed commits to a shared remote, resetting and force-pushing is dangerous because it conflicts with your teammates' local copies.
git revert is the safe alternative. Instead of moving the branch pointer back, it creates a new commit that applies the inverse of the specified commit. The original commit remains in history; a new "undo" commit is added on top.
Git opens your editor with a default commit message:
Save and close. Git creates the revert commit:
The history is intact. The revert commit cleanly undoes the changes of 2a3b4c5. This is push-safe: your teammates can pull this without any conflicts in history.
reset vs revert: When to Use Which
| Situation | Use |
|-----------|-----|
| Undo on a local branch, not yet pushed | git reset (any mode) |
| Undo a specific commit in shared history | git revert |
| Undo multiple commits in shared history | git revert (one per commit, or range) |
| Temporarily go back to inspect old code | git checkout <hash> (detached HEAD) |
git clean — Removing Untracked Files
git restore and git reset operate on tracked files. Untracked files are not affected by them. To remove untracked files, use git clean.
Always run git clean -n before git clean -f. The dry run shows exactly what will be deleted. Unlike most Git operations, git clean on untracked files is not recoverable — these files were never in Git, so they cannot be restored from Git's history.
git stash — Save Work in Progress
git stash is the "pause button" for your working directory. It lets you save your current state — all modified tracked files and staged changes — set the working directory back to a clean state (matching HEAD), and then restore your changes later.
The classic use case: you are halfway through a feature when an urgent bug report comes in. You are not ready to commit the feature work. You stash it, fix the bug, commit the fix, then pop the stash to resume where you left off.
Basic Stash Operations
Output of git stash list:
Stashes are numbered stash@{0} (most recent) through stash@{N} (oldest). The {0} stash is always the most recent.
Applying Stashes
apply leaves the stash in the list (useful if you want to apply it to multiple branches). pop removes it after applying.
Inspecting Stashes
Removing Stashes
Creating a Branch from a Stash
If significant time has passed since you stashed and the base branch has changed considerably, applying the stash to your current branch might cause conflicts. An alternative: create a new branch from the commit where you stashed, apply the stash there, and work from that branch:
This creates and switches to a new branch based on the commit where you ran git stash, and applies the stash on top. If there are conflicts, they will be much smaller.
Stash and Staged Files
By default, git stash stashes everything together, including staged files. To stash only unstaged changes while leaving the index (staging area) intact:
git reflog — The Ultimate Safety Net
git reflog is the command that has saved countless developers from disaster. It records every time HEAD moves — every checkout, commit, reset, merge, rebase — for the last 90 days (by default).
Even after a git reset --hard that seemed to delete commits, those commits still exist in Git's object database. git reflog lets you find them.
Using reflog to Recover Lost Commits
Scenario: you just ran git reset --hard HEAD~3 and immediately realized those three commits were important.
The reflog shows you were on c3d4e5f (with the commits intact) before the reset. To recover:
Reading the reflog
Each entry in the reflog has the format:
HEAD@{0}is the current stateHEAD@{1}is one step back in HEAD history- Actions include:
commit,checkout,reset,merge,rebase,pull
You can also view the reflog for a specific branch:
Reflog is Local
The reflog is stored in .git/logs/ and is specific to your local repository. It is not transferred when you push or pull. If you clone a repository or lose your .git/ directory entirely, reflog cannot save you. This is why regular backups and pushes to a remote are still important.
Practical Reference: Which Command to Use?
Here is a consolidated reference. Bookmark this.
Practical Exercises
Exercise 1: Practice git restore
Exercise 2: Practice the Three reset Modes
Exercise 3: reflog Recovery
Exercise 4: Master git stash
Challenge: Build a Recovery Scenario
- Make 5 commits with meaningful changes
- Run
git reset --hard HEAD~3— "accidentally" delete 3 commits - Using only
git reflog, find the lost commits - Recover them completely — all 5 commits should be in your history
Summary
git restore <file>discards unstaged working directory changes permanently. Use with care.git restore --staged <file>removes a file from the staging area while keeping the working directory changes.git resetmoves the branch pointer.--softkeeps staged changes intact;--mixed(default) unstages but keeps working dir;--harddiscards everything including working dir changes.git revertis the safe option for undoing commits that have been shared/pushed — it creates a new inverse commit rather than rewriting history.git cleanremoves untracked files. Always run-n(dry run) before-f(force delete).git stashsaves your work in progress to a temporary stack, cleans your working directory, and lets you restore later withgit stash pop.git reflogrecords every movement of HEAD for 90 days and can recover commits even after a hard reset.
The next lesson covers remote repositories — connecting your local repository to GitHub or another remote, pushing and pulling changes, and understanding how tracking branches work.