Skip to content

Reset HEAD (git reset)


What Will We Learn?

Using git reset and it’s real world use cases. Don’t miss the real world analogies (starting here) for Git reset modes — without reset mode, you can’t understand about git reset at all.

What git reset Does?

git reset essentially moves the HEAD pointer to the specified <commit> of your branch. You should be extra cautious using git reset as it deletes or alters the history of your branch.

When you move the HEAD, several things can happen to your working directory and index states. It is determined by the reset mode you chose.

How git reset Works?

Suppose your commit graph looks like this where HEAD is currently pointing to commit D in the master branch:

    flowchart RL
    subgraph commits
    A
    B
    C
    D["D (master)"]
    B --> A
    C --> B
    D --> C
    end
    HEAD --> D
  

Now you ask the Git to move the HEAD to point the commit B executing like: git reset --hard B, Git moves HEAD to point B and now your commit graph looks like:

    flowchart RL
    subgraph commits
    A
    B["B (master)"]
    B --> A
    end
    HEAD --> B
  

This history of commit is gone and no longer referenced by the master branch. git log won’t show them now. So, yes, I again warn you.

Warning

git reset is a destructive operation that deletes history. Be extra cautions.

Git Reset Modes

Reset mode you choose while executing git reset decides what happen to the state of files in your working directory and the staging area. So, it becomes crucial to understand git reset modes before moving on.

TL;DR

TL;DR
git reset --{soft|hard|mixed} <commit>
  • Soft Reset moves HEAD to <commit>. Worktree and Index are untouched.
  • Hard Reset moves HEAD to <commit>. Both Worktree and Index are overridden by the content of <commit>.
  • Mixed Reset moves HEAD to <commit>. Worktree is untouched and Index is overridden. This is default.

The Setup

First of all I would suggest to execute the following script in new folder to set up.

git init
for address in {morang,koshi,nepal,asia,world,universe}; do
  echo "$address" > file
  git add file
  git commit -m "$address"
done

Content of file Per Commit

If you have set it up correctly, executing the following will give you the content of file per commit.

git log --follow --format='%h' -- file | while read commit; do
  echo "${commit}:$(git show "$commit:file")"
done

Content of file per commit (your commit hash will differ):

76e3668:universe
3cf3842:world
a3f1dd9:asia
9b32f6f:nepal
7ff0e7a:koshi
5a58ca0:morang

HEAD pointer

The first hash is the one what HEAD is currently pointing to.

git log --all --format='%h %D'

Output:

76e3668 HEAD -> master
3cf3842
a3f1dd9
9b32f6f
7ff0e7a
5a58ca0

Modifying the file

Also let’s modify the file and add it to index. And again modify the file.

echo "galaxy" > file
git add file
echo "supergalaxy" > file

What’s the git status?

On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   file

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

Notice both Worktree and Index are modified.

$cat file
supergalaxy

$git show :file
galaxy

Soft Reset

git reset --soft <commit>

Note

Soft Reset moves HEAD to <commit>. Worktree and Index are untouched.

What does it mean?

Now let’s git reset in --soft move to move the HEAD pointer to the commit 9b32f6f — the one with the content nepal.

git reset --soft 9b32f6f

Now if you again run the command that will give you commit history and content of file:

This time the output is:

9b32f6f:nepal
7ff0e7a:koshi
5a58ca0:morang

See all the prior commits are gone, git log does not show them, history is deleted and HEAD now points to 9b32f6f.

9b32f6f HEAD -> master
7ff0e7a 
5a58ca0 

What we have said earlier is that Worktree and Index are untouched can now be proved:

$cat file
supergalaxy

$git show :file
galaxy

This means git status won’t change either.

Hard Reset

git reset --hard <commit>

Note

Hard Reset moves HEAD to <commit>. Both Worktree and Index are overridden by the content of <commit>.

What does it mean?

Before moving on, you need to delete the directory you created for the setup and run the setup again. Actually, running this and this will set you up again. It will change your commit hash and nothing more than previous setup.

