git reset --explain
Tags: git, reset
Published: 2019-01-08 20:32:09


---
title: git reset --explain
published: true
cover_image: https://thepracticaldev.s3.amazonaws.com/i/15wzb8t5310txcukt7t9.png
tags: git, reset 
description: "Reset is probably one of the least understood git commands with the addition of having a bad reputation for being dangerous. There is a valid reason for both of these claims: yes, the reset command is a bit harder to understand and in some cases, it can be dangerous."
---

*Reset is probably one of the least understood git commands with the addition of having a bad reputation for being dangerous. There is a valid reason for both of these claims: yes, the reset command is a bit harder to understand and in some cases, it can be dangerous. But, it is not all that hard. So in this post, I will give my best to present you with a clear and distilled tutorial to the reset command. To make it short and not too overwhelming I have abstracted the non-essential details and simplified some things, but if you want to know more on git's internal workings you can also check my [Understanding Git](https://medium.com/hackernoon/https-medium-com-zspajich-understanding-git-data-model-95eb16cc99f5) series for more details of some stuff presented here.*

## Git Trees
Before we dive into the `reset` command we need to take a look at what is called the trees of git: Working Directory, Staging Area and the Repository.

![git thress](https://thepracticaldev.s3.amazonaws.com/i/ewai9wp289nvh3m73izy.png)

You can think of them as areas where changes can reside from git's point of view:
- Working Directory - your project files on your filesystem
- Staging Area - a preview of the next commit
- Repository - datastore where git keeps all (past) commits.

The `reset` command operates on these threes/areas, but first, let's take a look how do `add` and `commit` command that we use daily, affect these areas.

***

Say we have a web app and we do some refactoring on our `index.php` file. Changes that we make are reflected in the Working Directory:

![workflow-1](https://thepracticaldev.s3.amazonaws.com/i/nc420p77t0yb2bmbjmtx.png)

We can confirm this by running `git status`:
```
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
modified:   index.php
```

Now we move those changes to the Staging Area by using the `add` command:

![workflow-2](https://thepracticaldev.s3.amazonaws.com/i/i44nplrxytu1hv43vjgr.png)

And running the `status` command now will tell us:
```
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
modified:   index.php
```
Because the `status` command sees that we have the same version of `index.php` file both in Working Directory and in the Staging Area, but not in Repository.

To add it there, we use the `commit` command:

![workflow-3](https://thepracticaldev.s3.amazonaws.com/i/uhv9ufeloh7dzkarfkk7.png)

And now the Working Directory, Staging Area and Repository all contain the same version of `index.php`, and running `git status` will tell us that there is:
```
nothing to commit, working tree clean
```

So, the way the `status` command works is that it compares file versions in Working Directory, Staging Area and Repository and if there are different than there are files to be staged/committed.

***

Let's say we now refactor the `index.php` file some more and do the whole add/commit cycle again.
Now our Working Directory, Staging Area and the Repository all contain the new second version of our `index.php` file.

![workflow-4](https://thepracticaldev.s3.amazonaws.com/i/wo5oov64zdbky534d7kg.png)

But what about the first version? If you remember, we did say that the Repository keeps all previous commits, so the first version of `index.php` file is still there:

![workflow-5](https://thepracticaldev.s3.amazonaws.com/i/int99tvcvv72thk3cg3c.png)

To keep track which version of our `index.php` file is the current one, Repository has a special pointer called `HEAD` that points to the current version (and the `status` command only looks at the current version that `HEAD` is pointing to when comparing it to the Staging Area's version).

***

And now that we have that covered we can finally go to our `reset` command and see how it works by manipulating the content of these areas.

## reset --soft

This first mode of `reset` command will only do one thing:
- move the `HEAD` pointer.

In our case, we will move it to the previous commit (the first version of `index.php`) by doing: `git reset --soft HEAD~1`

![reset-soft-1](https://thepracticaldev.s3.amazonaws.com/i/x3hcn3x96tu9ti4hxdqn.png)

The trees of git now look like this:

![reset-soft-2](https://thepracticaldev.s3.amazonaws.com/i/0fuznoq5a904nhm4tje8.png)

And if we would to run `git status` we would see a familiar message:
```
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
modified:   index.php
```
So, running `git reset --soft HEAD~1` has basically undone our last commit, but the changes contained in that commit are not lost - they are in our Staging Area and Working Directory.

## reset - mixed
The second mode of the `reset` command will do two things:
- move the `HEAD` pointer
- update the Staging Area (with the content that the `HEAD` is pointing to)

So, the first step is the same as with the `--soft` mode. The second step takes the  content that `HEAD` points to (in this case, it is version one of the `index.php` file) and puts it into the Staging Area.

![reset-mixed-1](https://thepracticaldev.s3.amazonaws.com/i/0fuznoq5a904nhm4tje8.png)

So, after running `git reset --mixed HEAD~1` our areas look like this:

![reset-mixed-2](https://thepracticaldev.s3.amazonaws.com/i/voj8ggf56tnqx1tjbxe7.png)

And if we would to run `git status` now we would again see a familiar message:
```
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
modified:   index.php
```
So, running `git reset - mixed HEAD~1` has undone our last commit, but this time the changes from that commit are (only) in our Working Directory.

## reset --hard
And now for the notorious hard mode. Running `reset -- hard` will do three things:
- move the `HEAD` pointer
- update the Staging Area (with the content that the `HEAD` is pointing to)
- update the Working Directory to match the Staging Area

So, the first two steps are the same as with `--mixed`.The third makes the Working Directory look like the Staging Area (that was already filled with what `HEAD` is pointing to).

![reset-hard-1](https://thepracticaldev.s3.amazonaws.com/i/oyd3lrl6k3jg6o8s32vh.png)

So, after running `git reset --hard HEAD~1` our areas look like this:

![reset-hard-2](https://thepracticaldev.s3.amazonaws.com/i/shfnzr2d0drdd0fx9z62.png)

And running `git status` will give us:
```
nothing to commit, working tree clean
```

So, running `git reset - hard HEAD~1` has undone our last commit and the changes contained in that commit are now neither in our Working Directory or the Staging Area. But they are not completely lost. Git doesn't delete commits from Repository (actually, it does sometimes, but rarely), so this means our commit with the second version is still in the Repository, only it is a bit harder to find (you can track it by looking at something called *reflog*).

So, what is then with this reputation of reset being **dangerous**? Well, there is one case where some changes could be permanently lost. Consider a scenario where after the second commit you make some more changes to your `index.php` file, but you don't stage and commit them:

![reset-hard-3](https://thepracticaldev.s3.amazonaws.com/i/em97n1eqjue5x81fhf2f.png)

And now you run `git reset --hard HEAD~1`:

![reset-hard-4](https://thepracticaldev.s3.amazonaws.com/i/h7901w56tpcjl86js8v6.png)

Since the `reset` command will overwrite the content of your Working Directory to match the Staging Area (that is made to match `HEAD`) and you never staged and committed your changes (there is no commit with those changes in the repository), all those changes will now be lost in time... Like tears in the rain.

The danger of hard reset lies in the fact that it is not Working Directory safe - meaning it won't give you any warning if you have file changes in your Working Directory that will be overwritten (and lost) if you run it. So be (extra) careful with a hard reset.

***

And there you have it: the `reset` command. I hope I did a good job explaining it and that you'll agree it is not that hard after all. And, yes it can be dangerous but only if used with `--hard` option. 

As said in the beginning if you would like to know more on the inner workings of git, you can check my [Understanding Git](https://hackernoon.com/https-medium-com-zspajich-understanding-git-data-model-95eb16cc99f5) series, and if you want a more in-depth explanation on the `reset` command you can check the [Reset Demystified](https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified) chapter from git pro book.

### Appendix:

- In the examples, we used we used `HEAD~1` as an argument for the `reset` command. As you probably already know every commit in git has a unique identifier called a checksum, and we can use it as an argument for the `reset` command too

![checksums](https://thepracticaldev.s3.amazonaws.com/i/na01nuo4po352azewbtl.png)


- To make examples simpler we only had one file that we edited and committed, in reality, we often commit multiple files, so a specific commit holds different versions of multiple files.

![commits](https://thepracticaldev.s3.amazonaws.com/i/2fhxswfh7zkhtxwkz7pi.png)

- The special `HEAD` pointer usually doesn't point directly to a commit (as shown in examples for simplicity) but to a branch pointer that then points to a specific commit

![pointer](https://thepracticaldev.s3.amazonaws.com/i/47dvuc7hubinjwsqnftp.png)