git reset is a powerful command for undoing changes by moving the current branch pointer to a different commit. Understanding its modes is crucial for safe operation.
git reset --hard permanently discards changes. Make sure you have backups or that you truly want to lose those changes before using it.
What Does Reset Do?
Reset changes which commit your current branch (HEAD) points to. Depending on the mode, it can also update:
- The index (staging area)
- The working directory (your files)
Reset Modes
—soft: Keep Everything
Moves HEAD only. Index and working directory unchanged.
Use case: Undo a commit but keep all changes staged.
Example:
# Combine last 5 commits into one
git reset --soft HEAD~5
git commit -m "Combined feature implementation"
—mixed: Default Behavior
Moves HEAD and updates the index. Working directory unchanged.
git reset HEAD~1
# Equivalent to:
git reset --mixed HEAD~1
Use case: Undo commits and unstage changes, but keep modifications in your working directory.
Example:
# Unstage all files
git reset
# Unstage specific files
git reset -- file.txt
—hard: Discard Everything
Moves HEAD, updates index and working directory. Destructive!
Use case: Completely discard commits and all changes.
This permanently deletes uncommitted changes. Tracked files not in the target commit are removed.
Example:
# Discard all local changes and commits
git reset --hard origin/main
—merge: Safe During Merges
Resets index and updates files different between commit and HEAD, but keeps local changes.
Use case: Abort a merge while preserving local modifications.
—keep: Preserve Local Changes
Resets index and updates working tree, but aborts if local changes would be lost.
Use case: Safely remove commits while keeping your work-in-progress changes.
Reset Modes Comparison
| Mode | HEAD | Index | Working Dir | Safety |
|---|
--soft | ✓ | ✗ | ✗ | Safe |
--mixed | ✓ | ✓ | ✗ | Safe |
--hard | ✓ | ✓ | ✓ | Dangerous |
--merge | ✓ | ✓ | Partial | Safe |
--keep | ✓ | ✓ | Partial | Safe |
Common Operations
Unstaging Files
Remove files from the staging area:
# Unstage all files
git reset
# Unstage specific file
git reset -- path/to/file.txt
# Alternative (newer syntax)
git restore --staged path/to/file.txt
Undoing the Last Commit
Keep changes but unstage them
Discard changes completely
Undoing Multiple Commits
# Undo last 3 commits, keep changes
git reset HEAD~3
# Undo commits, discard all changes
git reset --hard HEAD~3
Moving to a Specific Commit
# Reset to a specific commit
git reset --hard abc123
# Reset to where you were 2 moves ago
git reset --hard HEAD@{2}
Detailed Examples
Undo Add (Unstage)
$ git add frotz.c filfre.c
$ git reset # Unstage both files
$ git add filfre.c # Stage only the one you want
$ git commit
Undo Commit and Redo
$ git commit -m "Incomplete work"
$ git reset --soft HEAD^ # Undo commit, keep changes staged
$ # Edit files to fix them
$ git commit -a -c ORIG_HEAD # Commit with the old message
Create Topic Branch from Commits
$ git branch topic/wip # Save current position
$ git reset --hard HEAD~3 # Remove last 3 commits from main
$ git switch topic/wip # Continue work on topic branch
Undo a Merge
$ git merge feature
# Conflicts! Not ready to deal with this now
$ git reset --hard # Abort the merge
$ git reset --hard ORIG_HEAD # Alternative: undo completed merge
Undo Merge Keeping Local Changes
$ git pull
# After inspecting, you don't want these changes
$ git reset --merge ORIG_HEAD # Undo merge, preserve local changes
Split a Commit
Reset to before the commit
The -N flag marks files as intent-to-add.
Interactively stage changes
Commit each logical change
git commit -m "First logical change"
git add -p
git commit -m "Second logical change"
Continue until all changes are committed
git add .
git commit -m "Remaining changes"
Understanding the Three Trees
Git manages three trees:
- HEAD - Last commit snapshot, next parent
- Index - Proposed next commit snapshot (staging area)
- Working Directory - Your actual files
State Table Example
Given states A, B, C, D:
working index HEAD target --soft --mixed --hard
A B C D A B D A D D D D D
A B C C A B C A C C C C C
B B C D B B D B D D D D D
Recovering from Reset
ORIG_HEAD Reference
Git saves the previous HEAD position in ORIG_HEAD:
# Undo a reset
git reset --hard ORIG_HEAD
Using Reflog
Find lost commits in the reflog:
# View reflog
git reflog
# Output:
# abc123 HEAD@{0}: reset: moving to HEAD~3
# def456 HEAD@{1}: commit: Add feature
# Recover to before the reset
git reset --hard HEAD@{1}
Reset vs. Other Commands
Reset vs. Revert
| Aspect | Reset | Revert |
|---|
| History | Rewrites | Creates new commit |
| Safety | Dangerous on shared branches | Safe for shared branches |
| Use case | Local cleanup | Undo public commits |
# Reset (rewrites history)
git reset --hard HEAD~1
# Revert (preserves history)
git revert HEAD
Reset vs. Checkout vs. Restore
| Command | Moves HEAD | Updates Index | Updates Working Dir |
|---|
reset --soft | Yes | No | No |
reset --mixed | Yes | Yes | No |
reset --hard | Yes | Yes | Yes |
checkout <commit> | Yes (detached) | Yes | Yes |
restore | No | Optional | Yes |
Best Practices
-
Never reset public commits - Once pushed and shared, use
git revert instead
-
Use —soft for commit amendments - Safer than
git commit --amend for multiple commits
-
Prefer restore over reset for files -
git restore is clearer for file operations:
# Old way
git reset -- file.txt
# New way (Git 2.23+)
git restore --staged file.txt
-
Check reflog when in doubt - You can almost always recover with
git reflog
-
Use —keep for safety - When removing commits,
--keep prevents data loss
Common Mistakes and Solutions
Accidentally Used —hard
# Check reflog
git reflog
# Find the commit before reset
# Reset back to it
git reset --hard HEAD@{1}
Reset the Wrong Number of Commits
# Go back to any point in reflog
git reset --hard HEAD@{2}
Reset on the Wrong Branch
# Switch to the correct branch
git checkout correct-branch
# Reset to desired state
git reset --hard HEAD@{1}
# Fix the wrong branch
git checkout wrong-branch
git reset --hard <correct-commit>
Configuration
No configuration needed, but useful aliases:
[alias]
# Undo last commit, keep changes
undo = reset --soft HEAD~1
# Unstage everything
unstage = reset
# Discard all changes (with confirmation)
nuke = !git reset --hard HEAD && git clean -fd