Homeβ€Ί DevOpsβ€Ί Git Reset: Hard, Soft and Mixed Explained

Git Reset: Hard, Soft and Mixed Explained

Where developers are forged. Β· Structured learning Β· Free forever.
πŸ“ Part of: Git β†’ Topic 17 of 19
Understand git reset --hard, --soft, and --mixed with real examples.
βš™οΈ Intermediate β€” basic DevOps knowledge assumed
In this tutorial, you'll learn:
  • --soft keeps changes staged, --mixed (default) unstages them, --hard discards them. Only --hard risks losing work.
  • git reset is for local branch cleanup. git revert is for undoing changes on shared branches β€” it adds a new commit rather than rewriting history.
  • git reflog saves you after a bad --hard reset β€” commits you reset past are still recoverable for 30 days via reflog. Uncommitted working directory changes are not.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
⚑ Quick Answer
Git reset moves the branch pointer backwards in history. The three modes answer one question: what happens to the changes in the commits you're 'undoing'? Hard: throw them away. Soft: keep them staged. Mixed (the default): unstage them but keep the files. Hard is permanent. Soft and mixed are undo buttons.

git reset is one of those commands where the options have dramatically different consequences and the documentation is just dense enough to send people to Stack Overflow every time. I've taught Git to dozens of developers and 'git reset --hard and I lost my work' is in the top three support requests, every time.

The mental model that prevents mistakes: think of git reset as moving a bookmark in a book. The three modes control whether you keep the pages you're jumping over.

The Three Modes: Hard, Soft, Mixed

All three modes move HEAD (and the current branch pointer) to the specified commit. The difference is entirely in what happens to the working directory and the staging index.

--soft: HEAD moves back. Working directory untouched. Staging index untouched. The changes from the undone commits are staged and ready to recommit. Use this for 'I committed too early' or 'I want to rewrite the last N commits into one'.

--mixed (default): HEAD moves back. Working directory untouched. Staging index cleared. Changes appear as unstaged modifications. Use this for 'I committed the wrong things' or 'I need to regroup and re-add selectively'.

--hard: HEAD moves back. Working directory changed to match the target commit. Staging index cleared. Any uncommitted changes are gone. Use this when you genuinely want to throw away work. This is the only mode that can lose data.

git_reset_modes.sh Β· BASH
1234567891011121314151617181920212223242526272829303132
# Current state: 3 commits ahead of where we want to be
git log --oneline
# d4f8b3c (HEAD) Add unit tests
# 9c3e8a2 Fix null check
# 7b2d4f1 Add backoff logic
# a3f9c2e (origin/main) Initial PaymentRetryService

# --soft: undo commits, keep changes STAGED
git reset --soft HEAD~3
git status
# Changes to be committed: all 3 commits' changes are staged
# Use case: squash 3 commits into 1 clean commit

# --mixed (default): undo commits, keep changes UNSTAGED
git reset HEAD~3
# or: git reset --mixed HEAD~3
git status
# Changes not staged for commit: all 3 commits' changes in working dir
# Use case: regroup changes before re-adding selectively

# --hard: undo commits, DISCARD changes entirely
git reset --hard HEAD~3
git status
# nothing to commit, working tree clean
# WARNING: the changes from those 3 commits are gone

# Reset to a specific commit hash
git reset --soft a3f9c2e
git reset --hard a3f9c2e

# Reset a single file (unstage it)
git reset HEAD -- src/main/java/io/thecodeforge/payment/PaymentService.java
β–Ά Output
# --soft result:
On branch feature/payment-retry
Changes to be committed:
modified: src/main/java/io/thecodeforge/payment/PaymentRetryService.java
new file: src/test/java/io/thecodeforge/payment/PaymentRetryServiceTest.java

# --hard result:
HEAD is now at a3f9c2e Initial PaymentRetryService

git reset vs git revert: Which to Use

This is the question that trips up the most people in interviews and in practice. The key distinction: reset rewrites history, revert adds to history.

git reset moves the branch pointer backward. The commits you reset past effectively disappear from the branch. This is fine on your local feature branch. On a shared branch, it rewrites public history and requires a force push β€” which breaks teammates' local copies.

