What are the differences between "soft, Mixed, hard" in git reset?

Asked

Viewed 2,334 times

14

I would like to understand better what each type of reset in the git makes with the file(s) (s):

Types:

  • $ git reset --soft [commithash]
  • $ git reset --mixed [commithash]
  • $ git reset --hard [commithash]

3 answers

12


To show better what the git reset makes, first let’s establish a "base repository" and from it use the different options of reset.

So the first part will be about the setup of the "scenario", which was a little long and it seems that has nothing to do with the question, but be patient that at the end (hopefully) you will understand, because in the second part I use this "scenario" as a basis to show what each option of reset do. Come on:

Setting up the "scenario"

I set up a small repository with just a file called Arq.txt, 3-line:

$ ls
arq.txt

$ cat arq.txt 
primeiro
segundo
terceiro

Currently, the repository has 3 commits:

$ git log --oneline 
668185f (HEAD -> master) terceiro commit
8bc39c6 segundo commit
36da346 primeiro commit

$ git status
On branch master
nothing to commit, working tree clean

The git status shows that the file has no modifications compared to the last commit.

We can also see the difference between commits, so we know what has changed from one to another (this will be important to understand what the different options of reset do).

First let’s see the difference between the first and second commit:

$ git diff 36da346 8bc39c6
diff --git a/arq.txt b/arq.txt
index 98fdf1f..e274503 100644
--- a/arq.txt
+++ b/arq.txt
@@ -1 +1,2 @@
 primeiro
+segundo

The difference is that the "second" line was added to the file (indicated by the sign + right before the "second" text). Already between the second and third commit:

$ git diff 8bc39c6 668185f
diff --git a/arq.txt b/arq.txt
index e274503..c14b6c1 100644
--- a/arq.txt
+++ b/arq.txt
@@ -1,2 +1,3 @@
 primeiro
 segundo
+terceiro

Added the line "third".

That is, in the first commit the file had 1 line, in the second commit the second line was added, and in the third commit the third line was added (and this is how the file is currently).

Now I’ll add one more line to the file and run git add:

$ echo quarto >> arq.txt
$ cat arq.txt 
primeiro
segundo
terceiro
quarto

$ git add arq.txt
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   arq.txt

The file is marked to go to the next commit ("Changes to be Committed"). But instead of committing, I’ll add one more line to the file (but I won’t run git add):

$ echo quinto >> arq.txt 
$ cat arq.txt 
primeiro
segundo
terceiro
quarto
quinto

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   arq.txt

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

Notice that now the file is both in "Changes to be Committed" (will go in the next commit) as in "Changes not staged for commit" (will not go in the next commit). And understanding why this happens is important to understand what each option of reset ago.

Why does this happen?

This scenario I put together serves to understand some important concepts.

The first is the Working directory (working directory, or Working Tree, or Working dir). It’s basically what’s on your disk. In this case, it’s the file I’m moving, with the 5 lines:

$ cat arq.txt 
primeiro
segundo
terceiro
quarto
quinto

These changes will only be in the next commit if I send them to staging area (that is also called index or cache). That’s what happens when I do git add arquivo: the contents of the file (which is in the Working dir) is sent to the staging area.

In this case, only the fourth line is in the staging area (I made a git add when the file had 4 lines), but the fifth line did not (because it was edited afterward of git add).

We can see the differences using diff:

$ git diff --cached 
diff --git a/arq.txt b/arq.txt
index c14b6c1..f9efd34 100644
--- a/arq.txt
+++ b/arq.txt
@@ -1,3 +1,4 @@
 primeiro
 segundo
 terceiro
+quarto

The option --cached serves to compare the cache (another name for staging area) with the last commit. And note that the difference is the fourth line.

Already diff without arguments compares the Working dir with the staging area:

$ git diff
diff --git a/arq.txt b/arq.txt
index f9efd34..a166d58 100644
--- a/arq.txt
+++ b/arq.txt
@@ -2,3 +2,4 @@ primeiro
 segundo
 terceiro
 quarto
+quinto

And notice the difference is the fifth line.

