Git Reset
The git reset command is a tool for undoing changes. It points to the current state of HEAD back to a previous commit.
The git reset has three core forms of invocation that are Soft, Mixed, and Hard. These three forms correspond to Git's three internal state management systems, The commit History (HEAD), The Staging Area (INDEX), and The Working directory.
Git reset and the three trees of git
To properly understand how Git is working, we must understand Git's internal state management systems called the three trees of Git. These trees are just different collections of files.
The three trees of git architecture are:
- The Working Directory: It holds the actual files
- The Index or Staging Area: It is where files are getting prepared to be included on a commit.
- The HEAD or The Commit History: It is a place where commits are saved and retrieved. The HEAD point to the last commit.
The proper way to demonstrate the git three trees architecture is by creating a new repository and following files through the tree trees of git. Let us get started by creating a new repository by following the bellow commands:
$ mkdir git_reset_demo
$ cd git_reset_demo/
$ git init
Initialized empty Git repository in /git_reset_demo/.git/
$ touch reset_file_demo
$ git add reset_file_demo
$ git commit -m "initial commit"
[master (root-commit) 1a03d4b] initial commit
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 reset_file_demo
In the above example, we create a new git repository with one empty file reset_file_demo. The commit history has one commit (1a03d4b) from adding reset_demo_file.
The Working Directory or The Working Tree
The first tree is the working directory. It represents the files on your local file system. Any change to files will be viewed in the Working Tree. Git recognizes any change made to files in the Working Tree, but until you ask Git to track these files, it will not save any modification made to these files.
We will continue in our example by adding some change to the Working Tree.
$ echo "adding some text for git reset demo" >> reset_file_demo
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: reset_file_demo
no changes added to commit (use "git add" and/or "git commit -a")
In the above example, we added some text to reset_file_demo. After, we run the git status command that shows that Git is aware of the file's changes. These changes are apart of the Working Tree.
The Staging Area (Index)
The next tree is the Staging Index. On this tree, Git starts tracking and saving changes that occur in files. These changes are prepared to be included on the next commit.
Different terms can come while talking about the Staging Index like staging area, index, staged files, cache, directory cache, local cache, etc ...
To display the state of the Staging Index, we need use git ls-files
command as follows:
$ git ls-files -s
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 reset_file_demo
The git ls-files show information about files in the Staging Index and the Working Tree. To filter just for staged files, we add -s or --stage option. The -s option shows staged contents' mode bits, object name, and stage number.
In the above example, we are interested in the object name 'reset_file_demo' and the stage number which is the Staging object SHA-1 hash (e69de29bb2d1d6434b8b29ae775ad8c2e48c5391). The staging Index has its own object hash that is different from those of the commit history.
The next step is to add the modified file 'reset_file_demo' to the Staging Index.
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: reset_file_demo
no changes added to commit (use "git add" and/or "git commit -a")
$ git add reset_file_demo
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: reset_file_demo
In the above, we used the git add
command to move the modified 'reset_file_demo' file from the Working Tree to the Staging Index.
Let's display the Staging Index content.
$ git ls-files -s
100644 1ec4e63e067938156e289bc9e797b562456dd209 0 reset_file_demo
In the above output, we see that the object SHA-1 hash for 'reset_file_demo' has been changed from e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 to 1ec4e63e067938156e289bc9e797b562456dd209.
The Commit History or the Local Repository
The last tree is the Commit History which is everything in the .git directory.
To add items from the Staging Index to the Commit History, we will use the git commit command that takes all Staging Index changes, wraps them together, and add them to the Commit History.
$ git commit -m "modified content of rest_file_demo"
[master 90bb007] modified content of reset_file_demo
1 file changed, 1 insertion(+)
$ git status
On branch master
nothing to commit, working tree clean
In the example above, we created a new commit with the message "modified content of rest_file_demo". The changes are saved to the Commit History. After running the git status command, we can see that we now have a clean Working tree and nothing to commit.
Once the changes went through the three trees, the git reset
command can be invoked.
How git reset works
The git reset command seems to be similar to the git checkout command, as they both operate on HEAD.
However, the git checkout command operates only on the HEAD reference pointer, while the git reset
command will move
the HEAD pointer reference and the current branch reference. To explain this behavior, consider the following illustration:

