The three trees
Git tracks three "trees": the HEAD commit, the index (staging area), and the working tree (your files on disk). git reset moves the branch pointer and optionally rewrites the index and working tree. The flag determines how far down the chain it cascades.
--soft: move HEAD only
git reset --soft <target> moves the branch pointer but leaves the index and working tree untouched. The result: the changes from the abandoned commits appear as staged changes, ready to be re-committed.
git reset --soft HEAD~1
# Last commit's changes are now staged
git commit -m "Better message"
--mixed: also reset the index (default)
git reset --mixed <target> moves the branch pointer and resets the index to match. The working tree is untouched, so your edits remain - they just become unstaged.
git reset HEAD~1
# Equivalent to --mixed; changes are unstaged but still in your files
This is what git reset does when you omit the flag.
--hard: reset everything
git reset --hard <target> moves the branch pointer, resets the index, and overwrites the working tree. Uncommitted changes are destroyed.
git reset --hard HEAD~1
# The last commit and any working tree changes are gone
A decision table
- Want to redo the last commit message?
--soft HEAD~1then commit again. - Want to un-stage everything?
--mixed(or justgit reset). - Want to throw away local changes and match a remote?
--hard origin/main.
Resetting a single file
git reset HEAD path/to/file # un-stage that file
git checkout -- path/to/file # discard working-tree changes
# or with newer syntax:
git restore --staged path/to/file
git restore path/to/file
Reset versus revert
reset rewrites history by moving the branch pointer. revert creates a new commit that undoes a previous commit. Use revert on shared branches; use reset on local-only work.
Recovering from a bad --hard
The reflog records every HEAD movement for ~90 days. To recover:
git reflog
# Find the SHA before the reset
git reset --hard HEAD@{1}
The mental model
Picture the three trees as nested boxes - HEAD inside index inside working tree. --soft touches the innermost box; --mixed touches two; --hard touches all three. Once that picture clicks, reset stops being scary.