Michael Chinen
Michael Chinen

Reputation: 18697

What's the difference between git reset --mixed, --soft, and --hard?

I'm looking to split a commit up and not sure which reset option to use.

I was looking at the page In plain English, what does "git reset" do?, but I realized I don't really understand what the git index or staging area is and thus the explanations didn't help.

Also, the use cases for --mixed and --soft look the same to me in that answer (when you want to fix and recommit). Can someone break it down even more? I realize --mixed is probably the option to go with, but I want to know why. Lastly, what about --hard?

Can someone give me a workflow example of how selecting the 3 options would happen?

Upvotes: 1204

Views: 448130

Answers (19)

Ahmed El-Tabarani
Ahmed El-Tabarani

Reputation: 753

Common Info

The three options --soft, --mixed and --hard share common info like:

  • They all move the branch and HEAD simultaneously, not just the HEAD to the specific commit hash
  • When commit hash is not specified, the HEAD is being the default
  • Reverting the command can be done using reflog and reset to return to the original commit that the HEAD was on before

--soft

  • It doesn't modify the Working Directory
  • It doesn't do anything with files in the Staging
  • The modifications that occurred between HEAD and commit hash are moved to the Staging

--mixed

  • It doesn't modify the Working Directory
  • All files in the Staging are removed from the Staging and moved to the Working Directory
    as if you performed git restore --staged
  • The modifications that occurred between HEAD and commit hash are not moved to the Staging
  • --mixed is the default value for git reset

We can say that --mixed equals --soft but:

  1. without executing git add
  2. Remove all files from the Staging to the Working Directory

Fun fact: What you see in git status after executing --mixed, after we perform git add ., we will get the same git status as if we executed --soft

--hard

--hard is dangerous and should be used with caution because:

  • It moves the HEAD to the commit hash and forced the changes on the Working Directory
  • It gets rid of any modifications in the Working Directory without a way to recover them
    meaning, it discards the Modified Files
  • It also rid of any files in the Staging

Note: Beware of using --hard as it will affect the Working Directory and force it matchs the specified commit And any Modified Files in the Working Directory will be discarded

So, before using git reset --hard and if there are files currently being modified in the Working Directory, you should move these modifications elsewhere before executing git reset --hard. This is where git stash comes in and help you

Summarizing the differences between soft, mixed, and hard

Selection Working Directory Staged Files Unstaged Modified Files Changes made between HEAD and the specified commit
soft remain as is remain as is Moved to the Staging Area Moved to the Staging Area
mixed remain as is Moved to the Working Directory They remain as they are in the Working Directory They remain as they are in the Working Directory
hard Forced to be replaced to make the Working Directory match the version it was in the specified commit Get rid of it Get rid of it Get rid of it

Upvotes: 4

mkarasek
mkarasek

Reputation: 23349

When you modify a file in your repository, the change is initially unstaged. In order to commit it, you must stage it—that is, add it to the index—using git add. When you make a commit, the changes that are committed are those that have been added to the index.1

git reset changes, at minimum, where the current branch2 is pointing. The difference between --mixed and --soft is whether or not your index is also modified. So, if we're on branch master with this series of commits:

- A - B - C (master)

