Pro Git explains git reset
like this:
Recap
The
reset
command overwrites these three trees in a specific order, stopping when you tell it to:
- Move the branch HEAD points to (stop here if
--soft
)- Make the Index look like HEAD (stop here unless
--hard
)- Make the Working Directory look like the Index
The way I understood is that, if I do git reset --hard
, then both my index and my working directory would become EXACTLY like my HEAD. So I went ahead and did this:
# make a git repo
mkdir mygitrepo
cd mygitrepo
git init
# init commit
touch old_file
git commit -a
# stage a file
touch staged
git add staged
# create file that is not staged
touch unstaged
So far my repo looks like this:
HEAD
old_fileindex
old_file + stagedworking dir
old_file + staged + unstagedNow if I run git reset --hard
then I expect my repo to become:
HEAD
old_fileindex
old_fileworking dir
old_fileBut I would get this instead:
HEAD
old_fileindex
old_fileworking dir
old_file + unstagedI did similar test by explicitly passing target argument, like git reset --hard target
, and I got similar result: staged files are all gone, but unstaged files are still present after git reset --hard
.
Could some one explain if I misunderstood anything about git reset
?
As mentioned in "Undoing Changes"
The
git clean
command is often executed in conjunction withgit reset --hard
.
Remember that resetting only affects tracked files, so a separate command is required for cleaning up untracked ones.Combined, these two commands let you return the working directory to the exact state of a particular commit.
The --hard
option is documented as:
Resets the index and working tree.
Any changes to tracked files in the working tree since<commit>
are discarded.
This was already discussed back in 2008.
Simply put by Linux Torvalds:
If you're used to doing "
git checkout -f
" or "git reset --hard
", both of those checks(*) are just ignored. After all, you asked for a forced switch.
(* checks = dirty file or untracked files)
And at least in the second case, what I think happens is that git won't remove the file it doesn't know about, so you'll have a "turd" left around.
Added files though... even if there were never committed (being brand new), are still deleted by a git reset --hard
, as seen also in 2008:
I actually accidentally deleted hundred of newly added files yesterday doing just this.
My question is why "
git reset --hard
" can't make a special case for newly added tracked files?
After all, "git status
" knows that they're "new files", and "git reset --hard
" could realize that wiping them off the face of the earth isn't the most helpful thing possible.
As a suggestion, git read-tree -m HEAD
or git rm --cached <file list>
before a git reset --hard
would help keeping this new files around (removing them from the index)
you would want "
reset --hard
" to remove that path when a path does not exist in the HEAD but in index in other cases.
And it is my experience that far more often than not it is what is desirable.
(like getting rid of crufts from a conflicted merge)
Note that in this instance, git fsck
can still help recover those deleted new files.