StackKit

How to Undo Mistakes in Git: reset, revert, restore and reflog

By · · 4 min read

Git rarely loses work permanently, but its undo commands overlap and the names are confusing. This guide sorts them by situation: undoing changes in your files, undoing a commit that is already shared, rewriting local history, and recovering after a mistake.

The command output shown was captured from a throwaway repository with three commits (one, two, three).

First, find out what state you are in

Before undoing anything, look:

git status
git log --oneline

Most undo commands act on one of three places: the working tree (your files), the index (staged changes), or the commit history. Choosing the right command starts with knowing which one holds the change you want gone.

Discard changes in a file: git restore

git restore (Git 2.23 and newer) replaces a file in your working tree with the committed version.

echo x >> f          # edit the file
git restore f        # throw the edit away

This is destructive: uncommitted edits are not recoverable afterwards, because Git never recorded them. To unstage a file but keep your edits, use:

git restore --staged f

Undo a shared commit: git revert

git revert creates a new commit that applies the opposite change. History is not rewritten, so it is safe for commits that others may already have pulled.

git revert --no-edit HEAD
git log --oneline
f8a367c Revert "three"
726c197 three
f7b0f95 two
20187e1 one

The original commit stays in the history, and the new one cancels its effect. Use revert on main or any branch that other people use.

Move the branch back: git reset

git reset moves the current branch pointer to another commit. It rewrites local history, so use it on commits you have not pushed. It has three common modes:

Mode Branch moves Index (staged) Working tree
--soft yes kept kept
--mixed (default) yes reset kept
--hard yes reset reset
  • git reset --soft HEAD~1 undoes the last commit but leaves its changes staged, ready to recommit with a better message or combined with other changes.
  • git reset HEAD~1 (mixed) undoes the commit and unstages the changes, but your files keep the edits.
  • git reset --hard HEAD~1 undoes the commit and discards the changes in your working tree.

The --hard mode is the one that can destroy uncommitted work, so check git status before using it.

Recover "lost" commits: git reflog

Even after a hard reset, the commits still exist in the object database for a while. The reflog is a log of where HEAD has pointed:

git reflog
f7b0f95 HEAD@{0}: reset: moving to HEAD
f7b0f95 HEAD@{1}: reset: moving to HEAD~2
f8a367c HEAD@{2}: revert: Revert "three"
726c197 HEAD@{3}: commit: three
f7b0f95 HEAD@{4}: commit: two
20187e1 HEAD@{5}: commit (initial): one

Find the entry from before the mistake, then move back to it:

git reset --hard 'HEAD@{3}'     # back to the state at commit "three"

(Quote the expression so your shell does not interpret the braces.) Alternatively, create a branch at the old commit to keep it safely: git branch rescue 726c197.

The reflog is local to your clone and entries expire (by default after 90 days for reachable commits and 30 days for unreachable ones). It cannot recover changes that were never committed or staged.

Fix the last commit message or contents: git commit --amend

If the only problem is the most recent commit, amend it:

git add forgotten-file
git commit --amend

Amending rewrites that commit, so avoid it after pushing unless you are sure nobody else has pulled it.

Which command should I use?

  • Edited a file and want the old version back: git restore <file>
  • Staged the wrong file: git restore --staged <file>
  • A pushed commit was wrong: git revert <commit>
  • An unpushed commit was wrong, keep the changes: git reset --soft HEAD~1
  • An unpushed commit was wrong, discard everything: git reset --hard HEAD~1
  • I reset too far or lost a branch: git reflog, then git reset --hard to the entry
  • Only the last message or contents need fixing: git commit --amend

Common mistakes

  • Running git reset --hard with uncommitted work you wanted.
  • Force-pushing rewritten history to a shared branch. If you must, prefer git push --force-with-lease, which fails if someone else has pushed in the meantime.
  • Reverting a merge commit without specifying the parent (git revert -m 1 <merge>).
  • Assuming git checkout . is the only way. It still works, but git restore says what it does.

Summary

Use restore for files, revert for shared history, reset for local history, and reflog as the safety net. Commit early and often: anything committed is almost always recoverable, and anything uncommitted may not be.