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:
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:
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
:
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:
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:
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:
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
.
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.