Skip to main content
Git & GitHub · lesson 3 of 12
Git & GitHub beginner Lesson 3 of 12

Undoing Changes: restore, reset, and revert

See the real difference between git restore, the three reset modes, and git revert — including which ones rewrite history and which ones don't.

By SkillByExample · Published

git reset revert

Git has three different tools for “undo,” and they operate at three different levels: files, the index, and history itself. Using the wrong one either doesn’t go far enough or goes further than you wanted.

1) git restore reverts a file to its last committed content

$ # ...edit app.py, change "Hello" to "Hi"...
$ git diff --stat
 app.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git restore app.py
$ git diff --stat

No output the second time — git restore (no --staged) throws away the unstaged edit and puts the working copy back to what’s in the index (here, the last commit).

2) reset —soft undoes the commit, keeps the change staged

$ # ...re-edit app.py, commit it as "Change greeting to Hi (oops)"...
$ git reset --soft HEAD~1
$ git status -s
M  app.py

The commit is gone from history — HEAD moved back one step — but the change it introduced is still sitting in the index, staged and ready to be committed differently or dropped.

3) The default (—mixed) also unstages it

$ git reset HEAD
$ git status -s
Unstaged changes after reset:
M	app.py
 M app.py

Same commit, but now the change is only in the working directory — unstaged. This is what plain git reset (no flag) does by default: undo the commit, undo the staging, keep the file edit.

4) reset —hard discards the change entirely

$ git reset --hard HEAD
$ git status -s
HEAD is now at bd316aa Merge feature/add-tests into main

Empty status output — the working directory now matches HEAD exactly. This is the only one of the three reset modes that can lose uncommitted work, because it overwrites the working directory too.

5) Once a commit is shared, revert instead of reset

$ # ...commit "Change greeting to Hi (oops)" again, simulating it being pushed...
$ git revert --no-edit HEAD
$ git log --oneline -3
d7da0e1 Revert "Change greeting to Hi (oops)"
ccd5e66 Change greeting to Hi (oops)
bd316aa Merge feature/add-tests into main

Both the mistake and its fix stay in history. Anyone who already pulled ccd5e66 will cleanly pull d7da0e1 too — nothing was rewritten out from under them, which is why revert (not reset) is the safe choice for shared branches.

Practice

  1. Run git restore --staged <file> after staging a change (without reverting it). Confirm the change stays in your working directory but drops out of the index — the opposite half of what --soft reset does.

  2. Reproduce step 4 above but first stage the edit (git add) before running git reset --hard HEAD. Confirm staged-but-uncommitted changes are lost too, not just working-directory ones.

    Solution
    $ echo "x = 1" >> app.py && git add app.py
    $ git reset --hard HEAD
    $ git status -s

    Empty output — --hard resets both the index and the working directory to match HEAD, regardless of what was staged.

  3. Run git revert on a commit that isn’t the most recent one (git revert <hash> for an older commit) and observe that Git still applies the inverse diff correctly, creating a new commit on top rather than modifying history in place.

  4. Compare git reset --hard <hash> against git revert <hash> on the same target commit in two separate scratch clones. Confirm one rewrites git log and the other appends to it.

  5. Run git reflog right after a reset --hard (before doing anything else) and find the commit you just “lost” still listed — it’s not deleted yet, just unreachable from any branch. (Recovering it is covered in a later lesson.)

Frequently Asked Questions

What's the one-sentence difference between reset and revert?
git reset moves your branch pointer, potentially erasing commits from history; git revert adds a new commit that undoes an old one, leaving history intact. Use reset for commits nobody else has, revert for commits already pushed and shared.
What's the difference between --soft, the default (--mixed), and --hard reset?
--soft undoes the commit but leaves the change staged. The default, --mixed, undoes the commit and unstages the change but keeps it in your working directory. --hard undoes the commit and discards the change completely — it is the only one of the three that can lose work.
When would I use git restore instead of reset?
git restore operates on files, not commits — it's the modern, narrower tool for "put this one file back" (plain restore for the working directory, --staged to unstage). Reset works at the commit/branch level and can move HEAD itself.
Why did git revert create a new commit instead of deleting the bad one?
Because the bad commit had already been committed (simulating a pushed, shared commit), rewriting it with reset would change history other clones already have. Revert adds a commit that applies the inverse diff, which is safe to share.