The illustration above presents a sequence of commits on the master branch. The HEAD ref and the Master branch ref point to the last commit (the commit D).
We will see how the illustration change when we run git checkout B
and git reset B
.
git checkout B

After executing the git checkout B
command, the Master ref did not move, still pointing to the last commit (commit D)
while the HEAD ref has been moved and now is pointing to the commit B. The repository is now in a "detached HEAD" state.
git reset B

As we can see in the illustration above, after executing the git reset B
command, both the HEAD and branch refs moved to the specified commit.
Apart from updating the HEAD and the branch ref, the git reset
command will change the state of all three trees. There is always a change happening to the third three, the Commit Tree. This change is about the move of the HEAD and the branch ref to the specified commit.
On the other hand, there are command-line arguments for the Working Tree and the Staging Index --soft
, --mixed
, and --hard
,
which controls how the modification will occur.
Main Options
The git reset command has implicit arguments of --mixed
and HEAD
. Invoking the git reset
is equal to running git reset --mixed HEAD
. In this case, HEAD
is the specified commit. Rather than using HEAD
, any Git SHA-1 commit hash can be passed.

--hard
The most frequently used option is the --hard
. However, it is risky to use. When the --hard
option is used, The Commit History ref pointers are changed to point to the specified commit. Then it resets (clears out) The Working Directory and The Commit History and overwrites their contents with the specified commit's content. Any modifications that have been previously pending to The Working Directory or The Staging Index are cleared out to match the specified commit. This indicates any work in progress that lives in The Working Directory and The Staging Index will be lost.
To explain this, let us continue with the 'git_reset_demo' repository that we created before. First, we will make some change to the 'git_reset_demo' repository by executing the following commands:
$ echo 'new file demo content' >> new_file_demo
$ git add new_file_demo
$ echo "modified content" >> reset_file_demo
In the above commands, a new file named 'new_file_demo' is created and added to the Staging Index. Also, the content of the 'reset_file_demo' file has been updated. Now let us verify the repository state using the git status command.
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: new_file_demo
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: reset_file_demo
The above output shows that there are some pending changes in the repository. The Staging Index tree has a pending modification by adding the 'new_file_demo' file, and the Working Directory has a pending modification by updating the 'reset_file_demo' file.
Now let us verify the state of the Staging Index :
$ git ls-files -s
100644 dfaf1227cbd021fde5b0dfff42f9e7dcb1c382ed 0 new_file_demo
100644 1ec4e63e067938156e289bc9e797b562456dd209 0 reset_file_demo
The output shows that the 'new_file_demo' has been added to the Staging Index, and the 'reset_file_dem' has been updated. However, its staging SHA-1 hash (1ec4e63e067938156e289bc9e797b562456dd209) stays the same. This unchanged SHA-1 hash of the 'reset_file_demo' file is expected because we did not move the change from the Working Directory to the Staging Index.
Now let us run the ``git rest --hard` command and check the repository's new state.
$ git reset --hard
HEAD is now at 90bb007 modified content of reset_file_demo
$ git status
On branch master
nothing to commit, working tree clean
$ git ls-files -s
100644 1ec4e63e067938156e289bc9e797b562456dd209 0 reset_file_demo
In the above, after running the git reset
command using the --hard
option. The output shows that the HEAD ref is pointing now to the recent commit '90bb007'. Then, we verify the state of the repository with the git status
command. The output shows that there are no pending changes. Finally, we verify the Staging Index state and found that it has been moved to a point before 'new_file_demo' was created. The modification to the 'reset_file_demo' file and the addition of the 'new_file_demo' file has been lost. This loss cannot be undone.
Note: Before using the
--hard
option, be sure what you really want to do since thegit rest --hard
command overwrites any uncommitted changes. This means any pending work will be lost.
--mixed
The --mixed
option is the default operation mode for the git reset
command. The HEAD and Branch refs are moved to the specified commit. The Staging Index is reset to the state of the specified commit. Any modifications that have been pending in the Staging Index are moved to the Working Directory.
Let us continue with our 'git_reset_demo' example :
$ echo 'new file demo content' > new_file_demo
$ git add new_file_demo
$ echo 'more content' >> reset_file_demo
$ git add reset_file_demo
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: new_file_demo
modified: reset_file_demo
$ git ls-files -s
100644 dfaf1227cbd021fde5b0dfff42f9e7dcb1c382ed 0 new_file_demo
100644 237994aadf20ee6b9bc688b3f18bb8b95af43e3c 0 reset_file_demo
In the example above, a 'new_file_demo' has been added, and the contents of 'reset_file_demo' have been updated. After, these modifications are moved to the Staging Index using git add
. Under the repository's current state, it is time to execute the git reset
command.
$ git reset --mixed
Unstaged changes after reset:
M reset_file_demo
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: reset_file_demo
Untracked files:
(use "git add <file>..." to include in what will be committed)
new_file_demo
no changes added to commit (use "git add" and/or "git commit -a")
$ git ls-files -s
100644 1ec4e63e067938156e289bc9e797b562456dd209 0 reset_file_demo
The --mixed
option is the default mode. It is equivalent to executing git reset
without any options. The output of git status
indicates that modifications to 'reset_file_demo' and 'new_file_demo' is an untracked file. The Staging Index has been reset, and the pending modifications are moved to the Working Directory.
--soft
The --soft
option updates HEAD and Branch refs to the specified commit. The reset stops on the modification of the ref pointers. The Staging Index and the Working directory are not affected.
Let us continue with our demo repository:
$ git add reset_file_demo
$ git ls-files -s
100644 237994aadf20ee6b9bc688b3f18bb8b95af43e3c 0 reset_file_demo
$ rm new_file_demo
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: reset_file_demo
In the example above, the change of 'reset_file_demo' has been promoted to the Staging Index. We checked the update of the Index with the git ls-files -s
output. We removed 'new_file_demo using the rm new_file_demo
because we do not need it anymore. The git status
shows changes in the file 'reset_file_demo' to be committed.
With the current repository state, we can now invoke a git reset --soft
.
$ git reset --soft
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: reset_file_demo
$ git ls-files -s
100644 237994aadf20ee6b9bc688b3f18bb8b95af43e3c 0 reset_file_demo
In the above, we have executed a soft reset. Verifying the repository state using git status
and git ls-files -s
reveals that nothing has changed. A soft reset will only reset the Commit History. By default, git reset
is invoked with HEAD as the target commit. So in our example, our Commit History was already on the HEAD, and we implicitly reset to HEAD, so nothing really happened.
To execute a soft reset with a target that is not HEAD
. Let us create a new commit. We already have 'reset_file_demo' waiting in the Staging Index to be committed.
$ git commit -m"more content to reset_file_demo"
At this stage, our demo repository has three commits. We will move back in time like a time machine to the first commit. To do this, we need the first commit ID, which can be done by looking at the output from git log
.
$ git log
commit c6db2c83f28a54e5281e4b9bdefb84e3a904273f (HEAD -> master)
Author: yassere dahbi <dahbi.yassere@gmail.com>
Date: Fri Oct 25 12:41:49 2019 +0100
more content to reset_file_demo
commit 90bb007caabbd7c87752046a5ab754cbf44ad4c8
Author: author_name <author_email@pincode.com>
Date: Wed Oct 23 14:50:20 2019 +0100
modified content of reset_file_demo
commit 1a03d4bb1604fd7958d328909faafd90347403cf
Author: author_name <author_email@pincode.com>
Date: Wed Oct 23 11:47:49 2019 +0100
initial commit
We are interested in the first commit ID, for this example is 1a03d4bb1604fd7958d328909faafd90347403cf. So we will use this commit ID as the target for our soft reset.
Before using our time machine (git reset
) to travel back in time, let us first verify our repository's current state.
$ git status && git ls-files -s
On branch master
nothing to commit, working tree clean
100644 237994aadf20ee6b9bc688b3f18bb8b95af43e3c 0 reset_file_demo
Here the combo git status
and git ls-files -s
outputs that we have a clean Working Directory and there are no pending changes in the Staging Index.
The version of 'reset_file_demo' file in the Staging Index is 237994aadf20ee6b9bc688b3f18bb8b95af43e3c. So now we can invoke our time machine git reset --soft
to soft reset to the first commit.
$ git reset --soft 1a03d4bb1604fd7958d328909faafd90347403cf
$ git status && git ls-files -s
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: reset_file_demo
100644 237994aadf20ee6b9bc688b3f18bb8b95af43e3c 0 reset_file_demo
In the example above, we had a soft reset to the first commit and also executed the git status
and git ls-files -s
combo command that shows us the state of our repository. As we can see, git status
indicates that there are some modifications to 'reset_file_demo' in the Staging Index waiting to be committed. For git ls-files -s
shows that the Staging Index has not changed, and we still have the same SHA 237994aadf20ee6b9bc688b3f18bb8b95af43e3c that we have before.
To understand more what happened after a soft reset. Let us verify the state of the repository using git log
:
$ git log
commit 1a03d4bb1604fd7958d328909faafd90347403cf (HEAD -> master)
Author: author_name <author_email@pincode.com>
Date: Wed Oct 23 11:47:49 2019 +0100
initial commit
As we can see, the git log
output shows that we have a single commit in the commit history. This proves that git reset --soft
move back time to the specified commit. As with all git reset
invocations, the first action reset make is to reset the Commit tree. However, unlike --hard
and --mixed
that have both been against the HEAD and have not moved the Commit History back in time. A soft reset has only moved the Commit Tree back in time to the specified commit.
A soft reset does not touch the Working Directory or the Staging Index. It is only work in the Commit Tree. It moves back time to the specified commit.
The difference between Reset and Revert commands
The git reset command is a safe way to undoing changes comparing to the git reset
. There is a high probability that work can be lost using git reset. Git reset
does not delete a commit. However, it can make the commit "orphaned", which means that there is no direct access using a ref to attain them. Git will delete any orphaned commits when the internal garbage collector is triggered. By default, Git executes the internal garbage collector every 30 days. The orphaned commits can be found and restored using the git reflog.
Another face of difference between these two commands is that git revert
is suitable to undo public commits, while git reset
is suitable to undo local changes to the Working Directory and the Staging Index.
Try to avoid to Reset Public History
You should never use git reset <your-sha-commit>
command when there are snapshots after that are already pushed to a remote public repository. When you publish a commit, take into consideration that other developers depend on it too. Deleting commits that are being developed by other developers will cause a lot of problems in a project.
It is recommended practice to use git reset <your-sha-commit>
only on local changes, while for public changes, use the git revert
command instead.
Examples of using git reset
$ git reset <file_name>
The command above will remove the specified file from the Staging Index area without changing the Working Directory. It will unstage the specified file without overwriting any changes.
$ git reset
The command above will reset the Staging area to match the last commit. However, it will leave the Working Directory untouched. It will unstage all files from the Staging area without overwriting changes, offering the possibility to rebuild the staged snapshot from scratch.
$ git reset --hard
The command above will reset the Staging area and the Working Directory to match the last commit (The commit that the HEAD ref point). It will unstage all the pending work in the Staging area, and it will overwrite all changes in the Working Directory.
Be careful when using --hard
because you can lose pending work in the Staging area and the Working Directory.
$ git reset <commit_sha>
The command above will move the Commit History back in time to the specified commit. It will unstage all the pending work from the Staging area and move it to the Working Directory, and it will reset the Staging area to match the state of the specified commit. But it will not touch the Working Directory.
$ git reset --hard <commit_sha>
The command above will move the Commit History back in time and reset the Staging area and the Working Directory to match the specified commit.
Removing Local Commits
The example below presents a use case when you are working in a local git repository, and you have done different commits, but you decide that you want to get rid of them.
$ echo "initital content" >> demo_file
$ git add demo_file
$ git commit -m "initial commit for demo_file"
$ echo "more content" >> demo_file
$ git commit -am "adding content to demo_file"
$ echo "last content" >> demo_file
$ git commit -am "adding last content to demo_file"
$ git reset --hard HEAD^3
In the example above, we created a new file named "demo_file," and we committed the change of the file in three different commits. The git reset HEAD^3
moves the current branch backward by three commits.
This kind of reset should only be used for commits that are not published to a shared repository.
Unstaging files
The git reset
is usually used for preparing staged snapshots. In the example below, we have 2 files named "index.html" and "home.html" which have been already added to the repository. The git reset
command will help us to unstage the changes that are not related to the next commit.
git add .
git reset home.html
git commit -m "edit index.html"
git add .html
git commit -m "edit home.html"
In the above commands, we staged the two files, "home.html" and "index.html" using git add
. Then we realized that the change in "home.html" and "index.html" should be committed in different snapshots. So we used git reset
to unstage "home.html" so the first commit will just include "include.html".