Skip to main content
ANVISoftware Solutions
Lesson 13 of 15Intermediate18 min

Undoing and Recovering

By the end of this lesson

Recover from common mistakes, including work you thought was lost.

Almost everything in Git is recoverable, with one significant exception: work you never committed. Once a change exists in a commit, Git keeps it, and it can be found again even when every branch and pointer has stopped referring to it.

That asymmetry is the single most useful thing in this lesson. Committing is what makes work safe, which is an argument for committing early and often, even on messy work-in-progress you intend to tidy up later.

Match the situation to the tool. Choosing the right one starts with asking how far the change has travelled:

I changed a file and want the last committed version back
git restore. It overwrites your working copy from the commit, and your edit is gone for good because it was never recorded.
I staged something I did not mean to stage
git restore --staged. It takes the change out of the draft commit and leaves your file untouched.
I committed something wrong, and it has been pushed
git revert. It creates a new commit that undoes the change, leaving the original in history. Nobody else's history is disturbed.
I committed something wrong and it is still only on my machine
git reset. It moves your branch pointer back, as though the commit was never made. Only safe because nobody else has it.
My commits have vanished and I do not know where they went
git reflog. It lists where HEAD has been, including positions nothing points at any more, so you can get back to them.
Undoing changes that are not committed yet
Shell
# Throw away your edits to one file, back to the last commit
git restore src/InvoiceCalculator.cs

# Unstage a file but keep the edits you made to it
git restore --staged src/InvoiceCalculator.cs

# Both at once: unstage AND discard the edits
git restore --staged --worktree src/InvoiceCalculator.cs

# See what you would be throwing away, before you do it
git diff src/InvoiceCalculator.cs
  • git restore with no options rewrites your working file from the last commit. There is no undo for this, because the change you are discarding was never stored anywhere.
  • --staged operates on the staging area only. Your file on disk keeps every character you typed.
  • --staged --worktree does both, which is the full "pretend I never touched this file" option.
  • Run git diff first. It takes two seconds and it is the difference between discarding what you meant to and discarding an hour of work.

revert and reset are often confused, and the consequences of confusing them are unequal:

 git revertgit reset
What it doesAdds a new commit that undoes an earlier oneMoves the branch pointer to an earlier commit
Existing historyUnchanged — the original commit staysThe commits after the target are no longer on your branch
Safe on a pushed branch?YesNo — it rewrites what others may already have
Leaves a record of the undoYes, as a commit with a messageNo, as though it never happened
Typical useA released change turned out to be wrongTidying local commits before sharing them
Undoing commits
Shell
# Safe anywhere: a new commit that reverses an old one
git revert 9f3c2a1

# Undo the last commit but keep its changes staged
git reset --soft HEAD~1

# Undo the last commit and keep its changes as unstaged edits
git reset --mixed HEAD~1

# Undo the last commit and DISCARD its changes entirely
git reset --hard HEAD~1
  • git revert asks for a commit and produces a new one undoing it. Because it adds rather than rewrites, it is the correct choice for anything already pushed.
  • HEAD~1 means "one commit before where I am". HEAD~3 means three before.
  • --soft moves the pointer and leaves everything staged, which is how you redo a commit message or combine two commits into one.
  • --mixed is the default. The pointer moves and your changes become unstaged edits, ready to be restaged differently.
  • --hard moves the pointer and resets your working directory to match. Any uncommitted change in your working directory is destroyed permanently — not stashed, not in the reflog, gone. The reflog can recover the commits you reset past, because they were committed. It cannot recover uncommitted work, because nothing ever recorded it.

Every time HEAD moves — a commit, a switch, a merge, a rebase, a reset — Git writes a line in a local log recording where it moved from and where it moved to. That log is the reflog, and it is the reason a deleted branch or a bad reset is usually a ten-second problem rather than a lost afternoon. Most people never hear it mentioned until the day they need it.

Commits do not disappear when nothing points at them. They become unreachable through branches, which makes them invisible to git log, and they sit in the repository until Git eventually cleans up. The reflog is how you find them in the meantime, and the window is generous — typically at least thirty days for reachable entries and ninety for the rest.

The reflog is local to your clone. It records where your HEAD has been, not anyone else's, and it is not pushed or fetched. So it will not help with a colleague's mistake, and their reflog cannot help with yours.

Getting back work that appears to be gone
Shell
# Where has HEAD been? Newest first.
git reflog

# Typical output:
#   8c1d4e2 HEAD@{0}: reset: moving to HEAD~2
#   a71f903 HEAD@{1}: commit: Add retry to the payment gateway call
#   4b0e5df HEAD@{2}: commit: Extract PaymentGateway from OrderService

# Inspect a commit before you act on it
git show a71f903

# Safest recovery: a new branch pointing at the lost work
git switch -c recovered-payment-retry a71f903

# Or move the current branch back to where it was
git reset --hard HEAD@{1}

# Deleted a branch by mistake? Its tip is in the reflog too
git reflog | grep -i "payment-retry"
  • Each reflog line gives a commit identifier, a position reference, and what caused the move. The reason column is what lets you find the moment things went wrong.
  • Inspect before you act. git show on the identifier confirms it is the work you want and costs nothing.
  • Creating a new branch is the recovery with no downside: your current branch is untouched, and the lost commits now have a pointer, so they are safe again.
  • The final command searches the reflog for a branch name, which is how you recover the tip of a branch you deleted before it was merged.

Summary

  • Anything committed can be recovered; uncommitted work cannot, which is the argument for committing often
  • git restore discards working changes or unstages them; discarded working changes are gone permanently
  • revert adds a commit that undoes another and is safe on shared branches; reset moves the pointer and is only safe locally
  • reset --hard destroys uncommitted work permanently, and the reflog cannot bring it back
  • git reflog records everywhere HEAD has been, so lost commits and deleted branches are usually recoverable within weeks

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Try it yourself

Lose a commit and get it back

In a practice repository with at least three commits, note the output of git log --oneline. Run git reset --hard HEAD~2 and confirm the commits are gone from the log.

Now recover them using the reflog, without using any information you wrote down beforehand.

Show solution

git reflog shows the position HEAD held before the reset. Creating a branch at that identifier brings the commits back under a pointer, and they are visible in git log again.

Doing this deliberately once, on a repository you do not care about, is worth an hour of reading. When it happens for real you will already know the recovery takes three commands.

Note what the exercise required: the commits had to exist. Had those changes been uncommitted when you ran reset --hard, nothing in this lesson would bring them back.

Shell
git log --oneline
git reset --hard HEAD~2
git log --oneline          # two commits missing

git reflog                 # find the identifier before the reset
git switch -c recovered <identifier-from-reflog>
git log --oneline          # they are back

Think about it

revert or reset?

A change that was merged and released three days ago is causing incorrect invoices. Four people have pulled since.

Which of revert and reset is correct here, and what specifically goes wrong if you choose the other one?

Show solution

revert. It adds a commit that undoes the change, so everyone receives the fix by pulling normally and no existing history changes.

reset would move the branch pointer backwards, removing the commit from your copy. Pushing that requires a force push, which rewrites the shared branch. The four people who already pulled still have the original commit, and their next push can reinstate it or wipe out whatever came after — either way, someone's work is at risk.

There is also a record argument. A revert commit says publicly that this change was withdrawn and why. A reset pretends the change never existed, which is inaccurate and unhelpful to whoever investigates the same problem next quarter.

Knowledge check

Nothing is recorded and there is no score. The explanation appears either way.

Which of these cannot be recovered with git reflog?
A commit has been pushed and others have pulled it. It needs to be undone. What should you use?

Saved in this browser only.