backend / git / 04_reset_revert_checkout.md

reset, revert, checkout, restore

6 interview angles 5 min read source

reset, revert, checkout, restore

Three (now four) commands that all “undo” something but operate at different levels: working tree, staging area, branch pointer, history.

The mental model

A git operation can affect three places:

[ working tree ]  ←  what your editor sees
       ↓ git add
[ staging area / index ]  ←  what's staged for next commit
       ↓ git commit
[ committed history (HEAD pointer) ]

Each undo command operates at a different combination of these.

git reset — moves the branch pointer

git reset <commit> moves the current branch’s HEAD to <commit>. The flag controls what happens to the working tree and index.

Flag HEAD Index Working tree Use
--soft reset unchanged unchanged “uncommit but keep everything staged”
--mixed (default) reset reset to HEAD unchanged “uncommit and unstage, keep file changes”
--hard reset reset to HEAD reset to HEAD “throw it all away”
git reset --soft HEAD~       # undo last commit, keep changes staged
git reset HEAD~              # undo last commit, keep changes in working dir but unstaged (--mixed default)
git reset --hard HEAD~       # undo last commit AND throw away the changes
git reset --hard origin/main # make local match remote main exactly (destructive)

reset --hard is irreversible from working state, but the dropped commits stay in reflog for ~90 days. See 05_reflog_recovery.md.

git revert — make a new commit that undoes an old one

git revert abc1234           # creates a new commit that inverts abc1234's changes
git revert HEAD              # revert the most recent commit
git revert -n abc1234        # apply the inverse but don't commit yet

History stays intact. The “undo” is a new commit on top.

Before: A───B───C───D
git revert C
After:  A───B───C───D───!C    (!C is a new commit reversing C)

Always safe on shared branches because nothing is rewritten.

revert vs reset — when to use which

revert reset
Rewrites history? no yes
Safe on pushed branches? yes no (unless you own the branch)
Result new commit reversing the change branch pointer moves; commits “disappear” (recoverable from reflog)
Use for undoing something already on main/shared cleaning up local commits before pushing

Production rule: if it’s been pushed to a shared branch, revert. If it’s only local, reset.

git checkout — the overloaded command

Historically checkout did two unrelated things:

  1. Switch branches: git checkout branch-name.
  2. Restore files from a commit: git checkout HEAD -- file.py.

This confused everyone, so git split it:

Old New (git 2.23+)
git checkout <branch> git switch <branch>
git checkout -b <new> git switch -c <new>
git checkout -- <file> git restore <file>
git checkout <commit> -- <file> git restore --source=<commit> <file>

The old checkout syntax still works. Modern teams default to switch + restore for clarity.

git restore — modern file undo

git restore file.py                    # discard working-tree changes (default --worktree)
git restore --staged file.py           # unstage but keep working-tree changes
git restore --staged --worktree file.py # both: undo back to HEAD
git restore --source=HEAD~3 file.py    # bring file from 3 commits ago into working tree

Replaces the confusing git checkout -- file.py and git reset HEAD file.py.

Common scenarios

“I committed too early, want to add one more file”

git add forgotten.py
git commit --amend --no-edit       # amend last commit, keep its message

Or:

git reset --soft HEAD~             # uncommit, keep staged
git add forgotten.py
git commit -m "..."

“I committed to the wrong branch”

git log -1 --format=%H             # note the SHA of the commit
git reset --hard HEAD~             # remove it from current branch
git checkout target-branch
git cherry-pick <sha>              # apply it on the right branch

“I want to undo git add file.py

git restore --staged file.py       # modern
git reset HEAD file.py             # legacy

“I want to throw away local changes to a file”

git restore file.py                # modern
git checkout -- file.py            # legacy

“I made a mess, want to match remote main exactly”

git fetch
git reset --hard origin/main       # destroys local commits and changes — make sure you've pushed first

“I pushed a bad commit to main, need to undo”

git revert <sha>
git push                            # forward-only; safe for everyone

Don’t git reset --hard and git push --force to “fix” pushed commits — anyone who pulled them will get phantom duplicates.

git clean — remove untracked files

reset only touches tracked files. To delete untracked files (e.g. accidentally created build artifacts):

git clean -n                       # dry run — show what would be deleted
git clean -f                       # force delete untracked files
git clean -fd                      # also remove untracked directories
git clean -fdx                     # also remove .gitignored files (very destructive)

-n first, every time. -fdx will erase your node_modules, .venv, .env — make sure that’s what you want.

Common interview confusions

  • reset --hard deletes commits forever.” — they stay in reflog (~90 days). Use git reflog and git reset --hard <sha> to recover. See 05_reflog_recovery.md.
  • revert undoes a commit by removing it.” — it adds a new commit that reverses the changes. Original commit stays in history.
  • git checkout and git switch are different.”switch is a clearer alias for the branch-switching part of checkout. Same effect.
  • reset --soft HEAD does nothing.” — correct. You’re moving HEAD to where it already is. People mean reset --soft HEAD~.

Interview angle

  • reset --soft vs --mixed vs --hard?” — soft moves only HEAD (changes stay staged), mixed (default) also unstages, hard also wipes the working tree. Hard is destructive but recoverable via reflog within ~90 days.
  • “Difference between git reset and git revert?” — reset rewrites history (moves the branch pointer); revert adds a new commit that inverts the change. Reset for local cleanup, revert for shared branches.
  • “How do you undo a pushed commit safely?”git revert <sha> and push the new commit. Don’t reset + force-push pushed commits.
  • “What’s git restore and how does it relate to checkout?” — modern command for file-level undo. git restore file = git checkout -- file; git restore --staged file = git reset HEAD file. Splitting the overloaded checkout.
  • “Difference between git reset HEAD file and git rm --cached file?” — reset unstages a modified file. rm --cached removes a file from tracking entirely (next commit deletes it from the repo, but keeps it in working tree).
  • “You force-pushed and lost commits — how do you recover?”git reflog shows your local HEAD history; git reset --hard <reflog-sha> restores. If only the remote was wiped, the local clone still has them.