Great work!

XP to next level

BugEater
EN

--soft / --mixed / --hard: Three Depths of Undo

Learning Objectives

By the end of this lesson you will be able to:

  • Predict exactly what each of the three resets keeps and discards
  • Choose the right depth for a given undo
  • Explain what git reset --hard genuinely destroys
  • Un-stage a file and un-commit a commit with confidence

One Command, Three Areas

Every reset moves the current branch pointer to a commit you name. What differs is how much else it drags along.

Remember the three areas from the foundations trail:

Working directory   →   Staging area (index)   →   Repository (commits)
   your files              what git add put there      what git commit wrote
Reset Branch moves Index Working directory
--soft Yes Kept as it was Kept
--mixed (default) Yes Reset to match Kept
--hard Yes Reset to match Overwritten

Read it top to bottom: each one throws away everything the one above it does, plus one more layer.

--soft: Un-Commit, Keep Everything Staged

git reset --soft HEAD~1

The last commit is gone from the branch. All of its changes are still staged, ready to commit again.

Use it when the changes are right and the commit is not — wrong message, wrong grouping, forgot a file:

git reset --soft HEAD~1
git add tests/payments/refund.spec.ts     # the file you forgot
git commit -m "test: add refund boundary cases"

Three commits into one, same idea:

git reset --soft HEAD~3
git commit -m "test: cover the refund limit end to end"

Nothing can be lost by a --soft reset. Every change is still there, staged.

--mixed: Un-Commit and Un-Stage

git reset HEAD~1          # --mixed is the default

The commit is gone and the changes are back in your working directory, unstaged. The files are untouched; only the index was cleared.

This is the one you want when you need to re-decide what goes into which commit:

git reset HEAD~1
git add src/payments/refund.ts
git commit -m "fix: clamp refund to the configured limit"
git add tests/payments/refund.spec.ts
git commit -m "test: cover the clamped-refund path"

That is exactly the commit-splitting recipe from Module 2, now with a name.

--mixed also un-stages without touching commits:

git reset                              # un-stage everything
git reset src/payments/refund.ts       # un-stage one file

Nothing is lost here either. Your edits are all still in the files.

--hard: Move and Overwrite

git reset --hard HEAD~1

The commit is gone, the index is cleared, and your working directory is overwritten to match the target commit. Uncommitted edits in tracked files are gone.

Legitimate uses:

git reset --hard                       # discard all uncommitted changes, keep the commit
git reset --hard origin/main           # make my branch identical to the remote

The second is the recovery from Module 1 when someone rebased a branch you had pulled.

What It Really Destroys

Be precise about this, because the fear around --hard is half-misplaced.

Recoverable: the commits you moved off. They are still in the object database, and git reflog knows their IDs. Module 5 is entirely about getting them back.

Not recoverable: uncommitted changes in your working directory. They were never in a commit, so Git never stored them anywhere. Nothing brings them back.

So the danger of --hard is not the commits. It is the two hours of unsaved editing you had in the working tree.

Pro Tip: Run git status before every --hard. If it lists modified files you care about, git stash them first. Stash costs one second and is fully reversible; --hard over unsaved work is not.

Choosing

You want to Command
Redo the last commit, keeping the changes staged git reset --soft HEAD~1
Combine the last three commits into one git reset --soft HEAD~3 then commit
Split a commit into two git reset HEAD~1, stage in pieces
Un-stage a file git reset <file>
Throw away all uncommitted changes git reset --hard
Match a branch to the remote exactly git reset --hard origin/<branch>

And the constraint that governs all of them: reset rewrites your branch, so it is for commits that have not left your machine. For anything published, use git revert.

Key Takeaways

  • All three resets move the branch pointer; they differ in how much else they clear
  • --soft keeps changes staged; --mixed keeps them in the working directory; --hard overwrites the files
  • --mixed is the default, and is also how you un-stage a file
  • --hard cannot lose commits — the reflog has them — but it permanently loses uncommitted edits
  • git status before every --hard, and stash anything you would miss
  • Reset rewrites history, so it is only for commits nobody else has

Quiz

After git reset --soft HEAD~1, where are the changes from that commit?

Which reset do you use to split one commit into two?

What can git reset --hard destroy beyond recovery?

Your branch was rebased by its owner and you had pulled the old version. Which command adopts theirs?