【问题标题】:What is the difference between "git reset HEAD" and "git reset HEAD ."“git reset HEAD”和“git reset HEAD”有什么区别。
【发布时间】:2021-07-04 01:44:06
【问题描述】:

这是一个简写,例如 git add -A 是如何同时执行 git add .git add -u

请参阅此问题以了解上下文https://stackoverflow.com/a/22207257/7680918#

事实证明,Git 足够聪明,不会丢弃存储,如果它没有 应用干净。我能够通过 以下步骤:

  1. 取消暂存合并冲突:git reset HEAD .(注意尾随点)
  2. 保存冲突的合并(以防万一):git stash
  3. 返回master:git checkout master
  4. 拉取最新更改:git fetch upstream; git merge upstream/master
  5. 更正我的新分支:git checkout new-branch; git rebase master
  6. 要应用正确的隐藏更改(现在是堆栈中的第 2 个):git stash apply stash@{1}

【问题讨论】:

  • @Liam:我认为 OP 想要区分使用 HEAD 作为提交说明符 (git reset HEAD) 的 git reset 命令和使用该 plus 的命令.pathspec,如 git reset HEAD -- .。但是链接到旧答案并不是正确的方法。
  • 标题中应该有一个 (.) 点,但我想它会被删除,除非你使用引用
  • @torek 你知道这个问题的答案吗?
  • 还是没有意义,这可以用--soft ---mixed 或者--hard 代替 可以实现什么?
  • 我知道可以从非常仔细阅读git reset文档中推断出来。 :-) 我可能很快就会离开计算机一段时间,并且有一段时间无法写出来。

标签: git


【解决方案1】:

TL;DR

链接的答案可能不应该建议git reset HEAD .。它有效,但前提是您位于工作树的顶部。一个简单的git reset(没有选项也没有额外的参数)可能是一个更好的主意。

在阅读the git reset documentation之前,这里有一些背景知识:

  • pathspec 是一个文件名,可能包括路径组件,例如dir/filea/b/file.ext,或者只是file.ext 或其他。

  • git reset 命令最多可以影响三件事:通过名称 HEAD 看到的当前分支、Git 的索引,以及工作树中的文件 .

  • git reset 命令过于复杂:它实际上有多种“操作模式”。

要讨论 git reset 如何影响 HEAD,我们需要讨论分支名称的工作原理1 以及 HEAD 如何附加到分支名称。2 sup> 但是git reset种类 我们将在这里专门讨论 影响HEAD 本身,所以除了脚注之外,我们不会打扰。不过它确实会影响 Git 的索引,所以在继续之前让我们先谈谈 Git 的索引。


1简而言之,分支名称​​指向一个提交(通过存储原始提交哈希 ID)。 HEAD 的名称​​附加到分支名称,因此通过“移动您的HEAD”,git reset 会更改分支名称指向的提交。也就是说,Git 会在分支名称中写入一个不同的提交哈希 ID。

2git checkout 命令和 new-in-Git-2.23 git switch 命令在使用某个分支名称签出提交时执行此“附加”操作。


Git 的索引

Git 的索引是一个持久的(存储在磁盘上)数据结构,在大多数情况下,它记录了 Git 需要知道的内容,以便 Git 进行 下一个 提交。我喜欢这样说的方式是索引保存您的提议的下一次提交,或者至少是它的快照。通过这种方式,索引履行了它作为您的暂存区的作用,这就是它也有这个名称的原因。但索引不仅仅只是存储提议的下一次提交。特别是,当您进行合并并有合并冲突时,索引将扮演扩展角色。

不过,首先,让我们多谈谈这个暂存区角色。作为暂存区,索引保存的是每个源文件的完整副本。它不仅仅保存 更改 文件。 git status 命令特别提到更改的文件,特别没有提到未更改的文件。但是暂存区保存了所有文件。

这些文件最初来自当前提交。如果您没有替换暂存区in 中的文件,并且它仍然是当前提交的文件,那么它匹配 当前提交。所以git status 只是没有提到它。它还在那里!只是没有提到!但是如果你确实更改了一个工作树文件,然后运行 ​​git add,你告诉 Git:退出你现在拥有的文件,并放入另一个文件,使用相同的名称。 所以现在暂存文件与提交的文件不同

这对于我们在这里的特定目的而言并不重要,但值得注意的是,每个文件的暂存副本已经采用 Git 的压缩和去重格式。这就是文件在提交时的样子:名称存储在其他地方——就像它在索引中一样——而 contents 被压缩存储并去除重复数据,就像它们在 Git 的索引中一样.您的工作树中的文件没有压缩并且没有去重复,3所以git add必须在将它们存储到 Git 的索引之前对其进行压缩和去重,以便为新的提交暂存。