git revert <hash> creates a new commit that undoes the changes of the specified commit. History is preserved. The original commit is still there. You push normally. This is what you use on main, develop, or any shared branch.

Rule of thumb: reset for local cleanup on your own branches. Revert for undoing things on shared branches.

git_reset_vs_revert.sh Β· BASH
123456789101112
# RESET: rewrites history (local branches only)
git reset --hard HEAD~1    # Undo last commit, discard changes
git reset --soft HEAD~1    # Undo last commit, keep changes staged
# If already pushed: requires force push β€” DON'T do on shared branches
git push origin feature/my-branch --force-with-lease

# REVERT: adds a new 'undo' commit (safe for shared branches)
git revert HEAD            # Undo last commit
git revert a3f9c2e        # Undo specific commit
git revert HEAD~3..HEAD   # Undo last 3 commits (creates 3 revert commits)
git revert HEAD~3..HEAD --no-commit  # Stage all reverts, commit once
git push origin main      # Normal push β€” history intact
β–Ά Output
[feature/payment-retry 9f2c4a1] Revert "Add PaymentRetryService with exponential backoff"
1 file changed, 47 deletions(-)
⚠️
git reset --hard is permanent without reflogRunning git reset --hard discards uncommitted working directory changes permanently β€” there is no reflog for uncommitted work, only for commits. However, if you reset past committed work, those commits are still recoverable via git reflog for 30 days. The one scenario with no recovery: uncommitted changes you hadn't staged yet, wiped by --hard. Always check git status before running --hard.
ModeHEAD moves?Index (staging)?Working dir?Data loss?
--softYesUnchanged (changes staged)UnchangedNo
--mixed (default)YesCleared (changes unstaged)UnchangedNo
--hardYesClearedReverted to targetYes β€” uncommitted work lost

🎯 Key Takeaways

  • --soft keeps changes staged, --mixed (default) unstages them, --hard discards them. Only --hard risks losing work.
  • git reset is for local branch cleanup. git revert is for undoing changes on shared branches β€” it adds a new commit rather than rewriting history.
  • git reflog saves you after a bad --hard reset β€” commits you reset past are still recoverable for 30 days via reflog. Uncommitted working directory changes are not.
  • Never force-push after resetting a shared branch. The correct tool for shared branches is always git revert.

⚠ Common Mistakes to Avoid

  • βœ•Running git reset --hard on a shared branch and force-pushing β€” this rewrites public history and breaks every teammate who has fetched those commits.
  • βœ•Using --hard when you meant --soft, losing staged changes β€” always run git status first to understand exactly what's in your working directory and index.
  • βœ•Confusing 'git reset HEAD <file>' (unstage a file) with 'git reset HEAD~1' (undo a commit) β€” the presence or absence of a tilde makes a massive difference.
  • βœ•Using reset instead of revert on main to undo a bad deployment β€” revert is always correct for shared branches; reset + force push will cause merge conflicts for every developer who pulled the bad commit.

Interview Questions on This Topic

  • QWhat are the three modes of git reset and when would you use each?
  • QA bad commit was pushed to main. How do you undo it without force pushing?
  • QWhat is the difference between git reset and git revert? When is each appropriate?

Frequently Asked Questions

What is the difference between git reset --hard and --soft?

Both move HEAD back to a previous commit. --soft keeps your changes staged in the index, ready to recommit. --hard discards all changes and reverts your working directory to match the target commit β€” any uncommitted work is permanently lost.

How do I undo the last commit without losing my changes?

Run git reset --soft HEAD~1 to undo the last commit while keeping all changes staged. Or git reset HEAD~1 (--mixed) to undo the commit and unstage the changes, leaving them in your working directory. Both are non-destructive.

When should I use git revert instead of git reset?

Use git revert on any branch that other people have already pulled from β€” main, develop, release branches. Revert creates a new commit that undoes the change without rewriting history. Reset rewrites history, which breaks teammates' local repos and requires a force push.

πŸ”₯
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousGit Cherry Pick: Apply Commits Across BranchesNext β†’Git Fetch vs Pull: What's the Difference
Forged with πŸ”₯ at TheCodeForge.io β€” Where Developers Are Forged