How to remove the most recent local commits?

You accidentally committed the wrong files to Git, but didn't push the commit to the server yet. How to remove those commits from the local repository?

This is a very popular question on Stack Overflow.

The good news is that the fix is easy.

Setup

Let’s start from an empty repository.

alina

alina git:( main ) git status

On branch main

Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

alina git:( main )

A typical workflow involves editing a file, making a few changes, and then committing it once satisfied with the result.

alina

alina git:( main ) vi README.md

alina git:( main ) git add README.md

alina git:( main ) git commit -m 'add line 1'

[main b06569b] add line 1

 1 file changed, 6 insertions(+), 1 deletion(-)

alina git:( main )

This results in a commit tree like the following, where your last commit b06569 is the HEAD of the main branch:

alina

b06569b0e09d3fmainHEAD

Since those are valid commits, we can push them to our remote repository.

alina

alina git:( main ) git push

Enumerating objects: 5, done.

Counting objects: 100% (5/5), done.

Delta compression using up to 8 threads

Compressing objects: 100% (2/2), done.

Writing objects: 100% (3/3), 287 bytes | 287.00 KiB/s, done.

Total 3 (delta 0), reused 0 (delta 0), pack-reused 0

remote: . Processing 1 references

remote: Processed 1 references in total

To ssh://remote.mygit.com/gitpowerup/arenas.git

   0e09d3f..b06569b  main -> main

alina git:( main )

At this stage, let’s make another change to the README.md file and commit it.

alina

alina git:( main ) vi README.md

alina git:( main ) git add README.md

alina git:( main ) git commit -m 'add line 2'

[main b5dea60] add line 2

 1 file changed, 2 insertions(+)

alina git:( main )

Now your local commit has a new HEAD b5dea6 and the commit tree looks like this:

alina

b5dea60b06569b0e09d3fmainorigin/mainHEAD

However, you realize that you made a mistake and no longer want that last commit b5dea6.

solution #1 : git reset —hard

The common solution is to use git reset --hard, but be cautious as it is also the riskiest option: any uncommitted changes will be lost.

Any committed change, even if reset by the command, can be retrieved from the reflog.

The git reset --hard command acts like a time machine, bringing you back to a previous state of your repository and removing all the changes made since a given commit.

The syntax is git reset --hard <commit>. In this case, we want to remove the last commit, so we’ll use git reset --hard HEAD~1.

alina

alina git:( main ) git reset --hard HEAD~1

HEAD is now at b06569b add line 1

alina git:( main )

As indicated by the command, the HEAD is now at commit b06569b:

alina

b5dea60b06569b0e09d3fmainorigin/mainHEAD

In the graph above, we can still see the commit b5dea6, but it no longer has a reference pointing to it. It will be garbage collected.

The git reflog command shows that if you change your mind, you can always retrieve that commit if you act before garbage collection occurs.

alina

alina git:( main ) git reflog

b06569b (HEAD -> main, origin/main, origin/HEAD) HEAD@{0}: reset: moving to HEAD~1

b5dea60 HEAD@{1}: commit: add line 2

b06569b (HEAD -> main, origin/main, origin/HEAD) HEAD@{2}: commit: add line 1

0e09d3f HEAD@{3}: clone: from ssh://remote.mygit.com/gitpowerup/arenas.git

alina git:( main )

The git status command shows that the working tree is clean and the index is empty.

alina

alina git:( main ) git status

On branch main

Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

alina git:( main )

solution #2 : git reset [—mixed]

Let’s first recreate an unwanted commit.

alina

alina git:( main ) vi README.md

alina git:( main ) git add README.md

alina git:( main ) git commit -m 're add line 2'

[main 9c27cc3] re add line 2

 1 file changed, 2 insertions(+)

alina git:( main )

Now we’re back to a commit graph like this:

alina

9c27cc3b5dea60b06569b0e09d3fmainorigin/mainHEAD

Let’s undo that last commit 9c27cc3 using the git reset command.

If no mode is specified for the git reset command, it defaults to --mixed.

alina

alina git:( main ) git reset HEAD~1

Unstaged changes after reset:

M       README.md

alina git:( main )

This leads to a commit tree like the following:

alina

9c27cc3b5dea60b06569b0e09d3fmainorigin/mainHEAD

The difference with git reset --hard is that the working tree is not cleaned up: the (unwanted) changes are still present, but they are no longer staged.

With git reset --mixed, we are still back in time, but only up to the point where the file was modified, not yet staged.

Let’s stage and commit that file again, so that we can explore the third way to use git reset.

alina

alina git:( main ) git add README.md

alina git:( main ) git commit -m 'accept line 2'

[main 896831d] accept line 2

 1 file changed, 2 insertions(+)

alina git:( main )

This results in a commit tree like this:

alina

896831d9c27cc3b5dea60b06569b0e09d3fmainorigin/mainHEAD

solution #3 : git reset —soft

Now let’s try the last mode of the git reset command: git reset --soft.

As mentioned before, the git reset command requires a reference to a commit. You can use the HEAD^n notation to refer to a commit n commits before the current HEAD, but you can also use the hash of the commit you want to reset to.

alina

alina git:( main ) git reset --soft b06569

alina git:( main )

This leads to a commit tree like the following, with HEAD back at the commit b06569b.

alina

896831d9c27cc3b5dea60b06569b0e09d3fmainorigin/mainHEAD

This time, the git status shows us that the working tree is clean and the index is not empty.

alina

alina git:( main ) git status

On branch main

Your branch is up to date with 'origin/main'.

Changes to be committed:

  (use "git restore --staged <file>..." to unstage)

        modified:   README.md

alina git:( main )

With git reset --soft, we are still back in time, but only up to the point where the changes were staged but before the commit was made.

As indicated by the output of the git status command, you can run git restore --staged README.md to unstage the changes.

That will bring you back to the state where the working tree is clean and the index is empty.