所有这些的目的是让你意识到这些事情:

  • Git 索引中的内容是可提交的格式。
  • 每个 文件将在 next 提交中总是 暂存。从暂存区移除文件意味着整个文件不会在下一次提交中
  • git status 不会告诉您当前提交中与索引中的文件相同的文件。只有当这些文件以某种方式不同时,它才会说staged for commit。这样,即使您的提交都包含 10,000 个文件,您也只需要注意您更改的两三个文件

当您进行 new 提交时,例如,通过运行 git commit,Git 所做的是使用文件的 index 副本作为快照。由于它们已经采用了用于提交的格式,因此速度非常快。

不过,在这种特殊情况下,我们关心的是合并冲突后的索引。所以我们需要了解 expanded 索引,而不仅仅是普通的日常建议下一个快照索引。


3从技术上讲,是否对工作树文件进行重复数据删除取决于您的操作系统。例如,在 Linux 或 FreeBSD 上使用 ZFS,您可以配置文件系统本身来执行重复数据删除。这与 Git 无关,Git 自己进行单独的重复数据删除。


扩展索引

在正常的索引设置中,每个文件都处于 Git 所称的 stage zero。我们可以使用git ls-files --stage 看到这一点以及暂存编号。这是 Git 存储库的 Git 索引中的内容的 sn-p:

100644 908330a0a3d5d1c1bad56544ba5bb18c3b783c84 0       .travis.yml
100644 5ba86d68459e61f87dae1332c7f2402860b4280c 0       .tsan-suppressions
100644 65651beada79b6267b1d0bda518a88269374cfdf 0       CODE_OF_CONDUCT.md
100644 536e55524db72bd2acf175208aef4f3dfc148d42 0       COPYING
100644 ddb030137d54ef3fb0ee01d973ec5cee4bb2b2b3 0       Documentation/.gitattributes
100644 9022d4835545cbf40c9537efa8ca9a7678e42673 0       Documentation/.gitignore
100644 45465bc0c98f5d88cfe1ade092d29b5dc32c1e23 0       Documentation/CodingGuidelines

(完整的索引内容运行了将近 4000 行:每个文件一个条目,并且几乎有 4000 个文件)。 staging number 是每一行的第三个字段,如您所见,这里每个字段都是零。 这意味着没有正在进行的合并冲突。 最后一个字段包含文件的名称,如索引中所示;请注意,许多名称都嵌入了斜杠(这始终是正斜杠,即使在 Windows 上也是如此)。

当您遇到合并冲突时,Git 所做的就是在索引中留下三个——或者更准确地说,最多三个——每个版本file.4 这三个文件位于三个非零暂存索引号。暂存编号 1 表示索引中的文件来自 merge base 提交。暂存编号 2 表示该文件来自“我们的”或HEAD 提交,而暂存编号 3 表示该文件来自“他们的”提交。在非零阶段存在任何条目都表明该特定文件存在合并冲突。

您可以想到的一种方式是每个文件最多有四个“插槽”。如果文件不冲突,则使用槽零。否则,插槽 1、2 和/或 3 将被占用。因此,如果一个文件处于合并冲突状态,它有一个非零的槽号(同一个文件可能有更多条目,还有其他槽号)。否则,它的槽号为零(并且此文件没有更多条目)。

每当任何文件处于合并冲突状态时,Git 都会拒绝进行新的提交。它实际上不能,因为只有零槽条目可以进入提交:提交中没有用于槽号的空间。因此,在提交之前,您必须解决冲突

具体如何解决冲突取决于您自己。不过,Git 需要 的东西很简单:它只需要你告诉 Git:完全取出插槽 1、2 和/或 3 条目。 你可以这样做在很多方面:

  • git rm 将删除这些条目。
  • git add 会将工作树中的文件复制到索引中的零槽中,删除非零条目。
  • git resetgit restore 都可以放入零阶段文件,因此也可以删除非零条目。

这最后一个将是答案的关键。


4原来的索引设计应该在这里允许超过三个,但实际上似乎没有什么可以利用这一点。


让我们再回头看看问题

引发这一切的最初问题是:

git reset HEAD

和:

git reset HEAD .

然后是如何在另一个 StackOverflow 答案中使用它(包括运行六个列出的命令)。

git reset HEAD . 命令最好写成:

git reset HEAD -- .

为了明确. 这里是一个pathspec。我们现在转向the git reset documentation,特别是 SYNOPSIS 部分,内容如下:

