Git
Intermediate 14 min read Git

Undoing Changes & Recovery

Git almost never loses committed work if you know the tools: reset moves branch pointers (soft/mixed/hard), revert creates an inverse commit that is safe for shared branches, restore and stash handle uncommitted changes, and reflog records every pointer move for recovery.

resetrevertrestorestashreflogdetached HEADrecovery
Introduction

What is this?

Undoing in Git has two axes: uncommitted vs committed, and local vs shared. git restore discards or unstages working-tree changes; git reset moves a branch pointer while optionally unstaging or discarding; git revert adds a new commit that undoes another (safe on shared branches); git stash shelves uncommitted work; git reflog records every HEAD movement for recovery.

Motivation

Why do you need it?

  • Committed work is almost always recoverable — reflog and fsck keep snapshots of moved pointers.
  • git revert is safe on shared branches because it adds a commit instead of rewriting history.
  • git stash lets you switch context without losing or committing work in progress.
  • Understanding reset's three modes gives you precise control over staged and committed state.
Think about it

Mental model

Imagine

A word processor's undo stack

Think of

Git's reflog

Undo steps you back through every edit. Git goes further: reflog keeps a log of every place HEAD has been, so even deep mistakes are reversible.

Mechanics

How it works

  • git reset --soft HEAD~1 moves the branch pointer back but keeps working tree and index intact.
  • git reset (mixed, the default) also unstages changes, leaving them in the working directory.
  • git reset --hard additionally discards working-tree changes — permanent unless you have the commit hash from reflog.
  • git revert <commit> computes the inverse patch and creates a new commit, preserving history.
  • git reflog lists every HEAD movement; you can reset to an old reflog entry to resurrect lost commits.

Reset modes vs revert on a commit history.

Mistake commit on main
git revert
Safe for shared branches
git reset --hard
git reflog
Reference

Key commands

git restore <file>

Discard uncommitted changes in a file, restoring the last committed version.

git restore --staged <file>

Unstage a file, keeping the working-directory changes.

git reset --soft HEAD~1

Move the branch back one commit, keeping changes staged.

git reset --hard HEAD~1

Move the branch back and discard working-tree changes.

git revert <commit>

Create a new commit that undoes the given commit.

git stash push -m 'wip'

Shelve uncommitted changes with a label.

git reflog

List every HEAD movement for recovery.

Hands-on

Examples

Undo a mistaken commit before pushing

Undo a mistaken commit before pushing
# You accidentally committed a file with a typo:
git reset --soft HEAD~1 # uncommit, keep everything staged
git restore --staged app.js # unstage the file
# fix the typo
git add app.js
git commit -m "Fix typo in app.js"
# On a shared branch, use revert instead of reset:
git revert HEAD # adds a commit that undoes the last one

reset --soft uncommits without touching the working tree, letting you restage and recommit cleanly. On branches others depend on, revert is the safe choice because it adds a commit instead of rewriting the history they may have cloned.

Real world

In production

  • git revert is the standard way to roll back a bad commit on main without rewriting shared history.
  • git stash is used to pause work when an urgent fix or hotfix interrupts the current task.
  • Reset before pushing cleans up local-only mistakes without affecting anyone else.
  • Reflog recovery rescues work lost to a botched rebase or reset --hard.
Guidelines

Best practices

  • Never reset --hard on a shared branch — use revert there.
  • Prefer revert for anything already pushed; reset is for local-only mistakes.
  • Commit early and often so you always have something to revert or stash.
  • If you must rewrite pushed history, use --force-with-lease and coordinate with the team.
  • Check git status before every destructive command to confirm what will be discarded.
Watch out

Common mistakes

  • Running reset --hard with uncommitted work you wanted to keep.
  • Rewriting shared history with reset/rebase instead of using revert.
  • Forgetting that a stashed change still exists — then losing it in a clean-up.
  • Panicking after a reset --hard and not checking reflog, where the commit still exists.
Root cause analysis

Common issues

Error
You are not currently on a branch
Root cause

HEAD is detached because you checked out a raw commit.

Fix

Create a branch to keep the work with git switch -c rescue-branch, or return with git switch main.

Error
CONFLICT while applying stash
Root cause

Your working tree changed since the stash was created.

Fix

Resolve the conflict like a merge, then git stash drop once it is safely applied.

Error
Changes 'lost' after git reset --hard
Root cause

The branch pointer moved, orphaning the commit.

Fix

Find it in git reflog (or git fsck --lost-found) and restore with git reset --hard <hash> or a new branch.

When things break

Debugging tips

  • git reflog is the first place to look after any 'lost' commit.
  • git stash list shows shelved changes and their labels.
  • git fsck --lost-found finds orphaned commits unreachable from any branch.
  • git status before a destructive command reveals exactly what would change.
Interviews

Interview checkpoint

Test yourself

Quick quiz

0/3
1

Which command is safe to use on a shared branch that others have pulled?

2

git reflog can help you recover commits lost with git reset --hard.

3

What does the default git reset (mixed) do?

Quick reference

Cheat sheet

Undo & recover
git restore <file>Discard working-tree changes
git restore --staged <file>Unstage only
git reset --soft HEAD~1Uncommit, keep staged
git reset --hard HEAD~1Uncommit + discard
git revert <commit>Safe inverse commit
git stashShelve WIP
git reflogRecover lost commits
Go deeper

Official documentation