git reset vs git reset HEAD

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?


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.


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.


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