理解的关键git merge是 Git 不会比较两件事。Git比较三个件事。
Git 不能直接比较所有三个。它必须一次比较它们两个。其中两件事是文件的两个分支提示版本(或分支提示提交;稍后我将详细讨论这一点),但 Git 不会将它们相互比较。这是第三个出现的地方:第三个文件是文件的合并基础版本。
请记住,合并的目标是合并更改。但是 Git 不存储更改。Git 存储快照。每次提交都完整且完整地存储每个文件:给定一个提交,Git 获取整个README.md,整个main.py,无论此特定提交中的其他文件是什么,这就是提交中的版本。
要从快照中获取更改,我们需要两个快照:旧的和新的。然后我们玩一个找不同的游戏。对于 Git,那就是git diff:你给它旧提交的哈希 ID 和新提交的哈希 ID,它会为在两者之间更改的每个文件做一个差异。的输出git diff是一系列指令:删除这些行,添加这些其他行。如果您拍摄原始快照并应用说明,您将获得新快照。
但是,当我们合并时,我们希望将(比如说)Alice完成的工作与 Bob 完成的工作结合起来。所以 Git 的作用是:
我们将共享提交(Alice 和 Bob 开始的提交)称为合并基础。这是合并的第三个输入。Git 使用存储库中的历史记录(提交)自动找到此合并基础提交。这意味着您需要同时拥有 Alice和Bob 的提交,以及导致这两个分支提示的所有提交,以便您也拥有共同的起点提交。
请记住,每个提交及其快照都记录了有关快照的一些信息:例如,创建者的姓名和电子邮件地址。有一个日期和时间标记的时候,他们做到了,并解释日志消息,他们可以使用,为什么他们把它。它还存储其直接的原始哈希ID母公司承诺:他们使用的承诺,通过git checkout,从他们提出之前开始自己的承诺。这些家长哈希标识形成向后寻找链条:如果Alice和Bob无论从承诺开始H,爱丽丝做两次提交I和J和Bob做两次提交K和L,向后链是这样的:
I <-J <-- (Alice's latest)
/
... <-F <-G <-H
\
K <-L <-- (Bob's latest)
Run Code Online (Sandbox Code Playgroud)
Git 会自动查找H,这是 Alice 和 Bob 的起点。1
找到后H,Git 现在实际上运行了这两个git diff命令:
git diff --find-renames hash-of-H hash-of-J: 爱丽丝改变了什么git diff --find-renames hash-of-H hash-of-L: 鲍勃改变了什么合并过程现在结合了这些更改。对于 中的每个文件H:
[Git] 合并时是否逐行比较?
对此的答案是否定的。正如您现在所看到的,Alice 的版本与 Bob 的版本没有可比性。有是比较排序行由线; 它git diff是用于比较的任何东西——基本版本与 Alice 的,以及基本版本与 Bob 的相同比较。整个过程通过对两对提交进行完整的提交范围比较开始。在提交范围的比较中,发现 Alice 和 Bob 都更改了一些特定的文件,现在逐行比较,或者真正的 diff-hunk-by-diff-hunk,比较很重要。但它们来自第三个版本。
我不想每次都使用“git diff”手动检查。
你不必。如果你愿意,你可以,但要做到这一点,你需要找到合并基础提交,使用git merge-base也许。但如果你不想,那么......不要。 Git会找到merge-base提交;Git会做这两个独立的git diff操作;Git会将 Alice 的更改与 Bob 的更改结合起来,如果更改的行重叠,或者在某些情况下为abut,或者如果两者都跨越到文件末尾,则声明冲突。
(GIT中,如果Alice和Bob都取得了确切相同的变化恰好相同的路线,Git的只是需要改变的一个副本,其他VCSes可以在这里宣布冲突,无论是出于懒惰,他们不检查的变化是一样的,只是它们重叠,或偏执狂:如果两个改变了同样的思路,也许正确的结果是没有。只是使用更改的一个副本的Git只是说:“正确的结果是变化的一个副本”)
在任何情况下,Git 都会将合并的更改应用于文件的合并基础版本。这就是结果,可能存在合并冲突(以及文件的工作树副本内的合并冲突标记)。
最后,注意--find-renames两个git diff命令中的 。Git 将尝试判断 Alice 和/或 Bob 是否重命名了合并基础提交中的任何文件。如果是这样,Git 将尝试在最终结果中保留重命名。无论是 Alice 还是 Bob 进行了重命名,这都是正确的。如果 Alice和Bob 都重命名了文件,Git 不知道要使用哪个最终名称,并声明重命名/重命名冲突。如果 Alice 或 Bob删除文件而另一个修改它,则会出现类似的问题,如果 Alice 和 Bob 添加一个具有相同名称的新文件,则会发生最后一个冲突。这些类型的冲突就是我所说的的高层冲突:他们整个影响文件(和/或自己的名字),而不是单独的行内的文件。低级冲突(文件中的行)和高级冲突之间的这种差异在您使用-Xoursor-Xtheirs选项时很重要。
1即使 Alice 只进行了一次提交,例如J,在 Carol 的一次提交上I,例如 Carol 在 上进行的提交,这也有效H。共同的出发点仍然是H。Git 甚至不查看每个提交的作者身份:它只是从两个分支提示向后工作。
有多种合并策略。Git 默认使用 3 路合并算法递归。
三向算法使用最后一次共同提交。
例如:
master: A -> B -> C
Run Code Online (Sandbox Code Playgroud)
创建新分支
master: A -> B -> C
\
branch: D
Run Code Online (Sandbox Code Playgroud)
一些新的提交
master: A -> B -> C -> E
\
branch: D -> F
Run Code Online (Sandbox Code Playgroud)
假设 a.txt 中进行的所有更改(空单元格对应于空行)
commit C commit E commit F
---------- ---------- ----------
line a line a
line b new line d
line c new line e
line a line b
line b new line f
line c
new line g line c
Run Code Online (Sandbox Code Playgroud)
如果我们合并两个分支(提交 E,提交 F)会发生什么。它会产生合并冲突吗?答案是否定的。因为git不会逐行比较文件。它比较线条的上下文。
对齐a.txt文件
commit C commit E commit F
---------- ---------- ----------
new line d
line a-----------line a-----------line a
new line e
line b-----------line b-----------line b
new line f
line c-----------line c-----------line c
new line g
Run Code Online (Sandbox Code Playgroud)
在上表中,更改是对齐的。提交 C(祖先提交)中的行是我们的参考。git 比较参考线的邻居。在示例中,我们有 4 个插槽:
如您所见,只有一个分支(提交 E、提交 F)可能会添加新内容,或者两个分支可能会添加相同的内容。否则,会发生合并冲突。