This is why the file is listed at the same time to go and not go in the next commit. The fourth line is in the staging area and will go to the next commit. The fifth line is in Working dir but is not in the staging area and will not go on the next commit.

Another way to check what’s in the staging area is with the command ls-files:

$ git ls-files -s
100644 f9efd34b6e87df23f7b0c0275daaacff6b7ef8f0 0   arq.txt

And to see the contents of the file, just use cat-file passing the file hash (like everything in git, it doesn’t have to be the whole hash, just an unambiguous initial piece):

$ git cat-file -p f9efd34b6e
primeiro
segundo
terceiro
quarto

As we can see, the file that’s in the staging area has the fourth line.


Using reset

Now that we have the initial scenario, let’s see what each reset option does. Remembering our initial situation, we have 3 commits:

$ git log --oneline 
668185f (HEAD -> master) terceiro commit
8bc39c6 segundo commit
36da346 primeiro commit

HEAD (which is the pointer to the current branch) is pointing to the master (which is the standard branch). Basically, HEAD tells us where we are, and if we create another commit, it will be placed after the commit that HEAD points to.

In the staging area we have the fourth line of the file (which will be in the next commit), and Working dir we have the fourth and fifth lines.

--soft

The option --soft moves the HEAD to the specified commit, without changing the Working dir and the staging area. That is, if I pass the hash of the second commit as a parameter:

$ git reset --soft 8bc39c6

Now HEAD points to the given commit:

$ git log --oneline 
8bc39c6 (HEAD -> master) segundo commit
36da346 primeiro commit

But the Working dir continues the same (the file on my disk continues with 5 lines):

$ cat arq.txt 
primeiro
segundo
terceiro
quarto
quinto

And the staging area continues with 4 lines:

$ git diff --cached
diff --git a/arq.txt b/arq.txt
index e274503..f9efd34 100644
--- a/arq.txt
+++ b/arq.txt
@@ -1,2 +1,4 @@
 primeiro
 segundo
+terceiro
+quarto

Note that now the diff identified that the third and fourth lines were added. This happens because now the last commit is the second, and in this commit the file only had 2 lines. By comparing it with the staging area (that is with 4 lines), the difference is in the third and fourth lines.

Let’s also check with ls-files:

$ git ls-files -s
100644 f9efd34b6e87df23f7b0c0275daaacff6b7ef8f0 0   arq.txt

Note that the file hash remains the same, which indicates that the staging area was not changed:

$ git cat-file -p f9efd34b6e
primeiro
segundo
terceiro
quarto

The file that’s on staging area continues with the fourth line. That is, reset --soft did not change its content.

--mixed

The option --mixed is the default if none of the options are provided - but in the following example I will put it just to make it clear that I am using it.

Recalling that I’m starting from the initial "scenario": 3 commits (HEAD points to the third commit 668185f), the file on staging area has 4 lines and in the Working dir has 5 lines.

This option does the same as --soft (moves the HEAD to the specified commit), but does not stop there. It then updates the staging area with the content that’s in this commit:

$ git reset --mixed 8bc39c6
Unstaged changes after reset:
M   arq.txt

Now HEAD points to the 8bc39c6 commit:

$ git log --oneline 
8bc39c6 (HEAD -> master) segundo commit
36da346 primeiro commit

And the staging area was updated with the same commit content (so much so that the reset generated the message "Unstaged changes after reset", indicating that something has changed in the staging area).

We can see that diff --cached shows no more difference:

$ git diff --cached 
(não mostra nada)

That is, the staging area is the same as the last commit (which in this case is the second commit, where the file only has 2 lines). Let’s check with ls-files:

$ git ls-files -s
100644 e2745035197cf4b209887f1fcc056f1afe7ff23d 0   arq.txt

The hash of the file has changed (it is no longer "f9efd34b6e..."), which indicates that in fact the staging area has been changed. Let’s see its contents:

$ git cat-file -p e2745035
primeiro
segundo

And indeed the staging area is only two lines, identical to the second commit. But the file in my Working dir continues with 5 lines:

