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 toHEAD
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 afterHEAD
is taken as a path name.
For instance, suppose you rungit reset zorg
. Iszorg
a tree-ish, such as a tag name, or is it a path name,./zorg
?
Git's answer is: it's a tree-ish ifgit rev-parse
can turn it into a tree ID, otherwise it's a path.
You can either writegit reset -- zorg
orgit 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.egit checkout -- <file>
).
It just seems inconsistent withgit 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
.
Hencegit reset HEAD <file>
; - but you cannot represent the default parameter when you don't provide a tree for
git checkout
: it is the index.
Hencegit 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 theHEAD
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 ofgit 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