# Git Reset vs Revert vs Restore: Which to Use

> Originally published on [DevToolHub](https://devtoolhub.com/git-reset-vs-revert-vs-restore/).

Use `git reset` to move your branch back, `git revert` to undo a commit with a new commit, and `git restore` to fix files without touching history. The git reset vs revert choice comes down to one question: has anyone else pulled the commit? If yes, revert it. If no, reset it.

I ran all three on Git 2.56.0 in a scratch repo, including a push to a bare remote, so every output below is real. This guide covers the three reset modes, how to undo your last commit and keep the changes, what happens when you reset a pushed commit, and how to recover after a hard reset. It is part of our [complete Git and GitHub Actions guide](https://devtoolhub.com/git-github-actions-guide/).

![Decision flow for undoing a commit, picking reset, revert or restore](https://i0.wp.com/devtoolhub.com/wp-content/uploads/2026/10/2026-10-27-git-reset-vs-revert-vs-restore-diagram.webp)

## What Is the Difference Between Git Reset vs Revert vs Restore?

`git reset` moves the current branch to another commit. `git revert` adds a new commit that cancels an older one, so history stays intact. `git restore` copies file contents from a commit or the index into your working tree or index, and never moves the branch.

| Command | What it changes | History | Safe on pushed commits |
|---|---|---|---|
| `git reset` | Branch pointer, plus index and files depending on the mode | Rewritten | No |
| `git revert` | Adds a new commit | Kept | Yes |
| `git restore` | Files in the working tree or index | Untouched | Not applicable, it touches no commits |

The test repo has three commits, already pushed to a bare remote:

```text
d026baa change app to v3, add notes
1350c0e change app to v2
7974ef4 add app v1
```

The last commit changes `app.txt` to `v3` and adds `notes.txt`. Every example below undoes that commit in a different way.

## How Git Reset Works: Soft, Mixed and Hard

A reset has three modes, and they differ in what they do with your changes. `--soft` moves the branch and leaves the changes staged. `--mixed`, the default, leaves them in your files but unstaged. `--hard` throws them away. According to the [git reset docs](https://git-scm.com/docs/git-reset), Git also sets `ORIG_HEAD` to the old branch tip before every reset, which matters for recovery later.

| Mode | Branch | Index (staged) | Working tree |
|---|---|---|---|
| `--soft` | Moves | Unchanged | Unchanged |
| `--mixed` (default) | Moves | Reset to match | Unchanged |
| `--hard` | Moves | Reset to match | Reset to match |

Here is `--soft HEAD~1`. Both changes stay staged, ready to recommit:

```bash
git reset --soft HEAD~1
git status -sb
```

```text
## main
M  app.txt
A  notes.txt
```

Now `--mixed`, which is what a bare `git reset HEAD~1` runs. The edit to `app.txt` stays in the file, but nothing is staged:

```text
Unstaged changes after reset:
M	app.txt
## main
 M app.txt
?? notes.txt
```

Look at the last line. `notes.txt` was a new file in the undone commit, so after a mixed reset it shows up as untracked (`??`). Git has not deleted it, and `git add notes.txt` puts it back under version control.

⚠️ Note: `git reset --hard` discards every uncommitted change to tracked files across the whole repository, not just one file. Run `git status` first.

```bash
git reset --hard HEAD~1
```

```text
HEAD is now at 1350c0e change app to v2
```

`app.txt` went back to `v2` and `notes.txt` was gone. An untracked file I had left in the folder survived, because a hard reset only touches files Git tracks.

## Git Reset vs Revert for Your Last Commit: Keeping Your Changes

To undo your last commit and keep the work, run `git reset --soft HEAD~1` if you want the changes staged, or `git reset HEAD~1` if you want them unstaged. Both leave your files exactly as they were. Only the commit disappears.

```bash
git reset --soft HEAD~1   # changes stay staged
git reset HEAD~1          # changes stay in files, unstaged
```

This only works for commits nobody else has pulled. The branch moves back, so a commit you already pushed no longer matches the remote.

## Git Reset vs Revert After You Push

After you push, use `git revert`. A reset on a pushed commit makes your branch diverge from the remote, and the push fails. I reset the pushed branch with `--hard HEAD~1` and tried to push:

```text
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to '../remote.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart.
```

Only a force push gets that through, and a force push rewrites history for everyone who already pulled it. Whether that is even allowed depends on your workflow, so check how [Git Flow, GitHub Flow and trunk-based development](https://devtoolhub.com/git-workflows-gitflow-githubflow-trunk-based/) treat shared branches.

Revert avoids the problem. It adds a commit instead of removing one:

```bash
git revert --no-edit HEAD
git push origin main
```

```text
bccdd1d Revert "change app to v3, add notes"
d026baa change app to v3, add notes
1350c0e change app to v2
7974ef4 add app v1
To ../remote.git
   d026baa..bccdd1d  main -> main
```

The push worked as a plain fast-forward. `app.txt` was back to `v2` and `notes.txt` was gone, but the original commit and the revert both stay in the log. That record is the point, because anyone who pulled `d026baa` can see exactly what undid it.

Revert also works on a commit that is not the latest. `git revert 1350c0e` reverted `change app to v2` from further back in the log and added a new commit on top.

## How Git Restore Fixes Files Without Moving HEAD

`git restore` changes files, not commits. By default it overwrites your working tree copy. With `--staged` it only touches the index. With `--source` it pulls the file from any commit. The [git restore docs](https://git-scm.com/docs/git-restore) describe the working tree as the default location when you pass neither `--staged` nor `--worktree`.

⚠️ Note: `git restore <file>` throws away your unstaged edits to that file, and Git does not ask first. It exited with code 0 and printed nothing.

```bash
echo "oops" >> app.txt
git restore app.txt        # edit gone, file is back to v2
```

To unstage a file but keep your edit:

```bash
git add app.txt
git restore --staged app.txt
git status -sb
```

```text
## main
 M app.txt
```

The older `git reset app.txt` gave the same status in my test. Use whichever you remember, but `restore --staged` says what it does.

To pull one file from an old commit without moving the branch:

```bash
git restore --source=7974ef4 app.txt
cat app.txt                # v1
```

The file now shows as modified (` M app.txt`). To undo that, run `git restore --source=HEAD --staged --worktree app.txt`.

## Can You Recover After a Git Hard Reset?

You can recover committed work, but not uncommitted edits. A hard reset only moves the branch, so the old commit still exists. `git reset --hard ORIG_HEAD` jumps straight back:

```bash
git reset --hard ORIG_HEAD
```

```text
HEAD is now at d026baa change app to v3, add notes
```

`git reflog` lists every earlier position of `HEAD`, so you can also reset to `HEAD@{1}`. In my test the reflog showed `d026baa HEAD@{1}: commit: change app to v3, add notes` right after the reset. For more recovery steps, see our [Git troubleshooting and recovery guide](https://devtoolhub.com/git-troubleshooting-recovery-expert/).

Uncommitted work is different. I appended a line to `app.txt`, ran `git reset --hard`, and the line vanished. The newest reflog entry was just `reset: moving to HEAD`, because Git never recorded that edit. So commit or stash before you reset.

If you want a reset that refuses to lose local edits, use `--keep`. With a local change in `app.txt`, `git reset --keep HEAD~1` stopped with `error: Entry 'app.txt' not uptodate. Cannot merge.` instead of overwriting the edit.

## Reverting Merge Commits and Resolving Conflicts

A merge commit has two parents, so `git revert` needs to know which side to keep. Without `-m`, Git stops:

```text
error: commit 33e0fc4264dac85dea5f1897db2b5e31e9478c20 is a merge but no -m option was given.
fatal: revert failed
```

Pass `-m 1` to keep the first parent, which is the branch you merged into. The [git revert docs](https://git-scm.com/docs/git-revert) say parent numbers start at 1.

```bash
git revert --no-edit -m 1 HEAD
```

In my test that removed the merged feature's file (`feat.txt`) and kept the work from `main`.

Reverts can also conflict. I reverted an older commit that created `c.txt` after a later commit had changed the file:

```text
CONFLICT (modify/delete): c.txt deleted in parent of a26f5ed (add c.txt line A) and modified in HEAD.
error: could not revert a26f5ed... add c.txt line A
```

`git status` showed `UD c.txt`. Fix the file and run `git revert --continue`, or back out with `git revert --abort`, which restored the clean branch. For the usual conflict markers, see our [merge conflict guide](https://devtoolhub.com/how-to-handle-merge-conflicts-in-git/).

## Git Reset vs Revert vs Restore: Which to Use?

Pick by situation, not by habit. Keep this table next to your [Git cheat sheet](https://devtoolhub.com/git-cheat-sheet/):

| You want to | Use |
|---|---|
| Undo your last local commit, keep the work staged | `git reset --soft HEAD~1` |
| Undo your last local commit, keep the work unstaged | `git reset HEAD~1` |
| Undo your last local commit and discard the work | `git reset --hard HEAD~1` |
| Undo a commit you already pushed | `git revert <sha>` |
| Undo a merge you already pushed | `git revert -m 1 <merge-sha>` |
| Unstage a file | `git restore --staged <file>` |
| Discard edits to a file | `git restore <file>` |
| Get an old version of one file | `git restore --source=<sha> <file>` |

## Frequently Asked Questions

**Q: What is the difference between git reset and git revert?**
A: `git reset` moves your branch to an earlier commit and rewrites history. `git revert` adds a new commit that cancels an earlier one and keeps history intact. Reset suits local commits nobody has pulled, and revert suits anything pushed.

**Q: Does git reset delete commits?**
A: Not right away. The commits drop off your branch, but they stay reachable through `git reflog` and `ORIG_HEAD`. In my test, `git reset --hard ORIG_HEAD` brought the commit straight back.

**Q: What does git reset HEAD~1 do?**
A: It moves your branch back one commit and unstages that commit's changes. Your files stay as they were, and new files from the commit become untracked.

**Q: Can I revert a commit that is not the latest?**
A: Yes. `git revert <sha>` works on any commit and adds a new commit on top. It can conflict if later commits touched the same lines.

**Q: How do I undo a git reset?**
A: Run `git reset --hard ORIG_HEAD` right after the reset, or find the old commit in `git reflog` and reset to it. This only recovers committed work.

**Quick Summary:**
- `git reset` moves your branch, `git revert` adds a commit that cancels one, and `git restore` fixes files without moving anything
- Reset a commit only if nobody has pulled it. Resetting a pushed commit made my push fail with `non-fast-forward`
- `--soft` keeps changes staged, `--mixed` keeps them unstaged, and `--hard` discards them
- `git reset --hard ORIG_HEAD` recovers committed work, but uncommitted edits are gone for good
- Reverting a merge needs `-m 1`, and `git revert --abort` backs out of a conflict
- `git restore <file>` discards unstaged edits without asking

Before your next undo, settle the git reset vs revert question first: run `git log --oneline -3` and check whether the commit is on the remote. If it is, use `git revert`.
