Undoing a Git Commit

Christian Emmer
Christian Emmer
Dec 3, 2020 · 5 min read
Undoing a Git Commit

Complicated Git commands are hard to remember and are full of landmines - here's a short guide on how to undo any Git commit.

First, why would you want to undo a commit? There are a number of reasons:

  • Adding additional changes forgotten in a commit
  • Removing committed secrets
  • Fixing a typo in a commit message (though there are better ways)
  • Changing a Git Commit Message
    Changing a Git Commit Message
    Dec 5, 2020 · 6 min read

    The Git commands to change a commit message are situational - here's a short guide to all of them.

Example project

Here's an example project with 3 commits since cloning:

$ git reflog
4614585 (HEAD -> main) HEAD@{0}: commit: Bad commit
5fde797 (origin/main, origin/HEAD) HEAD@{1}: commit: Questionable commit
f62390c HEAD@{2}: commit: Good commit
338de59 HEAD@{3}: clone: from https://github.com/emmercm/undoing-a-git-commit.git

There is a bad commit that hasn't been pushed yet, and a questionable commit that has been pushed.

We'll refer to this project in the scenarios below.

Finding the last "good" commit

It's important for us to figure what the last "good" commit is, because we will undo all the changes after it. Knowing which commit is "good" in the example project is trivial, but it may not be for your project.

git log and git reflog are your primary tools here, and assuming you have verbose commit messages it shouldn't be too difficult to figure out what the last "good" commit in your project is.

From the example project, the shortened hash f62390c is the last definitively "good" commit, and we're unsure if 5fde797 is "good" or not.

Scenario 1: the commit hasn't been pushed

From the above example project, let's say we only want to undo the most recent "bad" commit, and leave the pushed commits alone. This makes 5fde797 the last known "good" commit. There are two ways we can undo all changes after 5fde797...

Undo the last commit, whatever it is:

$ git reset --soft HEAD~1

$ git reflog
5fde797 (HEAD -> main, origin/main, origin/HEAD) HEAD@{0}: reset: moving to HEAD~1
4614585 HEAD@{1}: commit: Bad commit
5fde797 (HEAD -> main, origin/main, origin/HEAD) HEAD@{2}: commit: Questionable commit
f62390c HEAD@{3}: commit: Good commit
338de59 HEAD@{4}: clone: from https://github.com/emmercm/undoing-a-git-commit.git

Undo all changes after 5fde797, specifically:

$ git reset --soft 5fde797

$ git reflog
5fde797 (HEAD -> main, origin/main, origin/HEAD) HEAD@{0}: reset: moving to 5fde797
4614585 HEAD@{1}: commit: Bad commit
5fde797 (HEAD -> main, origin/main, origin/HEAD) HEAD@{2}: commit: Questionable commit
f62390c HEAD@{3}: commit: Good commit
338de59 HEAD@{4}: clone: from https://github.com/emmercm/undoing-a-git-commit.git

In both strategies, the --soft flag signals that we want to leave files in the working tree (on disk) alone, which is the least destructive and often preferred behavior. From here we have the freedom to either make fixes and git commit, or git checkout -- and git rid of all uncommitted changes.

Scenario 2: the commit has been pushed

From the above example project, let's say we find the "questionable" commit to be "bad" - this makes f62390c the last known "good" commit. There are two steps to undo all changes after f62390c...

Undoing the commits locally:

$ git reset --soft f62390c

$ git reflog
f62390c (HEAD -> main) HEAD@{0}: reset: moving to f62390c
4614585 HEAD@{1}: commit: Bad commit
5fde797 (origin/main, origin/HEAD) HEAD@{2}: commit: Questionable commit
f62390c (HEAD -> main) HEAD@{3}: commit: Good commit
338de59 HEAD@{4}: clone: from https://github.com/emmercm/undoing-a-git-commit.git

Undoing the commits on the remote repository.

Warnings:

  • History rewriting has the potential of being destructive - make a local backup of your project folder, along with its .git folder, so you can recover from any incorrect commands.
  • Other people who have cloned or forked your repository will not have their copy fixed with git pull, they will also need to do some manual work (not covered here).
$ git push --force origin
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/emmercm/undoing-a-git-commit.git
 + 5fde797...f62390c main -> main (forced update)

$ git reflog
f62390c (HEAD -> main, origin/main, origin/HEAD) HEAD@{0}: reset: moving to f62390c
4614585 HEAD@{1}: commit: Bad commit
5fde797 HEAD@{2}: commit: Questionable commit
f62390c (HEAD -> main, origin/main, origin/HEAD) HEAD@{3}: commit: Good commit
338de59 HEAD@{4}: clone: from https://github.com/emmercm/undoing-a-git-commit.git

Alternative 1: manually correcting the mistake in a new commit

The solutions offered in the above scenarios have the potential to go wrong - if you're fixing the contents of a file, and you don't mind additional commits in your history, consider adding a new commit for the fix. This option won't work if you're trying to remove committed secrets.

Alternative 2: using git revert

Similarly to making a new commit by hand, git revert adds a new commit that undoes all changes from a certain commit, such as removing the file named bad added in the "bad" commit:

$ git revert 4614585
Removing bad
[main 6e1f431] Revert "Bad commit"
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 bad

$ git reflog
6e1f431 (HEAD -> main) HEAD@{0}: revert: Revert "Bad commit"
4614585 HEAD@{1}: commit: Bad commit
5fde797 (origin/main, origin/HEAD) HEAD@{2}: commit: Questionable commit
f62390c HEAD@{3}: commit: Good commit
338de59 HEAD@{4}: clone: from https://github.com/emmercm/undoing-a-git-commit.git