Git Reset: Soft, Mixed, Hard & Keep (Complete Guide for 2026)
git reset is the most misunderstood “undo” command in Git — mainly because it can affect three different things:
- The branch pointer /
HEAD(what commit you’re on) - The staging area (the index)
- Your working directory files
This guide gives you a simple mental model for those three areas, then shows the practical recipes you actually need in real work.
git reset is best for local, unpushed work.
If a commit is already pushed to a shared branch, prefer git revert.
If you already ran a bad reset or rebase and “lost” commits, use git reflog to recover.
Table of contents
1. Copy/paste recipes (the 90% cases)
Undo the last commit (keep changes staged)
# You committed too early (or want to change the message)
# Keeps everything staged (ready to re-commit)
git reset --soft HEAD~1
Undo the last commit (keep changes, but unstage them)
# Undo the commit, but put changes back into the working tree
# so you can edit and re-stage selectively
git reset HEAD~1
Undo the last commit (discard changes completely)
# DANGEROUS: deletes staged + unstaged changes in tracked files
git reset --hard HEAD~1
Unstage everything (undo “git add .”)
# Option A: classic reset (unstages everything)
git reset
# Option B (modern): recommended unstage command
git restore --staged .
Reset your branch to match origin/main (discard local commits and changes)
# Get the latest remote pointer
git fetch origin
# DANGEROUS: throws away local commits + local changes
git reset --hard origin/main
Recover after a bad reset --hard
# Find where HEAD used to point
git reflog -n 20
# Reset back to the previous commit you want (hash from reflog)
git reset --hard <commit>
git branch backup/before-reset
Then even if you mess up, you can get back by resetting to backup/before-reset.
2. The mental model: HEAD vs index vs working tree
To understand git reset, you need to keep three snapshots in mind:
| Area | What it is | Common command |
|---|---|---|
| HEAD | The commit your branch currently points to | git log -1 |
| Index (staging) | The “next commit” snapshot you are building | git add, git diff --staged |
| Working tree | Your actual files on disk | git diff |
git reset always moves HEAD. The mode decides whether it also updates the index and your working tree.
3. Reset modes: soft vs mixed vs hard vs keep
| Mode | Moves HEAD? | Resets index? | Updates working tree files? |
|---|---|---|---|
--soft |
Yes | No | No |
--mixed (default) |
Yes | Yes | No |
--hard |
Yes | Yes | Yes (destructive) |
--keep |
Yes | Yes | Only if it won’t overwrite local changes |
--soft: rewrite commits, keep staged changes
Use this when you want to redo a commit (message, author, squashing) but keep the content staged:
# Change the last commit message
git reset --soft HEAD~1
git commit -m "Better message"
--mixed (default): undo commits, keep files, unstage everything
This is the “undo commit but keep my work” mode. It moves HEAD and resets the index to match:
# Default mixed reset
# Equivalent to: git reset --mixed HEAD~1
git reset HEAD~1
After a mixed reset, your files still contain your changes, but they show up as unstaged modifications.
--hard: make everything match HEAD (destructive)
Hard reset is the nuclear option: it makes the index and working tree match the target commit.
# Throw away local changes to tracked files
git reset --hard
--keep: move HEAD, but refuse to overwrite your local changes
--keep is useful when you want to move the branch pointer but you have local modifications you don’t want to lose.
# Keep local working tree changes if possible
git reset --keep HEAD~1
If the reset would overwrite your local changes, Git will stop with an error instead of destroying your work.
4. Common scenarios
Fix a bad commit message (local, not pushed)
git reset --soft HEAD~1
git commit -m "Fix: correct message"
Split one commit into two commits
# Undo the commit but keep changes staged
# Then unstage selectively and commit in smaller chunks
git reset --soft HEAD~1
git restore --staged .
git add -p
git commit -m "Part 1"
git add -p
git commit -m "Part 2"
Undo “git add” (unstage a file)
Modern Git: use git restore --staged (recommended):
git restore --staged path/to/file
Classic Git: you can also use git reset with a path to unstage:
git reset path/to/file
git restore for file-level undo, and git reset for commit-history undo.
If you want the full decision tree, see Git Undo: Reset, Revert & Restore.
Throw away local commits and match the remote branch
# Make sure you really want to lose local work
git fetch origin
git reset --hard origin/main
Undo a merge that you haven’t pushed yet
If you just ran git merge and want to go back immediately, ORIG_HEAD often points to where you were before:
# Go back to where you were before the merge
git reset --hard ORIG_HEAD
If the merge was pushed, don’t reset the shared branch. Use git revert -m instead: Git Revert a Merge Commit.
5. Recovery: undo a bad reset with reflog
If you moved HEAD to the wrong place, don’t panic. Git usually still knows where you were.
Step 1: inspect reflog:
git reflog -n 20
Step 2: reset back to the commit you want:
git reset --hard <hash-from-reflog>
6. Reset vs revert vs restore
| Command | Best for | Rewrites history? |
|---|---|---|
git reset |
Undo local commits, move branch pointer, unstage changes | Yes (if you force-push) |
git revert |
Undo a pushed commit safely by adding a new commit | No |
git restore |
Discard file edits or unstage paths without moving HEAD | No |
7. Troubleshooting
“fatal: ambiguous argument 'HEAD~1'”
This often happens in a repo with only one commit (there is no parent). Try resetting to a specific hash, or use --soft with an explicit commit you can see in git log.
Untracked files are still there after reset --hard
git reset --hard only affects tracked files. To remove untracked files, use git clean (careful):
# Preview what would be deleted
git clean -nd
# Then delete untracked files/directories
git clean -fd
I reset to the wrong place and now my commit is gone
Use reflog and recover quickly:
git reflog -n 20
git reset --hard <hash>
FAQ
Does git reset delete commits?
It removes commits from the current branch history, but the commits usually still exist in the repository for a while (reachable from reflog) until garbage collection prunes unreachable objects.
What’s the safest way to “undo git add”?
Use git restore --staged <file> (modern, explicit) or git reset <file> (classic). Both unstages the path without rewriting history.
What if I already pushed?
If other people might have pulled the commit, prefer git revert instead of reset. See Git Undo: Reset, Revert & Restore.
Want the full “what should I run?” decision guide for undoing anything (including pushed commits)? See: Git Undo: Reset, Revert & Restore.