How to undo the most recent commits in Git

Git is a distributed version control system. Undoing the last commit is something everyone needs in their first week, and the command you want depends on whether you also want to keep the changes — and on whether the commit has already been pushed.

The three resets, and what separates them

All three drop the commit. The difference is what happens to the work that was in it:

$ git reset --soft  HEAD~1     # changes kept, staged
$ git reset         HEAD~1     # changes kept, unstaged  (--mixed, the default)
$ git reset --hard  HEAD~1     # changes gone

The clearest way to see it is git status --short, which uses two columns — the first for the index, the second for the working tree:

--soft  : status = "M  file.txt"   file = one two three
--mixed : status = " M file.txt"   file = one two three
--hard  : status = ""              file = one two

M in the first column means staged and ready to commit again. M in the second means the change is still in your file but you would need to git add it. Nothing at all means the work is gone.

So: --soft if you want to redo the commit immediately, the default if you want to rearrange what goes into it, --hard only when you genuinely want the work gone.

–hard takes uncommitted work with it

This is the part that costs people real work, and it is easy to miss because the command names a commit:

before: status --short :  M file.txt
after : status --short : ''
after : file           : one two

That edit was never committed. reset --hard does not just move the branch pointer — it also overwrites your working tree with the target commit, and anything you had not committed is simply overwritten.

Everything else on this page is recoverable. This is not.

The commit itself is recoverable

If you --hard reset away a commit, the commit is still there. Git only moved the branch pointer; nothing was deleted:

$ git reflog
a4ae70b HEAD@{0} reset: moving to HEAD~1
3f09ed6 HEAD@{1} commit: third commit
a4ae70b HEAD@{2} commit: second commit

$ git reset --hard 3f09ed6
3f09ed6 third commit  a4ae70b second commit  ea0365c first commit

git reflog records every position HEAD has held, including the ones no branch points at any more. It is the first thing to reach for after any “I think I just lost that” moment.

Unreferenced commits are eventually garbage collected, so this is not a backup — but the default grace period is 90 days, which is plenty for the panic.

If the commit is already pushed, revert instead

reset rewrites history. That is fine while the commits are only on your machine, and antisocial once anyone else has them — they will get the old commits back on their next push.

git revert makes a new commit that undoes an old one:

$ git revert HEAD
79a8358 Revert "third commit"   3f09ed6 third commit   a4ae70b second commit
file : one two

The content is undone and the history moves forward. Nothing anyone else has is invalidated.

The rule of thumb: not pushed, reset; pushed, revert.

If only the message was wrong

$ git commit --amend -m "third commit, better message"
20b765b third commit, better message   a4ae70b second commit

Note the hash changed — 3f09ed6 became 20b765b. --amend does not edit the commit; it builds a replacement and moves the branch to it. So it counts as rewriting history, with the same caveat about pushed commits.

You can also use --amend with no -m to add forgotten files to the last commit: stage them first, then amend.

More than one commit

HEAD~1 is “one commit back”, so the number is the only thing that changes:

$ git reset --soft HEAD~2
206acf5 first commit
status --short : M  file.txt

Both commits are gone and all of their content is staged, ready to be committed as one.

While you are here, the difference between the two suffixes:

HEAD   : 45584d7
HEAD~1 : 369ca07
HEAD^  : 369ca07

Identical on a linear history. ~ walks back n commits following first parents; ^ picks which parent of a merge you mean. You only notice the difference at a merge commit, where HEAD^2 is the branch that was merged in.

About Netcup (advertisement)

The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).