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
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:
| Mode | Branch moves | Staging area | Working tree | The undone changes end up… |
|---|---|---|---|---|
--soft | yes | kept | kept | staged, ready to commit again |
--mixed (default) | yes | reset | kept | as unstaged edits |
--hard | yes | reset | reset | gone |
--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
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"
[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
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
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
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.