我考虑合并的方式有什么根本错误吗?
是的:
...git diff src:somefile dst:somefile...
这不是合并所做的。合并一个分支并不意味着“让我的文件和他们的一样”。
提交图
让我们从绘制提交图开始:
... <- B <- C <- D <-- branch
在这里,大写字母代表提交 ID,即像 f931ca0... 这样的 40 个字符的 SHA-1 ID 之一。每个提交记录一个源代码树(例如,您可以使用<id>:path 查看该文件在该提交 ID 的内容)。它还记录一些父提交 ID:通常只有一个 ID,但有时会超过一个(“合并”提交),很少有零父 ID(“根”提交)。
branch 只是您的一个分支的名称,它包含提交 ID D,即分支名称“指向”该分支上最尖端的提交 D。提交D 指向C,后者又指向B,以此类推。
一个图变成“branch-y”或“branched”——不幸的是,这个术语与我们用来谈论一个分支的 name 的术语相冲突——当几个不同的提交指向同一个时父母。请记住,这里的箭头都指向左,即使它们指向左上或左下;并且由于文本样式的图表没有箭头空间,我们将在这里画一条线,如- 或\ 或/:
... B - C - D <-- br1
\
E - F <-- br2
这里分支br1 的尖端是commit D,分支br2 的尖端是commit F。您可以使用git rev-parse 向 git 询问实际 ID,具体的 40 个字符的东西:
$ git rev-parse br1
e59f6c2d348d465e3147b11098126d3965686098
例如。同样,我们说br1“指向”提交D,它又指向C,又指向B,以此类推。同时br2 指向F,E 又指向B。
任何给定分支名称指向的提交是该分支的“提示提交”。当您向分支添加新提交时,git 通过使新提交存储上一个提示的 ID 作为其父 ID 来执行此操作,一旦新提交完成并安全地存储在存储库中,更改存储在分支名称中的 ID .也就是说,如果你向br1添加一个新的提交,新的提交G会指向D,而git会让br1指向G:
... B - C - D - G <-- br1
\
E - F <-- br2
无论我们随着时间的推移提交了多少新的提示,提交B 仍然特别有趣。在图论术语中,这里的概念是“可达性”:commit B (总是)可以从 both 分支提示访问。
合并
那么,git merge 就是这样做的。你给它两个分支提示 ID - 其中一个是你现在所在的分支,另一个是你命名的分支 - 它:
- 标识从两个分支提示可到达的最近提交(这是“合并基础”);
- 从合并基础到当前分支提示进行差异;
- 从合并基础到另一个技巧的差异;
- 结合这两个差异来查看“双方”在哪里进行了相同的更改;
- 应用在步骤 3 中找到的更改,而不重新应用在步骤 4 中找到的组合更改;和
- 如果一切顺利,使用 两个 父级进行新的提交,这两个父级都是分支提示。
冲突发生在步骤 2 和 3 中发现的更改影响同一文件的同一区域,但完全相同相同(因此无法通过步骤 4 减去)。如果发生冲突,git 会让你解决它们;当你做最后的git commit 时,这仍然会产生同样的“合并提交”。
现在,假设在B-C-D-G 行中的某个地方,有人修改了路径README.txt 以在第一行包含一个感叹号。同时在B-E-F 行中,没有人对README.txt 这样做。最终的合并提交将保留此处所做的更改,但如果您将 br1:README 与 br2:README 进行比较,则会存在差异:br2 中的第一行将没有感叹号。这是因为合并的更改(base-to-br2)不会改变这一点;保留的更改(base-to-br1)确实如此。我们不希望将 base-to-br1 更改删除,我们只希望尚未存在的更改(未在“双方”进行)添加了。
无论如何,让我们在最后的合并提交H:
... B - C - D - G - H <-- br1
\ /
E ----- F <-- br2
现在br1:README 有! 但br2:README 没有。
简而言之,在合并之后,没有理由期望任何两个文件在新的分支提示中匹配。如果所有文件都必须完全匹配,则您将清除 br1 上的所有工作,仅将其替换为 br2 上的工作。