Undoing Commits with reset

git reset moves the current branch back to an earlier commit, as if the later commits never happened. Depending on the mode, it keeps their changes in the staging area, keeps them as unstaged edits, or throws them away completely. Use it on commits that exist only on your machine.

Starting point

$
git log --oneline
Output
db3cf93 (HEAD -> main) Add porridge recipe
291301d Add granola recipe
90c0089 Add omelette recipe
c4f5e47 Add pancake recipe

The three modes

Every reset moves the branch (and HEAD) to the commit you name. The mode decides what happens to the staging area and your files:

ModeBranch movesStaging areaWorking treeThe undone changes end up…
--softyeskeptkeptstaged, ready to commit again
--mixed (default)yesresetkeptas unstaged edits
--hardyesresetresetgone

--soft: undo the commit, keep it staged

Use it to redo the last commit differently — for example to combine it with more changes or give it a better message:

$
git reset --soft HEAD~1
git log --oneline
git status -s
Output
291301d (HEAD -> main) Add granola recipe
90c0089 Add omelette recipe
c4f5e47 Add pancake recipe
A  porridge.txt

The porridge commit is gone from the branch, but the file is still staged. Commit again whenever you're ready:

$
git commit -m "Add porridge recipe for busy mornings"
Output
[main 4283570] Add porridge recipe for busy mornings
 1 file changed, 1 insertion(+)
 create mode 100644 porridge.txt

--mixed: undo the commit, keep the edits

The default mode. Use it when you want to re-think what goes into each commit — for example to split one big commit into smaller ones:

$
git reset HEAD~2
git log --oneline
git status -s
Output
90c0089 (HEAD -> main) Add omelette recipe
c4f5e47 Add pancake recipe
?? granola.txt
?? porridge.txt

Two commits are undone, and their files are untracked again (because they were new in those commits). Now Sam can stage and commit them separately:

$
git add granola.txt && git commit --quiet -m "Add granola recipe"
git add porridge.txt && git commit --quiet -m "Add porridge recipe"
git log --oneline
Output
bb4cd64 (HEAD -> main) Add porridge recipe
c085d4c Add granola recipe
90c0089 Add omelette recipe
c4f5e47 Add pancake recipe

--hard: throw it all away

--hard resets everything to the named commit: the branch, the staging area and your files. Commits after it disappear from the branch, and any uncommitted changes are deleted.

$
echo "draft" > notes.txt
git add notes.txt
git reset --hard HEAD~1
git log --oneline
ls
Output
HEAD is now at c085d4c Add granola recipe
c085d4c (HEAD -> main) Add granola recipe
90c0089 Add omelette recipe
c4f5e47 Add pancake recipe
granola.txt  omelette.txt  pancakes.txt

The porridge commit and the staged notes.txt are both gone. Committed work can still be recovered with the reflog (lesson 20) — but uncommitted edits wiped by --hard are lost for good. Run git status before any hard reset.

A very common use is "throw away everything I've done since the last commit":

$
git reset --hard HEAD

Resetting to the server's version

If your local main has become a mess and you just want it to match the copy on GitHub exactly:

$
git fetch origin
git reset --hard origin/main

Resetting single files

Given a file name, git reset doesn't move the branch: git reset file just unstages that file, the same as git restore --staged file.

Only on unshared commits

Resetting removes commits from a branch, which rewrites history. That's fine for commits only you have. If the commits have been pushed and others may have based work on them, use git revert instead (next lesson), which undoes a commit by adding a new one.

Try it yourself

Make four small commits. Use --soft to merge the last two into one commit, --mixed to undo the newest and re-commit it with a better message, and finally --hard to drop it entirely. Check git log --oneline and git status after each step.