HEAD (the current commit) is C3 and the index matches C (assuming we haven't staged any changes with git add).

When we run git reset --soft B, master (and thus HEAD, indirectly) now points to B, but the index still has the changes from C; git status will show them as staged. So if we run git commit at this point, we'll get a new commit with the same changes as C.

(If we did have staged changes before the reset, those will still be in the index and git commit would commit those changes in addition to the changes from C.)


Okay, so starting from here again:

- A - B - C (master)

Now let's do git reset --mixed B. (--mixed is the default option, so this is equivalent to git reset B). Once again, master and HEAD point to B, but this time the index is also modified to match B. If we run git commit at this point, nothing will happen since the index matches HEAD. We still have the changes in the working directory, but since they're not in the index, git status shows them as unstaged. To commit them, you would git add and then commit as usual.

(This time, if we had staged changes before the reset, those changes will be removed from the index, but the files themselves will still contain any changes that were made.)


And finally, --hard is the same as --mixed (it changes your HEAD and index), except that --hard also modifies your working directory. If we're at C and run git reset --hard B, then the changes added in C, as well as any uncommitted changes you've made, will be removed, and the files in your working copy will match commit B. Since you can permanently lose changes this way, you should always run git status before doing a hard reset to make sure your working directory is clean or that you're okay with losing your uncommitted changes.


In each of the above cases, we specified a commit (B) to reset to. If you don't provide one, it uses HEAD as the default. git reset --soft with no commit specified doesn't do anything (it makes the current branch point to where it was already pointing), but it can be useful for the other two modes. git reset --mixed (or just git reset) updates the index to match the current commit, which has the effect of unstaging any changes that have been added but not committed; git reset --hard does the same, plus it removes any changes in your working directory so that all your files match the latest commit on your current branch. (Again, since those changes are permanently lost, you should be careful when doing a hard reset.)


1 The index is also referred to as the "staging area". You can think of it as another copy of all the files in the repository. When a branch is checked out, the index is updated so that all its files match the contents of that branch. When you git add a file, any changes you made to that file are copied to the index, and when you git commit, the contents of the index are turned into a commit and added to the current branch.

2 HEAD is how Git refers to the commit that is currently checked out. HEAD (in most cases) points to a specific branch, called the current branch, and that branch points to a specific commit.

3 Because HEAD points to master and master points to C. Whenever we add or remove a commit on the current branch, the commit that HEAD refers to changes not because HEAD itself changed, but because the branch that HEAD points to was updated to point to that commit.

Upvotes: 2136

artfulrobot
artfulrobot

Reputation: 21397

  • All types of reset change the HEAD in the repo. Additionally...
  • git reset --soft <B> moves the changes from commits removed from repo into the index, merging in any that were there already.
  • git reset --hard <b> loses the changes in the working tree and the index.
    It is the only one to change the working tree.

diagram supporting text explanation

Upvotes: 2

Vivek Maru
Vivek Maru

Reputation: 8499

mkarasek's Answer is great, in simple terms we can say...

  • git reset --soft : set the HEAD to the intended commit but keep your changes staged from last commits
  • git reset --mixed : it's same as git reset --soft but the only difference is it un stage your changes from last commits
  • git reset --hard : set your HEAD on the commit you specify and reset all your changes from last commits including un committed changes.

--soft and --mixed are a bit similar, the only difference is, if you want to keep your changes in staging area use --soft, and if you don't want your changes in staging area use --mixed instead.

Upvotes: 16

matt
matt

Reputation: 534895

Three types of regret

A lot of the existing answers don't seem to answer the actual question. They are about what the commands do, not about what you (the user) want — the use case. But that is what the OP asked about!

It might be more helpful to couch the description in terms of what it is precisely that you regret at the time you give a git reset command. Let's say we have this:

A - B - C - D <- HEAD

Here are some possible regrets and what to do about them:

1. I regret that B, C, and D are not one commit.

git reset --soft A. I can now immediately commit and presto, all the changes since A are one commit.

2. I regret that B, C, and D are not two commits (or ten commits, or whatever).

git reset --mixed A. The commits are gone and the index is back at A, but the work area still looks as it did after D. So now I can add-and-commit in a whole different grouping.

3. I regret that B, C, and D happened on this branch; I wish I had branched after A and they had happened on that other branch.

Make a new branch otherbranch, and then git reset --hard A. The current branch now ends at A, with otherbranch stemming from it and containing B, C, and D.

(Of course you could also use a hard reset because you wish B, C, and D had never happened at all.)

Upvotes: 133

CDT
CDT

Reputation: 10621

Mo Ali has put it in simplest terms and here's another simple explanation:

--soft: reset HEAD pointer to previous commit

--mixed:--soft + delete added changes

--hard: --mixed + recover working tree file changes (CAREFUL!)

Upvotes: 1

Tomer Ben David
Tomer Ben David

Reputation: 8886

In these cases I like a visual that can hopefully explain this:

git reset --[hard/mixed/soft] :

enter image description here

So each one affects different scopes:

  1. Hard => WorkingDir + Index + HEAD
  2. Mixed => Index + HEAD
  3. Soft => HEAD only (index and working dir unchanged).

Upvotes: 61

Orion
Orion

Reputation: 245

--mixed vs --soft vs --hard:

--mixed:

   Delete changes from the local repository and staging area.

   It won't touch the working directory.

   Possible to revert back changes by using the following commands.

     - git add

     - git commit

   Working tree won't be clean.

--soft:

    Deleted changes only from the local repository.

    It won't touch the staging area and working directory.

    Possible to revert back changes by using the following command.

     - git commit.

    Working tree won't be clean

--hard:

    Deleted changes from everywhere.

    Not possible to revert changes.

    The working tree will be clean.

NOTE: If the commits are confirmed to the local repository and to discard those commits we can use:

 `git reset command`.

But if the commits are confirmed to the remote repository then not recommended to use the reset command and we have to use the revert command to discard the remote commits.

Upvotes: 11

Via_fx_24
Via_fx_24

Reputation: 309

I’m not a git expert and just arrived on this forum to understand it! Thus maybe my explanation is not perfect, sorry for that. I found all the other answer helpful and I will just try to give another perspective. I will modify a bit the question since I guess that it was maybe the intent of the author: “I’m new to git. Before using git, I was renaming my files like this: main.c, main_1.c, main_2.c when i was performing majors changes in order to be able to go back in case of trouble. Thus, if I decided to come back to main_1.c, it was easy and I also keep main_2.c and main_3.c since I could also need them later. How can I easily do the same thing using git?” For my answer, I mainly use the “regret number three” of the great answer of Matt above because I also think that the initial question is about “what do I do if I have regret when using git?”. At the beginning, the situation is like that:

A-B-C-D (master)

  1. The first main point is to create a new branch: git branch mynewbranch. Then one get:

A-B-C-D (master and mynewbranch)

  1. Let’s suppose now that one want to come back to A (3 commits before). The second main point is to use the command git reset --hard even if one can read on the net that it is dangerous. Yes, it’s dangerous but only for uncommitted changes. Thus, the way to do is:

Git reset --hard thenumberofthecommitA

or

Git reset --hard master~3

Then one obtains: A (master) – B – C – D (mynewbranch)

Then, it’s possible to continue working and commit from A (master) but still can get an easy access to the other versions by checking out on the other branch: git checkout mynewbranch. Now, let’s imagine that one forgot to create a new branch before the command git reset --hard. Is the commit B, C, D are lost? No, but there are not stored in any branches. To find them again, one may use the command : git reflog that is consider as “a safety command”( “in case of trouble, keep calm and use git reflog”). This command will list all commits even those that not belong to any branches. Thus, it’s a convenient way to find the commit B, C or D.

Upvotes: 0

timhc22
timhc22

Reputation: 7451

Please be aware, this is a simplified explanation intended as a first step in seeking to understand this complex functionality.

May be helpful for visual learners who want to visualise what their project state looks like after each of these commands:

Given: - A - B - C (master)


For those who use Terminal with colour turned on (git config --global color.ui auto):

git reset --soft A and you will see B and C's stuff in green (staged and ready to commit)

git reset --mixed A (or git reset A) and you will see B and C's stuff in red (unstaged and ready to be staged (green) and then committed)

git reset --hard A and you will no longer see B and C's changes anywhere (will be as if they never existed)


Or for those who use a GUI program like 'Tower' or 'SourceTree'

git reset --soft A and you will see B and C's stuff in the 'staged files' area ready to commit

git reset --mixed A (or git reset A) and you will see B and C's stuff in the 'unstaged files' area ready to be moved to staged and then committed

git reset --hard A and you will no longer see B and C's changes anywhere (will be as if they never existed)

Upvotes: 104

magentaqin
magentaqin

Reputation: 2139

You don't have to force yourself to remember differences between them. Think of how you actually made a commit.

  1. Make some changes.

  2. git add .

  3. git commit -m "I did Something"

Soft, Mixed and Hard is the way enabling you to give up the operations you did from 3 to 1.

  • Soft "pretended" to never see you have did git commit.
  • Mixed "pretended" to never see you have did git add .
  • Hard "pretended" to never see you have made file changes.

Upvotes: 42

De Novo
De Novo

Reputation: 7600

There are a number of answers here with a misconception about git reset --soft. While there is a specific condition in which git reset --soft will only change HEAD (starting from a detached head state), typically (and for the intended use), it moves the branch reference you currently have checked out. Of course it can't do this if you don't have a branch checked out (hence the specific condition where git reset --soft will only change HEAD).

I've found this to be the best way to think about git reset. You're not just moving HEAD (everything does that), you're also moving the branch ref, e.g., master. This is similar to what happens when you run git commit (the current branch moves along with HEAD), except instead of creating (and moving to) a new commit, you move to a prior commit.

This is the point of reset, changing a branch to something other than a new commit, not changing HEAD. You can see this in the documentation example:

Undo a commit, making it a topic branch

          $ git branch topic/wip     (1)
          $ git reset --hard HEAD~3  (2)
          $ git checkout topic/wip   (3)
  1. You have made some commits, but realize they were premature to be in the "master" branch. You want to continue polishing them in a topic branch, so create "topic/wip" branch off of the current HEAD.
  2. Rewind the master branch to get rid of those three commits.
  3. Switch to "topic/wip" branch and keep working.

What's the point of this series of commands? You want to move a branch, here master, so while you have master checked out, you run git reset.

The top voted answer here is generally good, but I thought I'd add this to correct the several answers with misconceptions.

Change your branch

git reset --soft <ref>: resets the branch pointer for the currently checked out branch to the commit at the specified reference, <ref>. Files in your working directory and index are not changed. Committing from this stage will take you right back to where you were before the git reset command.

Change your index too

git reset --mixed <ref>

or equivalently

git reset <ref>:

Does what --soft does AND also resets the index to the match the commit at the specified reference. While git reset --soft HEAD does nothing (because it says move the checked out branch to the checked out branch), git reset --mixed HEAD, or equivalently git reset HEAD, is a common and useful command because it resets the index to the state of your last commit.

Change your working directory too

git reset --hard <ref>: does what --mixed does AND also overwrites your working directory. This command is similar to git checkout <ref>, except that (and this is the crucial point about reset) all forms of git reset move the branch ref HEAD is pointing to.

A note about "such and such command moves the HEAD":

It is not useful to say a command moves the HEAD. Any command that changes where you are in your commit history moves the HEAD. That's what the HEAD is, a pointer to wherever you are. HEADis you, and so will move whenever you do.

Upvotes: 11

Hansen W
Hansen W

Reputation: 1098

All the other answers are great, but I find it best to understand them by breaking down files into three categories: unstaged, staged, commit:

  • --hard should be easy to understand, it restores everything
  • --mixed (default) :
    1. unstaged files: don't change
    2. staged files: move to unstaged
    3. commit files: move to unstaged
  • --soft:
    1. unstaged files: don't change
    2. staged files: dont' change
    3. commit files: move to staged

In summary:

  • --soft option will move everything (except unstaged files) into staging area
  • --mixed option will move everything into unstaged area

Upvotes: 63

Mo Ali
Mo Ali

Reputation: 7640

In the simplest terms:

  • --soft: uncommit changes, changes are left staged (index).
  • --mixed (default): uncommit + unstage changes, changes are left in working tree.
  • --hard: uncommit + unstage + delete changes, nothing left.

Upvotes: 732

Nesha Zoric
Nesha Zoric

Reputation: 6620

--soft: Tells Git to reset HEAD to another commit, so index and the working directory will not be altered in any way. All of the files changed between the original HEAD and the commit will be staged.

--mixed: Just like the soft, this will reset HEAD to another commit. It will also reset the index to match it while working directory will not be touched. All the changes will stay in the working directory and appear as modified, but not staged.

--hard: This resets everything - it resets HEAD back to another commit, resets the index to match it, and resets the working directory to match it as well.

The main difference between --mixed and --soft is whether or not your index is also modified. Check more about this here.

Upvotes: 2

Vishwas Abhyankar
Vishwas Abhyankar

Reputation: 406

Basic difference between various options of git reset command are as below.

  • --soft: Only resets the HEAD to the commit you select. Works basically the same as git checkout but does not create a detached head state.
  • --mixed (default option): Resets the HEAD to the commit you select in both the history and undoes the changes in the index.
  • --hard: Resets the HEAD to the commit you select in both the history, undoes the changes in the index, and undoes the changes in your working directory.

Upvotes: 2

Suresh Sharma
Suresh Sharma

Reputation: 1844

Before going into these three option one must understand 3 things.

1) History/HEAD

