Git reset与git reset HEAD的区别

24
每次文件被暂存后,Git都会提供有用的指令,以防需要取消暂存文件:
(使用“git reset HEAD …”取消暂存)
然而,Atlassian的Git教程只是简单地说:
git reset 这似乎更加直接,那么为什么会有差异呢?

3
注意:现在还有git restore --staged -- afile这个命令,自Git 2.23(2019年8月)起可用。详见我下面编辑后的回答 - VonC
3个回答

25

在默认参数方面,与git reset手册页面无差异:

<tree-ish>/<commit> 在所有形式中都默认为 HEAD

该消息最初没有包括 HEAD: 2007年1月3c1eb9c提交, git 1.5.0-rc1, 但由于默认值不总是已知的,帮助信息明确说明了应该将其重置为哪个提交。

HEAD 出现在2007年11月367c988提交,Git 1.5.4中:

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)

torek 指出了 评论区 中的一个实际区别:

通过指定 HEAD,你保证在 HEAD 后面的第一个单词被视为路径名。
例如,假设你运行 git reset zorg。那么 zorg 是一个树对象吗,比如标签名,还是一个路径名,./zorg
Git 的答案是:如果 git rev-parse 可以将其转换为树 ID,则它是一个树对象,否则它是一个路径名。
你可以写成 git reset -- zorg 或者 git reset HEAD zorg 来确保 Git 将其视为路径。

请参考 "删除一个命名不当的 git 分支" 中关于双短横线语法 ( -- ) 的更多信息。


OP skube评论中提到:

顺便说一下,他们建议使用这种方法来丢弃工作目录中的更改
(即 git checkout -- <file>)。
这似乎与 git reset HEAD <file> 不一致。

虽然git reset 手册清楚地指出,在git reset <tree-ish> -- <paths> 中缺少树对象时意味着 HEAD,但git checkout <tree-ish> -- <paths>则不是这样。

git checkout <tree-ish> -- <pathspec>

当给定<paths>时,git checkout不会切换分支。 它会从索引文件或命名的<tree-ish>(通常是提交)中更新工作树中的指定路径。 这意味着git checkout -- path会用已经暂存(git add)的内容覆盖工作区。 而git reset -- PATH(作为git reset的mixed形式)将使用HEAD所包含的内容重置索引(有效地取消暂存已添加的内容)。 git resetgit checkout没有使用相同的默认值,因此:
  • 对于git reset <tree-ish> <file>,你可以表示默认树: HEAD,因此使用git reset HEAD <file>
  • 但是当你未提供树来使用git checkout时,你无法表示默认参数,它是索引。因此使用git checkout -- file

这里必须使用--,因为git checkout有一个参数,为了明确该参数是代表文件,需要使用--

请注意git checkout HEAD files与之不同: torek评论中提到了此事

git checkout HEAD path会将HEAD提交(树状物)复制到索引,然后再复制到工作目录。


注意:自Git 2.23+(2019年8月)起,您可以使用git restore代替。

请参见示例

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

手册页面:

git restore --staged hello.c 没有指定源,只还原索引(--staged): 它默认使用HEAD作为源。

默认情况下,恢复工作树和索引的源分别是索引和HEAD。
可以使用--source指定提交作为恢复源。

其他示例:

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 是一个更自然的命令名称,没有歧义。


4
值得注意的是,通过指定 HEAD,您可以确保在 HEAD 后的第一个单词被视为路径名。例如,假设您运行了 git reset zorgzorg 是树对象,如标签名,还是路径名 ./zorg?Git 的答案是:如果 git rev-parse 可以将其转换为树对象 ID,则它是树对象,否则它是路径名。您可以写成 git reset -- zorggit reset HEAD zorg 来确保 Git 将其作为路径名处理。 - torek
@torek非常正确,像往常一样。我已经在答案中包含了您的评论(附加链接),以便更多人能看到。 - VonC
所以,如果 git reset -- <file>git reset HEAD <file> 在功能上是相同的,并且两者都比简单的 git reset <file> 更安全一些,为什么 git 建议使用 HEAD 版本呢?当然,输入 -- 比输入 HEAD 更容易。顺便说一句,他们建议在工作目录中放弃更改(即 git checkout -- <file>)。这似乎有些不一致。 - skube
@skube 因为Git希望您了解在文件名之前涉及到一个<treeish>的语法。 git reset -- <file> 完全掩盖了这一点。 - VonC
使用 git checkout 命令时,-- 版本 (git checkout -- path) 会告诉 git 从索引中复制(提取索引版本到工作目录),而 git checkout HEAD path 则会从 HEAD 提交中复制到索引,然后再复制到工作目录。我发现这个特定的差异特别令人困惑,但由于 git reset 是重置索引,所以实际上是有道理的。 - torek
显示剩余4条评论

4

默认情况下,git reset等同于git reset HEAD

引用手册(我强调):

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

从这里可以看出行为没有实际上的区别。

这似乎更加直接,那么差异在哪里呢?

既然它们是相同的,你可以使用两个中最短的。


1

第一次提交之前,HEAD 不存在,然后我们得到:

$git reset HEAD stagedFile
fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接