Git Reset
Git reset moves your branch pointer and optionally your staging area and working directory to a different commit, and its three modes control exactly how much gets rewound.
The Three Modes of Reset
Every git reset operation starts by moving the current branch pointer (HEAD) to the commit you specify. What differs between modes is how far that rewind reaches beyond the pointer itself: --soft only moves HEAD, --mixed also resets the staging area, and --hard resets the staging area and the working directory files too. Understanding this gradient is the key to using reset safely instead of accidentally destroying work.
Soft Reset — Keep Everything Staged
git reset --soft <commit> moves HEAD back but leaves all the changes from the undone commits sitting in the staging area, ready to be recommitted. This is useful for squashing several small commits into one before pushing.
Squash three commits with soft reset
$ git log --oneline -4
9f2a301 (HEAD -> main) Fix typo
7b8c910 Add missing test
3d4e502 Implement login form
ab1c003 Base scaffold
$ git reset --soft ab1c003
$ git status
On branch main
Changes to be committed:
new file: src/login/LoginForm.jsx
new file: src/login/LoginForm.test.js
$ git commit -m "Implement login form with tests"
[main e5f8a12] Implement login form with tests
2 files changed, 88 insertions(+)Mixed Reset — Unstage but Keep Files
This is the default mode if you omit a flag entirely. It moves HEAD and clears the staging area, but leaves your working directory files exactly as they were, now showing as unstaged modifications. It's the middle ground for when you staged too much, or too early.
Unstage everything back to a known commit
$ git reset 3d4e502
Unstaged changes after reset:
M src/login/LoginForm.jsx
$ git status
On branch main
Changes not staged for commit:
modified: src/login/LoginForm.jsxHard Reset — Discard Everything
git reset --hard <commit> moves HEAD, clears the staging area, and overwrites your working directory files to match that commit exactly. Any uncommitted work, staged or not, is gone with no confirmation prompt. This is the mode that actually deletes data from your working copy, so it deserves real caution.
Hard reset discards uncommitted work
$ git status
On branch main
Changes not staged for commit:
modified: src/config/settings.json
$ git reset --hard HEAD
HEAD is now at 9f2a301 Fix typo
$ git status
On branch main
nothing to commit, working tree clean- git reset --soft <commit> — rewind HEAD, keep changes staged
- git reset --mixed <commit> (or just git reset) — rewind HEAD, unstage, keep files
- git reset --hard <commit> — rewind HEAD, discard staged and unstaged changes
- git reset HEAD <file> — unstage a single file without touching commits
- git reflog — your safety net for finding commits after a reset went wrong
When to Reach for Something Else Instead
A practical rule of thumb: soft and mixed resets are nearly always recoverable and safe to experiment with on local work, while hard reset should be treated like a delete key — pause, double check git status and git diff first, and make sure nothing you care about is only living in the working directory.
Exercise: Git Reset
What does `git reset --soft HEAD~1` do with the last commit's changes?