Git合并过程中Git索引的内容如何演变(以及失败合并后索引中会有什么)?

8

我对git索引的内容有些模糊的概念,因为我使用git-addgit-commit命令时会涉及到它,但是当执行git-merge命令时,我并不知道这些内容会发生什么变化。特别是在合并失败(例如由于某些冲突)时,我想了解索引中保存的内容。


合并(merge)和变基(rebase)在某些方面相似。也许你可以通过执行 git rebase -i 命令,将所有的 pick 替换为 edit,然后观察 .git 修改来看看发生了什么。 - Asenar
@Asenar:相似性是因为rebase在内部是一系列的cherry-pick操作,对于每个被cherry-pick的提交,Git使用合并机制来应用该提交所暗示的更改。 - torek
谢谢您的准确 :) - Asenar
1个回答

12
针对任何给定的路径,在索引中最多会有四个“版本号”,从0到3编号。我将称它们为“槽”,就好像它们实际上对于每个条目都在那里,并且很容易进行索引(这使它们更容易考虑),尽管实际上只有在需要时才会动态地引入额外的版本。这些“虚拟插槽”可以是“空的”,表示该文件不存在。

(实际上,一旦在索引中创建了一个条目,如果需要,它将用标志位 CE_REMOVED 标记。这变得非常复杂,因为整个目录中的文件都可以被标记为“已删除”,然后可以创建一个名称与前一个目录相同的文件,并标记为“已添加”。让我们假装我们有固定的空插槽。:-) )

插槽#0 是“正常的”,未冲突的,一切顺利的条目。它包含一堆缓存数据,路径名和存储在仓库中的文件的 blob-ID(SHA-1)。

当合并成功时,一切“照常”,所以唯一的特殊情况是冲突的合并。当 slot 1、2 和/或 3 不为空时,合并是“冲突的”。跳过大部分机制,发生的是这样的。合并使用“最新的”名称填充所有插槽,并且:

  • 插槽0为空(您不能进行“提交”,直到您解决了冲突,在那时,除非您真的想要删除该文件,否则此插槽不会再为空)。
  • 插槽1(“基本”)填充具有共同祖先版本的版本。如果文件是新文件(在两个版本中都是),则此插槽为空。
  • 插槽2(“ours”)用目标(HEAD,除非您手动调用某些底层合并机制)版本填充。如果文件已在 HEAD / 合并目标中被删除,则此插槽为空。
  • 插槽3(“theirs”)以正在合并的版本填充。如果文件已在被合并的版本中被删除,则此插槽为空。
一旦你解决了冲突并 "git add",#0插槽将被填充为任何你 "add" 的内容,从而清除 #1 至 #3 中的条目——或者,如果你使用 "git rm" 命令删除了有冲突的文件,则其他阶段条目仍将被删除,但现在 #0 插槽保持为空,这也解决了冲突。
更具体地说,假设你有一个共同的祖先,其中包含(除其他文件外)这两个文件:
gronk
flibby

你目前在分支cleanup上,并且将gronk重命名为breem,并编辑了这两个文件以及flibby。然后你决定执行git merge work命令,合并来自工作区的修改,其中他们修改了gronk但没有重命名它,并删除了flibby。一些其他的文件已经成功合并。

索引中将包含三个版本的bleem和两个版本的flibby

$ git checkout cleanup
Switched to branch 'cleanup'
$ git merge work
CONFLICT (modify/delete): flibby deleted in work and modified
in HEAD. Version HEAD of flibby left in tree.
Auto-merging bleem
CONFLICT (content): Merge conflict in bleem
Automatic merge failed; fix conflicts and then commit the result.
$ git ls-files --stage
100644 4362aba7f3b7abf2da0d0ed558cbf5bc0d12e4b0 1   bleem
100644 49db92a61392e9fd691c4af6e1221f408452a128 2   bleem
100644 04b399c8fe321902ce97a1538248878756678ca2 3   bleem
100644 366b52546711401122b791457793a38c033838dd 1   flibby
100644 6fecb1480f45faaabc31b18c91262d03d3767cde 2   flibby
100644 7129c6edb96d08bb44ca1025eb5ae41d41be8903 0   x.txt
您可以使用git show :1:bleem查看bleem的原始(基本)版本。在基本版本中被称为gronk(在此情况下也是work中的名称),但现在被称为bleem,因为Git认为您已将gronk重命名为bleemcleanup中。(Git在合并基础和HEAD之间找到重命名,然后在必要时将相同的重命名应用于work,就像在这种情况下一样。)
同样,您可以使用git show :3:bleemgit show work:gronk查看work版本,并使用任何以下选项查看HEAD版本:git show HEAD:bleemgit show cleanup:bleemgit show :2:bleem(插槽2包含HEADcleanup版本,并根据HEAD中的名称进行命名)。
对于flibby,由于它在work中被删除,因此没有“theirs”(插槽3)版本。
要解决冲突,您只需要告诉git addgit rm更新插槽零条目并删除1至3条目。当然,使用git add时,进入插槽0的内容是当前工作目录中的任何内容,因此通常需要先编辑文件。

顺便提一下,我在上面标签2和3为“ours”和“theirs”。这也是git checkout的处理方式(git checkout --oursgit checkout --theirs让您将版本2或3写入插槽0;像大多数检出操作一样,“删除”其他插槽,从而解决冲突)。但是,在一个变基中,HEAD分支实际上是正在进行变基的分支,“theirs”版本是您正在变基的分支。因此,在我看来,ours/theirs术语并不是很好:在变基期间很容易混淆。

我还要注意的是,如果您处于冲突的合并中,git checkout -m将通过擦除插槽0并根据需要“复原”插槽1-3中的版本(并将冲突合并文件写入工作目录,同时遵循您的merge.conflictstyle设置中的任何更改),“重新创建”合并冲突。


这是一个非常好的答案。 - Edward Thomson

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