git reset vs git reset HEAD
Asked Answered
P

3

24

Every time a file has been staged, Git offers helpful instructions in the event you needed to unstage a file:

(use "git reset HEAD <file>..." to unstage)

However the decent Git Tutorials by Atlassian simply say:

git reset <file>

This seems more straightforward, so why the difference?

Popularity answered 21/11, 2015 at 23:9 Comment(1)
Note: you also have now git restore --staged -- afile, with Git 2.23 (August 2019). See my edited answer belowTitanesque
T
25

No difference (from git reset man page) in term of default parameter:

The <tree-ish>/<commit> defaults to HEAD in all forms.

That message initially did not include HEAD: commit 3c1eb9c, Jan. 2007, git 1.5.0-rc1, but since the default is not always known, the help message makes it clear to which commit you are supposed to reset.

HEAD appears in commit 367c988, Nov. 2007, Git 1.5.4:

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

torek points out an actual difference in the comments:

By specifying HEAD, you guarantee that the first word after HEAD is taken as a path name.
For instance, suppose you run git reset zorg. Is zorg a tree-ish, such as a tag name, or is it a path name, ./zorg?
Git's answer is: it's a tree-ish if git rev-parse can turn it into a tree ID, otherwise it's a path.
You can either write git reset -- zorg or git reset HEAD zorg to make sure that git treats it as a path.

See more on the double hyphen syntax ( -- ) in "Deleting a badly named git branch".




The OP skube adds in the comments:

As an aside, they do suggest it for discarding changes in working directory
(i.e git checkout -- <file>).
It just seems inconsistent with git reset HEAD <file>.

While git reset man page clearly indicates the lack of tree-ish in git reset <tree-ish> -- <paths> means HEAD, it is not so for git checkout <tree-ish> -- <paths>.

git checkout <tree-ish> -- <pathspec>

When <paths> are given, git checkout does not switch branches.
It updates the named paths in the working tree from the index file or from a named <tree-ish> (most often a commit).

That means git checkout -- path will override the working tree with what has already been staged (git add'ed).
While git reset -- PATH (being the mixed form of git reset) will reset the index with what HEAD contains (effectively un-staging what was added)

git reset and git checkout don't use the same default, and:

  • you can represent the default tree for git reset <tree-ish> <file>: HEAD.
    Hence git reset HEAD <file>;
  • but you cannot represent the default parameter when you don't provide a tree for git checkout: it is the index.
    Hence git checkout -- file.

The -- has to be used in the git checkout case, since there is only one parameter, and it needs to be clear that parameter represents files.

Note that git checkout HEAD files is different: torek mentions in the comments

git checkout HEAD path copies from the HEAD commit (the tree-ish) to the index and then on to the working dir.


Note: with Git 2.23+, August 2019, you might use git restore instead

See the examples:

To restore a file in the index to match the version in HEAD (this is the same as using git-reset)

$ git restore --staged hello.c

man page:

git restore --staged hello.c does not specify a source, and restore the index only (--staged): it does so (by default) using HEAD as source.

By default, the restore sources for working tree and the index are the index and HEAD respectively.
--source could be used to specify a commit as the restore source.

Other examples:

You can restore both the index and the working tree (this the same as using git-checkout)

$ git restore --source=HEAD --staged --worktree hello.c

or the short form which is more practical but less readable:

$ git restore -s@ -SW hello.c

git restore is a more natural command name, and has no ambiguity.

Titanesque answered 21/11, 2015 at 23:11 Comment(9)
It's worth adding that by specifying HEAD you guarantee that the first word after HEAD is taken as a path name. For instance, suppose you run git reset zorg. Is zorg a tree-ish, such as a tag name, or is it a path name, ./zorg? Git's answer is: it's a tree-ish if git rev-parse can turn it into a tree ID, otherwise it's a path. You can either write git reset -- zorg or git reset HEAD zorg to make sure that git treats it as a path.Charlenecharleroi
@Charlenecharleroi Very true, as usual. I have included your comment in the answer (with additional links) for more visibility.Titanesque
So if git reset -- <file> and git reset HEAD <file> are functionally the same, and both being a little safer than simply git reset <file>, why does git offer the suggestion to use the HEAD version? Surely, it easier to type -- than HEAD. As an aside, they do suggest it for discarding changes in working directory (i.e git checkout -- <file>). It just seems inconsistent.Popularity
@Popularity because Git wants you to be aware that the syntax invoves a <treeish> before the file. git reset -- <file> obfuscates that entirely.Titanesque
@Charlenecharleroi the OP points out an interesting inconsistency between two help message: git reset HEAD <file> and git checkout -- <file>: why the different syntax?Titanesque
With git checkout, the -- version (git checkout -- path) tells git to copy out of the index (extract index version to work dir), while git checkout HEAD path copies from the HEAD commit to the index and then on to the work dir. I find this particular difference especially confusing, but since git reset is resetting the index, it does actually make sense.Charlenecharleroi
@torek: so while git reset -- <file> and git reset HEAD <file> are identical, git checkout -- <file> and git checkout HEAD <file> are not? In other word, the absence of treeish in git checkout -- <file> does not mean by default HEAD (while it does default to HEAD for git reset)Titanesque
@Charlenecharleroi After re-reading the doc, it makes sense now. I have edited the answer accordingly.Titanesque
@Popularity Thanks to torek, I have edited the answer to address the apparent inconsistency between git reset HEAD <file> and git checkout -- file.Titanesque
A
4

By default, git reset is equivalent to git reset HEAD

Quoting the man page (my emphasis):

git-reset - Reset current HEAD to the specified state.

git reset [-q] [<tree-ish>] [--] <paths>…
git reset (--patch | -p) [<tree-ish>] [--] [<paths>…​]
git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]

In the first and second form, copy entries from <tree-ish> to the index. In the third form, set the current branch head (HEAD) to <commit>, optionally modifying index and working tree to match. The <tree-ish>/<commit> defaults to HEAD in all forms.

[...]

git reset [-q] [<tree-ish>] [--] <paths>…​

This form resets the index entries for all <paths> to their state at <tree-ish>. (It does not affect the working tree or the current branch.)

This means that git reset <paths> is the opposite of git add <paths>.

From this you see that there's no actual difference in behavior.

This seems more straightforward, so why the difference?

Since they're both the same, you might as well use the shortest version of the two.

Ariew answered 21/11, 2015 at 23:12 Comment(0)
M
1

First time, before any commit the HEAD does not exist, then we get:

$git reset HEAD stagedFile
fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree
Moire answered 27/11, 2017 at 21:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.