概要

git reset [-q] [<tree-ish>] [--] <pathspec>…​ git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>] git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>…​] git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]

这里有一些语法技巧需要理解:方括号[] 中的内容是可选的,尖括号&lt;&gt; 中的内容将替换为一些非空字符串,省略号(…​)表示“一个或更多我们刚才所说的”。 (在这种情况下,这是一个或多个路径规范。)括号括住用竖线分隔的替代方案|,因此(--patch | -p) 意味着您可以在此处编写--patch-p。这些句法噱头在大多数 Unix/Linux 文档的 SYNOPSIS 部分中大多是标准的。

这里还有更多特定于 Git 的技巧:单词tree-ish 表示根据gitrevisions 文档可以接受的任何东西,只要 Git 可以将其转换为内部树对象说明符。在这种情况下,这意味着任何指定 commit 的东西都有效。 HEAD 指定一个提交——当前提交——所以git reset HEAD 匹配这个git reset &lt;tree-ish&gt;

第一个概要条目需要git reset,允许一个可选的-q,允许一个可选的&lt;tree-ish&gt;,允许一个可选的--,然后需要一个&lt;pathspec&gt;。所以:

git reset HEAD

与此形式不匹配,因为缺少路径规范。但是:

git reset HEAD .

确实匹配这种形式:它省略了--,但这是允许的。

概要部分中的 second 形式需要git reset 部分,像以前一样有一个可选的-q,允许一个可选的--pathspec-from-file——如果使用了,--pathspec-file-nul 也可以是使用——然后有一个可选的&lt;tree-ish&gt;。所以:

git reset HEAD

匹配此形式。 (这是文档中的一个小故障!)

第三种形式需要--patch-p,所以这两个命令都不匹配。

最后一种形式需要git reset(一如既往),然后允许选项--soft--mixed--hard--merge--keep、可选的-q 和可选的&lt;commit&gt;(注意这不是&lt;tree-ish&gt;,而是一个commit)。该:

git reset HEAD

command 也匹配这种形式。所以git reset HEAD,没有点作为路径规范,可能是其中之一。

正如我上面所建议的,这是文档中的一个小故障:git reset HEAD 应该采用两个允许的匹配中的哪一个?我们只能通过阅读文档来了解这一点(即使这样我们也必须猜测一下,或者尝试重置测试)。

下一部分是 DESCRIPTION 部分。它表示使用 pathspec

git reset 的种类

... 将与&lt;pathspec&gt; 匹配的所有路径的索引条目重置为&lt;tree-ish&gt; 的状态。 (它不影响工作树或当前分支。)

这意味着指定提交中的文件状态(在本例中为HEAD)被复制到索引中。没有完全提到的(尽管通过对git restore 的引用在下一段中至少部分涵盖了它)是这具有与git add 相同的清除非零阶段条目的副作用。

所以这回答了什么:

git reset HEAD -- .

会:它重置,如清除冲突以及复制HEAD 的提交副本,每个与路径规范匹配的文件.

这给我们留下了一个问题:哪些文件与 pathspec . 匹配? 当前的重置文档存在缺陷。它将我们引用到the gitglossary page,它告诉我们路径规范可以是相对于工作树的顶部或当前工作目录的,并且每个更具体的页面(即git reset 的页面)应该说.它没有说。事实上,这里的. 是相对于当前工作目录。所以如果你不是在树的顶端:

git reset HEAD -- .

表示仅此目录及以下目录中的文件

实验(一些现有文件的git rm --cached 和一些新文件的git add)表明git reset HEAD -- . 将由于git rm 丢失的任何文件恢复到索引,并从索引中删除任何没有的新文件在HEAD。如果文档对此更清楚可能会很好,但也许我们可以将实验结果视为确定/目标行为。

让我们继续执行另一个命令:

git reset HEAD

这是否意味着git reset 没有--pathspec-from-file 选项但有&lt;tree-ish&gt;,或者这是否意味着git reset 没有--hard--mixed 或除了&lt;commit&gt; 之外的其他任何东西?好吧,如果它前者,它根本不会提供任何路径规范。 --pathspec-from-file 背后的想法是在文件中而不是在命令行中提供路径规范,但这样就有一些路径规范。如果 Git 将其视为前者,则根本没有路径规范。

我们可以在这里进行测试:

$ git reset HEAD^{tree}
error: object fcb94a429496c28fa7f95926e9d46840671d0d88 is a tree, not a commit
fatal: Could not parse object 'HEAD^{tree}'.

