Reputation: 4531
I have taken a look at this stackoverflow link but I think the subtle difference between what I am asking is the usage of HEAD
within the checkout cmd as their suggestions don't' seem to be working :
Is there a difference between git reset --hard HEAD and git checkout .?
git checkout HEAD -- .
cleans out my staging area as well.
Additionally, the second answer, regarding deleted files added to staging area,
seem to be coming back with git checkout HEAD -- .
Is there a situation where one would get different results?
Upvotes: 3
Views: 1354
Reputation: 490068
Yes, there is a difference, besides that implied by .
if you're not at the top level of your repository. I won't go so far as to claim that what follows is the only difference; it suffices to show one example, to see that they are different.
Start by creating a new repository with at least one commit (here I have made two, as is my habit):
$ mkdir treset
$ cd treset
$ git init
Initialized empty Git repository in ...
$ echo 'for testing reset vs checkout' > README
$ git add README
$ git commit -m 'initial commit'
[master (root-commit) 058b755] initial commit
1 file changed, 1 insertion(+)
create mode 100644 README
$ echo contents for file a > file-a
$ echo contents for file b > file-b
git commit -m 'add files'
[master f505609] add files
2 files changed, 2 insertions(+)
create mode 100644 file-a
create mode 100644 file-b
At this point, HEAD
and the index match—both contain the content from commit f505609
—and the work-tree contains the (normal format) files that match that commit as well. Now let's add a new file, and copy it into the index:
$ echo 'uncommitted file' > foo
$ git add foo
Technically, the git add foo
created blob object a9e2570d6af8c05b57e2cefecaebeedfabc98bf2
in the repository and then put that hash ID into the index:
$ git ls-files --stage
100644 e16f62b2e75cf86a6f54adcfddcfd77140f238b9 0 README
100644 881d9334f4593efc7bab0dd536348abf47efed5c 0 file-a
100644 fa438bc26ce6b7a8f574bad9e63b83c912a824b9 0 file-b
100644 a9e2570d6af8c05b57e2cefecaebeedfabc98bf2 0 foo
(The hash ID of this blob object is predictable due to the known content for file foo
. That's true of the other three files as well, but they're actually committed, so those blob objects are permanent. The one for foo
could be GCed, if we never actually commit it and instead remove the entry from the index.)
git checkout HEAD
If we use git checkout HEAD
, we direct Git to copy from HEAD
into the index and then expand them into normal work-tree files. HEAD
contains three files (README
, file-a
, and file-b
), so this does that and updates the three work-tree files with the contents they already have—so there's no observable effect.1
$ git checkout HEAD -- .; ls
file-a file-b foo README
Note that file foo
remains, both in the index (run git ls-files
again to see) and in the work-tree.
1Unless, that is, we inspect things like file modification times or system calls executed, via whatever OS-level tools we have available. In this case we can tell if Git really did overwrite the work-tree files or not. On my system, it actually didn't, because the index hashes matched the HEAD
hashes and the stat
data cached in the index matched the stat
data from the work-tree files, so it didn't bother. But in principle Git copied HEAD
to the index, and then the index to the work-tree, and if it were necessary based on hashes and/or stat data, Git would have actually touched the work-tree files here.
git reset --hard
If we tell Git to reset the index to match the current commit, and reset the work-tree to match changes to the index, the action is different. This time, Git examines the index and sees that file foo
is present, while it's absent in the commit. So Git removes file foo
from the index, and updates the work-tree accordingly:
$ git reset --hard HEAD; ls
HEAD is now at f505609 add files
file-a file-b README
File foo
has vanished from the work-tree.
If we were to use git reset --mixed HEAD
, Git would remove foo
from the index, but not from the work-tree. (The default action for this kind of reset—there are many other kinds—is --mixed
.)
git restore
With the new Git 2.23+ git restore
command, we can control the index and work-tree separately. First we have to put foo
back into the index and work-tree:
$ echo 'uncommitted file' > foo
$ git add foo
We can now choose whether to copy HEAD
to the index or not, and whether to manage the work-tree similarly. Its documentation is a bit more explicit too:
If a path is tracked but does not exist in the restore source, it will be removed to match the source.
What it means for a path to be "tracked" is that the path is in the index. In this case, foo
is in the index now (due to the git add
) so it is tracked. If we restore the index from HEAD
, foo
will be removed from the index, just as with git reset --hard
or git reset --mixed
. So let's try VonC's command, but with .
(the current directory and all sub-directories)2 as the pathname, here:
$ git restore --source HEAD --staged --worktree .
$ ls
file-a file-b README
So you can see that this had the same effect as git reset --hard
. Unlike git reset
, git restore
has only one job—though with two parts to it—so we need not worry about other modes of operation.
(This is why both git switch
and git restore
were added: the mostly do the same things you can already do with git checkout
and git reset
, but they only have one job, even if it has several parts. By contrast, git checkout
has anywhere from about three to about seven different jobs, depending on how you count, and git reset
has anywhere from about three to about five.3)
2This particular repository has only the one top level directory, so we need not worry that you've done a cd subdir
within the work-tree. If you had, though, .
would mean apply this to the subdir/*
files, so that checkout and reset would be even more different.
3For git checkout
, consider:
git checkout-index
)git checkout -m
with a file name)git checkout -m
but with a branch name)While that's just five, we can git checkout --ours
and git checkout --theirs
and some might wish to count these as separate from the usual "extract from index" flavor. We can get even more when you add create branch (git checkout -b
) and forcibly reset branch (git checkout -B
). Alas, git switch
has the create and forcibly-reset options as well!
Some might, of course, lump some or all of these into one operation, just as git checkout
does. That's why I say "depending on how you count".
For git reset
, consider:
HEAD
HEAD
HEAD
)HEAD
)git reset -p
(cannot move HEAD
)all of which are lumped under the one command git reset
.
Upvotes: 4
Reputation: 1329582
To avoid the confusion, you should use the new experimental command git restore
(Gti 2.23+, August 2019).
As I explained in "What is git restore
Command ? What is the different between git restore
and git reset
?", you can specify what to restore with git restore
.
This is more explicit than relying on HEAD or no HEAD in git checkout
.
To do a reset --hard
with git restore
:
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
Upvotes: 1