2) Stage/index

3) Working directory

reset --soft : History changed, HEAD changed, Working directory is not changed.

reset --mixed : History changed, HEAD changed, Working directory changed with unstaged data.

reset --hard : History changed, HEAD changed, Working directory is changed with lost data.

It is always safe to go with Git --soft. One should use other option in complex requirement.

Upvotes: 11

Nickpick
Nickpick

Reputation: 6587

A short answer in what context the 3 options are used:

To keep the current changes in the code but to rewrite the commit history:

  • soft: You can commit everything at once and create a new commit with a new description (if you use torotise git or any most other GUIs, this is the one to use, as you can still tick which files you want in the commit and make multiple commits that way with different files. In Sourcetree all files would be staged for commit.)
  • mixed: You will have to add the individual files again to the index before you make commits (in Sourcetree all the changed files would be unstaged)

To actually lose your changes in the code as well:

  • hard: you don't just rewrite history but also lose all your changes up to the point you reset

Upvotes: 2

James Lawruk
James Lawruk

Reputation: 31337

Here is a basic explanation for TortoiseGit users:

git reset --soft and --mixed leave your files untouched.

git reset --hard actually change your files to match the commit you reset to.

In TortoiseGit, The concept of the index is very hidden by the GUI. When you modify a file, you don't have to run git add to add the change to the staging area/index. When simply dealing with modifications to existing files that are not changing file names, git reset --soft and --mixed are the same! You will only notice a difference if you added new files or renamed files. In this case, if you run git reset --mixed, you will have to re-add your file(s) from the Not Versioned Files list.

Upvotes: 26

Related Questions