这使用gitrevisions 语法来确保我们为git reset 提供了一个树状结构,而不是一个提交。结果是立即出错。运行git reset HEAD 有效,因此这个特定的git reset 必须与第四个语法匹配,而不是第二个。 (文档应该省略了 --pathspec-from-file 周围的方括号。)

第四种语法是由以下描述的:

<em>git reset</em> [&lt;mode&gt;] [&lt;commit&gt;]

部分,上面写着:

此表单将当前分支头重置为&lt;commit&gt;,并可能根据&lt;mode&gt; 更新索引(将其重置为&lt;commit&gt; 的树)和工作树。如果省略&lt;mode&gt;,则默认为--mixed。 [剪辑]

所以这是一个--mixed 重置,因为我们省略了&lt;mode&gt; 参数(这是概要中列举的四个选项之一)。我们选择的提交——导致当前分支名称移动那个提交——是HEAD选择的提交。但是HEAD 是当前分支名称选择的提交。所以这个动作是来自某个提交——我们称之为“提交 X”——提交 X。如果你从你现在站立的地方跳下来但安排降落在你现在站立的地方,那并不是一个真正的移动,是它? :-)

无论如何,这意味着 resets the current branch head 短语变得无关紧要:当前分支头只是停留在原处。我们继续并可能更新索引git reset 是否更新索引取决于&lt;mode&gt;,我们刚才说的是--mixed,其中文档继续说:

重置索引但不重置工作树...

所以这会将当前提交复制回索引中。这有点像使用匹配每个文件的路径规范,只是我们根本不需要路径规范,实际上禁止使用路径规范。 (使用路径规范让我们进入第一种语法,而不是第四种语法。)

与带有路径规范的git reset 一样,这具有撤消任何合并冲突状态的副作用:索引中的任何非零阶段条目都将替换为其提交中的零阶段副本。

没有很好的文档记录,但对于标准的--mixed 重置始终是正确的,这种git reset从 Git 的索引中删除任何文件 不在所选提交中。正如我通过实验发现的,git reset HEAD -- . 命令在这里的行为方式相同。

总结

让我在这里再复制一遍:

事实证明,Git 足够聪明,不会在应用不干净的情况下丢弃存储。

这部分表示要解决的问题是:

<do some hacking>
<realize that this is the wrong commit>
$ git stash
$ git checkout somebranch
$ git stash pop
<receive merge conflict messages>

git stash pop 操作在其第一个 (git stash apply) 步骤的中间停止,并且在此处完全省略并且不会运行其第二个 (git stash drop) 步骤。

我能够通过以下步骤达到所需的状态:

  1. 取消暂存合并冲突:git reset HEAD .(注意尾随点)

这就是问题所在。git reset --mixed HEAD,可以拼写为git reset,在这里可能是更好的方法。这会将索引恢复到 git stash apply 甚至开始之前的状态。

(因为我们绝对不需要 this 合并的结果——我们可以在以后的任何时候重新创建它,只要我们仍然有两个 stash 提交——我们本可以运行git reset --hard。但是这个特殊的人没有这样做。)

  1. 保存冲突的合并(以防万一):git stash

这是完全没有必要的。它进行了两次 more 提交——每个 stash 条目是两个或三个提交;请参阅this old answer of mine——其中一个是当前提交的副本,其中一个包含工作树中的跟踪文件;然后它运行git reset --hard(我们本可以早点完成)。

  1. 返回master:git checkout master

这当然完全符合我们的预期。因为git stash 末尾的git reset --hard,所以没有暂存或未暂存的更改:当前提交、Git 的索引和您的工作树都匹配,git status 会说nothing to commit, working tree clean

  1. 拉取最新更改:git fetch upstream; git merge upstream/master

这会从名为upstream 的远程获取新提交,然后执行git merge upstream/master 将执行的任何操作;这显然取决于在git fetch 中获得的upstream/master 上的提交与当前(master)分支上的提交。

如果我们做出一些假设——master 通常与upstream/master 同步,除非上游 Git 的用户向upstream 添加新提交——这将执行一个简单的快进操作。

  1. 更正我的新分支:git checkout new-branch; git rebase master

这将完成通常的变基工作。

  1. 要应用正确的隐藏更改(现在是堆栈中的第 2 个):git stash apply stash@{1}

如果我们之前跳过了额外的(不必要的)存储,git stash apply 会在这里做我们想要的事情。

【讨论】:

    猜你喜欢
    • 2023-03-23
    • 2014-08-25
    • 2018-06-25
    • 2017-12-10
    • 2021-10-28
    • 2011-09-06
    • 2016-02-24
    • 2020-04-07
    相关资源
    最近更新 更多