Now let’s git reset in --hard mode to move the HEAD pointer to the commit 9b32f6f — the one with the content nepal.

git reset --hard 9b32f6f

Hard Reset moves HEAD to <commit>. This part is already explained in soft reset. What actually matter here is — both Worktree and Index are overridden by the content of <commit> part.

The git status now is changed. Nothing is modifed, nothing to commit.

On branch master
nothing to commit, working tree clean

The is because:

both Worktree and Index are overridden by the content of <commit>

You content of file at commit 9b32f6f was nepal. And both Worktree and Index is overridden by the content nepal. So, nothing left to commit or even stage.

$cat file
nepal

$git show :file
nepal

Mixed Reset: The Default

git reset --mixed <commit>

Note

Mixed Reset moves HEAD to <commit>. Worktree is untouched and Index is overridden. This is default.

What does it mean?

Before moving on, you need to delete the directory you created for the setup and run the setup again. Actually, running this and this will set you up again. It will change your commit hash and nothing more than previous setup.

Now let’s git reset in --mixed mode to move the HEAD pointer to the commit 9b32f6f — the one with the content nepal.

For this, it’s worth revisiting the git status. And the content of file in Worktree and Index:

$cat file
supergalaxy

$git show :file
galaxy

Again the part that actually matter is — Worktree is untouched and Index is overridden. This is default.

git reset --mixed 9b32f6f

As said, Index is overridden.

$cat file
supergalaxy

$git show :file
nepal

You can see the differnce, previous output was galaxy, now is nepal. The git status also doesn’t match to previous version. This is because Index has been overridden to match the HEAD so no need to commit it again. But Worktree is untouched so still need to add and commit. Now the git status shows:

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

no changes added to commit (use "git add" and/or "git commit -a")

This all can be confusing and overwhelming in the first read. I recommend reading it twice and following each step. Each step counts.

Scenarios of Using git reset

Git reset modes have laid you the foundation so that you could actually understand when to use it. Common scenarios are discussed here. But a warning before that.

Warning

If you are working with teammates in a collaborative environment in remote repositories. Deleting history will raise a lot of confusion and introduce more recovery challange. So communicate well before altering history.

Reseting Accidental Commit

# You commit the changes
git commit -m "docs: add usage"

# You Realize it was not up to date or ready yet
# So you softly reset back to previous commit
git reset --soft HEAD~1

Why use soft reset here?

Because it will preserve any work in progress in your working directory and also staging area which you don’t want to lose.

Discarding All the Changes

After last commit, you may have made changes to files in the working directory and also you have staged changes. But now due to some reason, you no longer want all those change and want the previous clean state — hard reset is your friend here.

# You commit the changes
git commit -m "whatever change you made"

# You mess up with working dir
echo "nonsense" > file

# You add to index
git add unncessary/

# You realize all was mess, previous state was good, so clean it up with hard reset
git reset --hard HEAD~1

Unstaging File

If you stage something what you don’t want to include in your next commit, you can unstage that file with default `–mixed`` reset mode.

git reset file

Generally, git restore --staged file is used to unstage file which is simple because you don’t really have to understand behind the scenes of git reset.

Recovery

Mistakes do happen unintentionally, especially while altering the history. git reflog can be your friend here.

git reflog is a powerful feature that tracks your HEAD throught your usage. It knows where the HEAD has been. If you have accidentally reset and has nowhere to go, thanks to git reflog. Fire it up:

git reflog

A sample output:

bc14b4a (HEAD -> master) HEAD@{0}: reset: moving to bc14b4a
6100fcd HEAD@{1}: commit: feat: add user authentication
e4cdb31 HEAD@{2}: commit: feat: implement user registration
4efbb0e HEAD@{3}: commit: feat: initialize application structure
bc14b4a (HEAD -> master) HEAD@{4}: commit: chore: configure development environment
6c1001c HEAD@{5}: commit: docs: add project documentation
adcd864 HEAD@{6}: commit (initial): chore: initialize project

Now identify the commit hash of known good state and hard reset back to it.

git reset --hard <known-good-commit-hash>

You will thank git reflog.

Last updated on