TL;DR:您不需要重新添加“要提交的更改”中的文件。
Git 的“索引”
Git 有一个叫做 index 或 staging area 的东西,有时甚至是 cache(如 git diff --cached )。解释得不是很好,部分原因是它很复杂。幸运的是,索引的使用非常简单,除了(唉)在合并期间。 :-)
索引中的内容最好描述为您将进行的下一次提交。这也有助于记住 Git 总是有一个 当前提交,名为 HEAD,和(--bare 存储库除外),work-tree 或 working tree,您可以在其中准备好文件以供查看、编译、编辑和进行其他任何操作是你处理你的文件。
最初,您的工作树、索引和HEAD 提交都匹配(当您第一次克隆存储库时)。您不能直接在索引中接触文件,因此您可以在工作树中进行任何更改,甚至创建新文件,您可以在其中完成所有工作。然后,要将修改后的文件放回索引中,请在路径上运行 git add,以便 Git 将修改后的文件复制到索引中。要将新文件放入索引中,您还需要在路径上运行 git add。
如果你出于某种原因兴奋不已并立即运行git commit,Git 会读取你当前的索引并将其打包为一个新的提交。新提交将拥有索引中的每个文件,作为它的工作树快照,现在处于它现在的状态,在索引中。
如果该状态匹配HEAD 提交,git commit 只会说:“呵呵?为什么你现在尝试进行新的提交?”因此,您可能已经更改了索引中的某些内容。 git status 将向您展示索引中与HEAD 提交不同的内容:这些是“要提交的更改”。
合并
运行git merge 有时会揭示有关索引的秘密。 in 索引中的每个条目实际上有四个 slots,它们被编号。通常,您只会看到插槽 0,它是为“正常”文件保留的,准备提交。
合并旨在合并两组(或有时更多,但让我们坚持两组)更改。例如,也许您和 Bob 从相同的文件开始。然后您对一些文件进行了一些更改,git add-ed 并提交了源的新快照。与此同时,Bob 对一些文件进行了一些更改——甚至可能是相同的文件——以及 git add-ed 并提交,现在是时候将你的更改和 Bob 的更改结合起来了。
要执行合并——将某些文件合并为动词——Git 首先找到 合并基础,即您和 Bob 同步的点。然后 Git 运行两个 git diffs:一个从该合并库到 your 的最新提交,一个从同一合并库到 Bob 的最新提交。这显示了“你改变了什么”和“Bob 改变了什么”。
Git 然后尽最大努力合并这些更改(包括,在本例中,找出并遵循文件重命名,并找出如果只有 一个 时该怎么做你们中的一些人重命名了一些文件——但大多数合并冲突不包括文件重命名,除非你编写了大量的 Java 代码......)。但有时,Git 自己无法做到这一点。在这种特殊情况下,Git 会将合并工作交还给您,此时您会看到索引中的所有 slots。
索引槽 1 是 Git 存储文件的 base 版本的位置。这是你们俩开始的通用版本。
索引槽 2 是文件的 HEAD 或 --ours 版本。 (有时也称为“本地”版本。)
索引槽 3 是文件的另一个或 --theirs(或有时是“远程”)版本。
这几乎就是它的全部内容:三个插槽包含三个版本,您最终会得到“未合并的路径”。工作树中的文件包含 Git 最初的合并工作,以及冲突标记和 --ours 和 --theirs 版本的一些混搭。如果您将 merge.conflictstyle 设置为 diff3,Git 还将包含 base 版本文件中的部分,存储在索引槽 1 中。现在由您来解决合并问题。 p>
确定正确的分辨率并更新文件的工作树版本后,您必须git add 路径。这会将工作树版本存储到索引槽 0 中,清除槽 1-3 中的三个条目。由于 slot 0 保存了正常的、待提交的版本,因此文件现在可以提交了。
对于根本没有更改的文件,或只是你们中的一个人更改了某些内容,或 Git 认为它正确地结合了您的更改和 Bob 的更改,那些文件已经在工作树和(零槽)索引中更新。如果结果与当前的HEAD 提交不同,git status 会将文件显示为“要提交的更改”。
最后几位
我在上面说过,这就是几乎的全部。关于索引槽,最后要记住的几件事是:
-
有些插槽可能是空的。假设您和 Bob 都添加了一个文件。在这种情况下,您会遇到“添加/添加冲突”。什么版本的文件是通用基础版本?当然没有:你们都添加了一个 new 文件。所以在这种情况下,基本插槽(插槽 1)是空的。
如果您删除一个文件而 Bob 更改了它,也会发生同样的情况。在这种情况下,--ours 插槽(插槽 2)是空的,而插槽 1 和 3 保存基本版本和 Bob 的版本。由您决定是保留 Bob 的版本、删除文件还是使用第三种变体,但同样只有三个插槽中的两个被填充。
-
如果你们中的一个或两个重命名一个文件,则三个槽中的文件版本可能来自早期提交中具有不同名称的文件。例如,您的输出显示:
renamed: app/OldName.php -> app/NewName.php
这里没有冲突,但如果有冲突,至少有一个app/NewName.php 的插槽将由其他提交版本的app/OldName.php 填充。
这主要在您查看旧版本时很重要。如果在单独的克隆或工作树中签出其中一个文件尚未重命名的提交,或者如果您使用git show <commit>:app/OldName.php 查看文件而不签出,则必须使用 old 名称,只要它有旧名称。但是,如果您使用git checkout --ours 或git checkout --theirs 将这两个版本中的一个提取到工作树中,则必须使用 new 名称,因为索引现在将文件存储在 new名字。
如果您这样做使用git checkout --ours 或git checkout --theirs 分别获取您或 Bob 的文件版本,这不会解析文件,但会破坏 Git 的尝试将它们合并到工作树中。如果要恢复 Git 合并它们的尝试,请使用 git checkout -m。请注意,所有这些都会覆盖文件的工作树版本,同时保留三个未解析的索引槽。
不过,奇怪的是,如果您 git checkout <commit-id> -- <path> 从某个特定提交中获取文件的旧版本,那么 覆盖 索引:它将提交的文件版本复制到索引槽零,破坏槽 1-3 条目。该文件现在似乎已解决!同样,您可以使用git checkout -m 恢复未解决的状态。 (当然这会覆盖工作树版本。)
同样,如果您在文件还没有完全完成的情况下错误地使用git add 解析文件,您可以git checkout -m 取消解析它 - 当然,这会覆盖工作 -树版本。如果您已经完成了大部分解析,那么您不妨完成解析并重新git add 结果。这将使用从工作树中新复制的版本替换零槽索引条目:文件保持解析,但准备下一次提交的版本更改为最新的git add-ed 版本。