$ cat arq.txt 
primeiro
segundo
terceiro
quarto
quinto

--hard

The option --hard does everything that --mixed does, but also overrides the contents of my Working dir.

Recalling that I’m starting from the initial "scenario": 3 commits (HEAD points to the third commit 668185f), the file on staging area has 4 lines and in the Working dir has 5 lines.

$ git reset --hard 8bc39c6
HEAD is now at 8bc39c6 segundo commit

First let’s see if he did the same as --mixed: HEAD must be in the second commit and the staging area must be the same as this commit (i.e., the file must be two lines long):

$ git log --oneline 
8bc39c6 (HEAD -> master) segundo commit
36da346 primeiro commit

$ git diff --cached
(não mostra nada)

$ git ls-files -s
100644 e2745035197cf4b209887f1fcc056f1afe7ff23d 0   arq.txt
$ git cat-file -p e2745035
primeiro
segundo

As we can see, --hard did the same as --mixed. But he did something else: man Working dir was overwritten with the same content as the second commit:

$ cat arq.txt 
primeiro
segundo

In short

  • --soft: moves HEAD to the other commit
  • --mixed: moves the HEAD and overwrites the staging area
  • --hard: move HEAD, override the staging area and the Working dir

This example was clearly copied inspired by this article. I recommend that you read, because it has more detailed examples, especially for cases where you pass files as parameters (because then the behavior changes a little), among other use cases - but as these additional cases are not the focus of the question, I decided to stop here, because it’s already become giant.

  • PS: I forgot to mention that in my environment the git log is like a alias and in fact the complete command would be git log --oneline --decorate, that shows (HEAD -> master) on exit (without the decorate, that is not shown).

5

Soft

Do not touch the index file or the work tree (but reset the head, or HEAD, as well as all modes). This leaves all your files changed to "Changes to be committed" as the git status would put.

Mixed

Reset the index, but not the working tree (i.e., the changed files are preserved, but not marked for confirmation) and report what has not been updated. This is the default action. If -N is specified, the removed paths are marked as adding intent.

Hard

Reset the index and the work tree. Any changes to the files tracked in the work tree are discarded.


Now to better understand the concepts of repository, Working Tree (tree or working directory) and index:

Repository

The container that keeps all your commits and files within a certain timeline.

Working tree

Commonly called Working Tree, it represents the directory where you actually work, modifying or adding files to be committed in the future.

Index or staging area

Where commits are prepared, that is, the index compares the files in the work tree with the files in the repository and so any change in the work tree is marked by the index before it is confirmed.

  • Welcome to Sopt Felipemm! I appreciate your answer right now, but I still have questions. I just saw about git, so I don’t understand much! rs What would be "index file" and "work tree"?

  • 1

    soft: changes the index of the HEAD ("head" was to kill...) for the last commit actually performed and the changes made continue in the Stage area; Mixed: same thing of the soft, except that the changes do not remain in the Stage area (ie, you would have to git add again to the desired files); hard: moves the HEAD to the last commit and PHYSICALLY removes your changes (so use carefully).

  • 1

    Rbz works as follows: We have the repository, Working Tree (working tree or working directory) and index. The repository is the container that keeps all your commits and files within a given timeline. The working tree is where you actually modify or add files to be commited. The index or staging area, on the other hand, is where commits are prepared, that is, the index compares the files in the work tree with the files in the repository and so any change in the work tree is marked by the index before it is confirmed.

  • You gave me a clearer vision! What Felipemm would like, is that in this way he explained, he added this in his question, in an explanatory form for the total beginners in git (which is my case). So your answer would be quite complete, and that’s good for the next ones who come looking about it! It would help a lot of people! Give it a whirl! rs

  • Perfect Rbz, I’ll do it

1

Reset Soft Commit Undo. Keep Stage and modifications.

Reset Mixed Undo Commit and Stage. Keeps modifications.

Reset Hard Undo Commit, Stage and modifications.

Stage "are the files selected for Commit".

Browser other questions tagged

You are not signed in. Login or sign up in order to post.