What is difference between 'git reset --hard HEAD~1' and 'git reset --soft HEAD~1'?
Asked Answered
G

5

156

I tried to undo my commit in git. Is it dangerous to use git reset --hard HEAD~1?

What is the difference between different options for git reset?

Note: while HEAD is the current/latest commit, HEAD~1 is the commit right before HEAD.

Guillermoguilloche answered 4/7, 2014 at 7:32 Comment(1)
possible duplicate of Whats the difference between git reset --mixed, --soft, and --hard?Septi
O
318

git reset does know five "modes": soft, mixed, hard, merge and keep. I will start with the first three, since these are the modes you'll usually encounter. After that you'll find a nice little a bonus, so stay tuned.


Let's assume you have a repository with a history akin to this:

7e05a95  (HEAD -> main) Update a
e62add5  Update b
ca9ae0a  Update a
9b6060d  Add c
eebe372  Add b
947586a  Add a

Where the latest commit (7e05a95) contains these changes:

diff --git a/a b/a
index b66ba06..28b68e2 100644
--- a/a
+++ b/a
@@ -1 +1 @@
-new content
+new new content

Now what would happen when you run git reset with the various different modes? Let's find out!

soft

When using git reset --soft HEAD~1 you will remove the last commit from the current branch, but the file changes will stay in your working tree. Also, the changes will stay on your index, so following with a git commit will create a commit with the exact same changes as the commit you "removed" before.

How would this look like in practice? Like this:

> git reset --soft HEAD^ # Assuming HEAD points at 7e05a95
> git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    modified:   a

As you see the changes in file a are on the index, and ready to be committed again.

mixed

This is the default mode and quite similar to soft. When "removing" a commit with git reset HEAD~1 you will still keep the changes in your working tree but not on the index; so if you want to "redo" the commit, you will have to add the changes (git add) before commiting.

In practice the result might look like this:

> git reset --mixed HEAD^ # Assuming HEAD points at 7e05a95
> git status
On branch main
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:   a

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

The changes of file a are still there but they're not on the index.

hard

When using git reset --hard HEAD~1 you will lose all uncommited changes and all untracked files in addition to the changes introduced in the last commit. The changes won't stay in your working tree so doing a git status command will tell you that you don't have any changes in your repository.

Tread carefully with this one. If you accidentally remove uncommited changes which were never tracked by git (speak: committed or at least added to the index), you have no way of getting them back using git.

A practical example might look like this:

> git reset --hard HEAD^ # Assuming HEAD points at 7e05a95
> git status
On branch main
nothing to commit, working tree clean

As you can see, no changes remain. Assuming you also had some uncommitted changes in the file b these would be lost too!

> echo 'some uncommitted changes' > b
> git status
On branch main
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:   b

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

> git reset --hard HEAD^ # Assuming HEAD points at 7e05a95
> git status
On branch main
nothing to commit, working tree clean

Bonus

keep

git reset --keep HEAD~1 is an interesting and useful one. It only resets the files which are different between the current HEAD and the given commit. It aborts the reset if one or more of these files has uncommited changes. It basically acts as a safer version of hard.

Let's revisit the example from before, where you had some uncommitted changes in b:

> echo 'some uncommitted changes' > b
> git status
On branch main
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:   b

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

> git reset --keep HEAD^ # Assuming HEAD points at 7e05a95
> git status
On branch main
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:   b

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

You removed the changes in file a but retained the uncommitted changes in file b!

So to reiterate: "hard" will remove all changes while "keep" only removes changes from the reset commit(s).


Each of these modes is explained in depths in the git reset documentation.

Note
When doing git reset to remove a commit the commit isn't really lost, there just is no reference pointing to it or any of it's children. You can still recover a commit which was "deleted" with git reset by finding it's SHA-1 key, for example with a command such as git reflog.

Omit answered 4/7, 2014 at 7:45 Comment(4)
I disagree that these 3 are the ones we should usually use. They are the 3 that were first available so people talk about these 3 more, but --hard is almost never the right thing to do, as --keep is much safer and applies to most of the senarios where --hard works. Training your fingers to use --keep might save you, one day ...Muskmelon
I didn't try to suggest that we should use them, merely that these are commands one encounters most of the time. Feel free to edit the answer as you see fit.Omit
To add a bit more details, after git reset --soft HEAD~1, using git commit --reuse-message=HEAD@{1} to reuse the last commit with the preserved old index as shown here https://mcmap.net/q/12379/-git-how-to-reuse-retain-commit-messages-after-39-git-reset-39Ingest
@MatthieuMoy, three years late but I added a section on keep. ;)Omit
U
13

Git reset has 5 main modes: soft, mixed, merge, hard, keep. The difference between them is to change or not change head, stage (index), working directory.

Git reset --hard will change head, index and working directory.
Git reset --soft will change head only. No change to index, working directory.

So in other words, if you want to undo your commit, --soft should be good enough. But after that, you still have the changes from bad commit in your index and working directory. You can modify the files, fix them, add them to index and commit again.

With the --hard, you completely get a clean slate in your project. As if there hasn't been any change from the last commit. If you are sure this is what you want, then move forward. But once you do this, you'll lose your last commit completely. (Note: there are still ways to recover the lost commit).

Ultramarine answered 4/7, 2014 at 7:44 Comment(0)
M
5

This is a useful article which graphically shows the explanation of the reset command.

https://web.archive.org/web/20171119010424/http://git-scm.com/blog/2011/07/11/reset.html

Reset --hard can be quite dangerous as it overwrites your working copy without checking, so if you haven't commited the file at all, it is gone.

As for Source tree, there is no way I know of to undo commits. It would most likely use reset under the covers anyway

Matthieu answered 4/7, 2014 at 7:48 Comment(1)
+1 for the link to the official documentation. I would also mention git reset --help which explains quite well (in my opinion) the five modes, or at least the two asked by the OP.Radman
S
3

This is the main difference between use git reset --hard and git reset --soft:

--soft

Does not touch the index file or the working tree at all (but resets the head to , just like all modes do). This leaves all your changed files "Changes to be committed", as git status would put it.

--hard

Resets the index and working tree. Any changes to tracked files in the working tree since are discarded.

Smackdab answered 1/8, 2018 at 15:34 Comment(0)
C
1

It is as simple as that, reset --hard <commit-id> will get you to the commit state and forgot all of the changes done after the commit state, In other case reset --soft <commit-id> will also get you back to the commit state but keep the files unstaged which were committed after the commit state and you can play around with it.

Note: If you are doing it on local machine environment, you have push to your updated work forcefully git push -f origin <branch-name> as the remote branch commit id is out of sync.

Carn answered 20/8, 2022 at 15:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.