Accidentally changed a file, staged the wrong content, or committed a mistake? Learn when to use `git restore`, `git revert`, and `git reset`—and how to recover without making things worse.
# Undoing Git Mistakes Without Panic
You just committed a change.
Then you notice the typo.
Or worse, you realize you committed the wrong file, deleted something important, or introduced a bug that wasn't there five minutes ago.
Your first thought might be:
"How do I undo this?"
Git has several ways to undo changes, but they're not interchangeable. Some affect files in your working directory. Others change what's staged. Some create new commits, while others move branch references and can make commits harder to find.
The good news? Once you understand what each command changes, undoing mistakes becomes much less intimidating.
In this article, we'll focus on three commands:
```bash
git restore
git reset
git revert
```
We'll also cover how to recover when you think you've lost a commit.
## 1. First, identify what you want to undo
Before running an undo command, ask yourself three questions:
1. Have I modified a file without staging it?
2. Have I staged changes but not committed them?
3. Have I already created a commit?
These situations require different approaches.
Remember the three areas from our earlier articles:
```text
Working Directory
|
| git add
v
Staging Area
|
| git commit
v
Repository History
```
A change can exist in one or more of these areas at the same time.
For example, you can stage a file and then edit it again. That leaves one version staged and additional changes in your working directory.
Understanding where your change lives is the first step to undoing it safely.
## 2. `git restore`: Undo file changes
Let's start with the most common situation.
You've edited `app.js`, but you haven't staged the changes. After reviewing the file, you decide you want to discard those edits and return to the version in the staging area.
Run:
```bash
git restore app.js
```
This restores the working-tree copy of `app.js` from the index—the staging area.
If the file wasn't staged, the index will normally contain the version from your current commit, so the file returns to that version.
**Warning:** This can permanently discard the unstaged changes in the affected file. Make sure you don't need them before running the command.
### What if the changes are staged?
Suppose you've already run:
```bash
git add app.js
```
Now the changes are in the staging area. To unstage them without discarding the edits from your working directory, use:
```bash
git restore --staged app.js
```
This updates the index from the current `HEAD` version of the file. Your working-tree edits remain.
Conceptually:
```text
Before:
Working Directory Staging Area
Modified Modified
After git restore --staged:
Working Directory Staging Area
Modified Original
```
The file is no longer staged, but the changes remain available for editing.
You can inspect the result with:
```bash
git status
git diff
```
### What if you want to discard both staged and unstaged changes?
First, understand that this is destructive.
If you want the file to match the current commit and discard all local changes to it, you can run:
```bash
git restore --source=HEAD --staged --worktree app.js
```
This restores both the index and working-tree version of the specified file from `HEAD`.
Use it only when you're sure you no longer need those changes. If the file contains valuable work, make a backup or save the changes elsewhere first.
### The mental model for `git restore`
Think of `git restore` as a command for restoring file content.
It can update the working directory, the staging area, or both, depending on the options you provide.
It doesn't normally move your branch to another commit or create a new commit.
## 3. `git reset`: Move the branch back
Now let's look at a different situation.
You've created a commit, but it shouldn't be part of the current branch history.
For example:
```text
A --- B --- C main
```
Suppose C contains a mistake, and you want your branch to point back to B.
That's where `git reset` comes in.
```bash
git reset --soft HEAD~1
```
This moves the current branch back by one commit but preserves the changes introduced by that commit in the staging area.
The history now looks like:
```text
A --- B main
```
The commit C is no longer the tip of `main`, but its changes are still staged, ready to be amended or recommitted.
The three main reset modes determine what happens to the index and working directory.
### Soft reset: Keep everything staged
```bash
git reset --soft HEAD~1
```
- Moves the current branch back one commit.
- Keeps the changes from the removed commit staged.
- Leaves the working directory unchanged.
Use this when you want to redo a commit message, combine commits, or adjust the contents of a recent commit.
### Mixed reset: Keep changes, but unstage them
```bash
git reset --mixed HEAD~1
```
This is also the default behavior when you run `git reset HEAD~1` without specifying a mode.
- Moves the current branch back one commit.
- Resets the staging area to the target commit.
- Keeps the changes in your working directory.
Your edits remain, but they are no longer staged.
### Hard reset: Reset the index and working directory
```bash
git reset --hard HEAD~1
```
This moves the branch back one commit and resets both the index and working directory to match the target commit.
Changes tracked by Git that differ from the target can be discarded.
**Be careful:** `--hard` can destroy uncommitted work. It can also overwrite tracked files that differ from the target commit. It isn't a general cleanup command to run casually.
Before using it, inspect your state:
```bash
git status
git log --oneline -5
```
If you're unsure whether you'll need your changes, save them in a separate commit, create a backup, or use another safe preservation method first.
## 4. `git revert`: Undo a commit without rewriting history
Now imagine you've already pushed a bad commit to a shared repository.
Other developers may have fetched it. Rewriting shared history can disrupt their work.
In this situation, `git revert` is often the safer choice.
Suppose your history looks like this:
```text
A --- B --- C main
```
Commit C introduced a bug.
Instead of removing C from the history, run:
```bash
git revert C
```
Replace `C` with the actual commit hash.
Git creates a new commit that reverses the changes introduced by C, provided the revert can be applied cleanly.
The history becomes:
```text
A --- B --- C --- D main
```
Commit D reverses C's changes.
The original commit remains in the history. Git records the correction as a new commit.
This is particularly useful for shared branches because it preserves the existing history rather than moving the branch backward.
### What if the revert has conflicts?
A revert can encounter conflicts if later changes overlap with the changes being reversed.
If that happens, Git pauses and asks you to resolve the conflicts.
After resolving the files, stage them:
```bash
git add app.js
```
Then complete the revert:
```bash
git revert --continue
```
If you decide not to continue and want to cancel an in-progress revert, use:
```bash
git revert --abort
```
Inspect `git status` to understand the current operation before proceeding.
### The mental model for `git revert`
`git revert` doesn't erase the original commit. It creates a new commit that applies the inverse of the selected commit's changes.
This makes it a good option for undoing published changes while preserving the historical record.
## 5. Which command should you use?
Here's a quick comparison.
| Situation | Command |
|---|---|
| Discard unstaged edits to a tracked file | `git restore app.js` |
| Unstage a file but keep its edits | `git restore --staged app.js` |
| Undo the latest commit but keep changes staged | `git reset --soft HEAD~1` |
| Undo the latest commit and keep changes unstaged | `git reset --mixed HEAD~1` |
| Move back and discard local tracked changes | `git reset --hard HEAD~1` |
| Undo a commit while preserving shared history | `git revert <commit>` |
The key distinction is what you're changing:
- **`restore`** changes file content in the working directory or index.
- **`reset`** moves the current branch and can also change the index and working directory.
- **`revert`** creates a new commit that reverses an earlier commit.
If you remember those three ideas, choosing the right command becomes much easier.
## 6. What if you reset to the wrong commit?
Suppose you accidentally run:
```bash
git reset --hard HEAD~2
```
You intended to move back one commit, but you moved back two—and some local changes disappeared.
Before panicking, remember that Git often retains references to recent branch positions in its reflog.
Run:
```bash
git reflog
```
You might see output similar to:
```text
e7f8g9h HEAD@{0}: reset: moving to HEAD~2
c3d4e5f HEAD@{1}: commit: Fix login validation
b2c3d4e HEAD@{2}: commit: Add error handling
```
The hashes and entries here are illustrative.
If `c3d4e5f` is the commit you want to return to, you may be able to restore your branch to it:
```bash
git reset --hard c3d4e5f
```
Do not copy the example hash; use the real hash from your own reflog.
Before performing another reset, inspect the candidate commit:
```bash
git show --stat c3d4e5f
```
A reflog records recent movements of local references, including where `HEAD` and branches pointed previously. It can help recover commits that are no longer reachable from your current branch tip.
However, it isn't a permanent backup. Reflog entries expire, and unreachable objects may eventually be pruned. Recovery of uncommitted changes that were discarded by a hard reset is not guaranteed.
The lesson is simple: if you think you've lost a commit, inspect the reflog before making more changes.
## 7. A safe way to experiment
If you're learning these commands, don't experiment on your main project.
Create a small test repository:
```bash
mkdir git-undo-playground
cd git-undo-playground
git init
```
Create a file:
```bash
echo "Version one" > notes.txt
git add notes.txt
git commit -m "Add notes"
```
Now make another change and commit it:
```bash
echo "Version two" >> notes.txt
git add notes.txt
git commit -m "Update notes"
```
Inspect the history:
```bash
git log --oneline
```
You now have two commits.
Try a soft reset:
```bash
git reset --soft HEAD~1
git status
```
Observe that the latest commit is no longer the branch tip, but its changes are staged.
You can recreate the commit:
```bash
git commit -m "Update notes"
```
Next, experiment with `git restore` and `git revert`. Because this is a disposable repository, you can learn what each command does without risking real work.
## 8. Common misconceptions
**"Reset and revert are the same."**
No. Reset moves the current branch reference and may change the index and working directory. Revert creates a new commit that reverses an earlier commit.
**"Restore deletes a commit."**
No. Restore operates on file content in the working directory or staging area. It doesn't normally move the branch reference.
**"A hard reset permanently deletes every trace of a commit."**
Not necessarily. A commit may remain recoverable through the reflog or other references for some time, although recovery isn't guaranteed indefinitely.
**"Revert is always conflict-free."**
No. Git may need your help if the changes being reversed overlap with later edits.
**"If a commit was pushed, reset is always forbidden."**
Not strictly. Rewriting history can be appropriate in some controlled situations, but it requires care and coordination. For a shared branch, revert is generally the safer default when you need to undo a published change.
## The takeaway
Git gives you multiple ways to undo mistakes because mistakes can occur at different stages of the workflow.
Remember:
1. Use `git restore` to restore file content.
2. Use `git reset` when you intentionally need to move a branch reference, understanding what happens to staged and unstaged changes.
3. Use `git revert` to reverse a commit by creating a new commit.
4. Use `git reflog` when you need to inspect where your local references pointed previously.
Above all, inspect your repository before running a destructive command:
```bash
git status
git log --oneline -5
```
A few seconds of checking can save you from a lot of unnecessary recovery work.
## What's next?
You've learned how to create commits, branch, merge, share changes, investigate history, and undo mistakes.
In the final article of this introductory series, we'll put those concepts together into a practical feature-branch workflow, from starting a feature to integrating it safely.
**Next: A Practical Git Workflow for Everyday Development.**

