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.jsx

Hard 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
Note: Never run git reset (in any mode that moves the branch pointer) on a branch that other people have already pulled from. Once teammates have based their own commits on the ones you erase, their next pull or push turns into a tangled mess of diverged history and duplicated commits.
Note: If a hard reset ever removes commits you actually needed, don't panic immediately — git reflog keeps a local record of where HEAD has pointed recently, and you can usually recover with git reset --hard <reflog-entry> as long as it hasn't been garbage collected.

When to Reach for Something Else Instead

SituationRecommended command
Undo a commit already pushed and sharedgit revert
Fix the message or contents of only the last commitgit commit --amend
Rewind local, unpushed commits before sharinggit reset (soft or mixed)
Discard local uncommitted changes entirelygit reset --hard
Clean up a messy local branch history before a PRgit rebase -i

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?