git reset --hard 不会使工作树看起来像索引?

git reset --hard doesn't make the working tree look like index?

Pro Git 解释 git reset 是这样的:

Recap

The reset command overwrites these three trees in a specific order, stopping when you tell it to:

  1. Move the branch HEAD points to (stop here if --soft)
  2. Make the Index look like HEAD (stop here unless --hard)
  3. Make the Working Directory look like the Index

我的理解是,如果我这样做 git reset --hard,那么我的索引和工作目录都会 完全 就像我的 HEAD。所以我继续这样做:

# 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

到目前为止我的回购看起来像这样:

现在如果我 运行 git reset --hard 那么我希望我的回购变成:

但我会得到这个:

我通过显式传递目标参数做了类似的测试,比如 git reset --hard target,我得到了类似的结果:暂存文件都消失了,但未暂存文件在 git reset --hard 之后仍然存在。

如果我对 git reset 有任何误解,谁能解释一下?

如“Undoing Changes”中所述

The git clean command is often executed in conjunction with git 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.

--hard 选项记录为:

Resets the index and working tree.
Any changes to tracked files in the working tree since <commit> are discarded.


早在 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.

(* 检查 = 脏文件或未跟踪文件)

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.


添加了 个文件...即使从未提交过(全新),仍然会被 git reset --hard 删除,因为 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.

作为建议,git read-tree -m HEADgit rm --cached <file list> before a git reset --hard 将有助于保留这些新文件(将它们从索引中删除)

Junio C. Hamano stil argues:

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.

(比如从有冲突的合并中删除 crufts)

请注意,在这种情况下,git fsck can still help recover those deleted new files

Git 将始终遵循一条重要规则(除非明确告知不要这样做):不要触摸未跟踪的文件

使用默认功能,Git 将永远不会执行任何会导致未跟踪文件数据丢失的操作。这是因为 Git 永远无法恢复未跟踪文件的信息:它不“了解”它们,因此不会将它们的内容存储在任何地方,这使得对它们的任何操作都非常危险。所以它根本不会碰它们。

这也是为什么 Git 不允许您切换到具有未跟踪文件的分支,如果该分支包含具有相同路径的 已跟踪 文件。因为这样做会覆盖您未跟踪的文件,将其替换为该分支中的文件,但您永远无法恢复该未跟踪文件。所以 Git 不会切换分支,而是要求您先处理未跟踪的文件(这可能意味着重命名它,将其添加到 Git,甚至删除它)。

所以当你做git reset时,你只会影响索引。而 git reset --hard 会影响索引和工作目录。对于 Git,工作目录仅包含跟踪文件。这就是为什么 git reset --hard 既不会删除未跟踪的文件,也不会删除您 .gitignore 中的文件。 Git 不“了解”它们,因此不会接触它们。

如果您想删除未跟踪的文件,可以使用一个命令:git clean。该命令带有不同的参数组合,它们都执行各种不同的工作。

默认情况下 git clean -f 将删除未跟踪的 文件 但保持忽略的文件不变。如果你还想删除被忽略的文件,可以使用git clean -x -f。请注意,您通常必须为 git clean 指定 -f(或 --force)才能执行任何操作。这只是一个额外的安全机制,以确保您知道自己在做什么。您可以使用 -n 代替执行干 运行 并且只显示 Git 将删除哪些文件。这样可以轻松避免问题。