合并时git如何比较两个文件?

ern*_*rky 3 git git-merge

git 如何比较两个文件。哪些算法用于比较两个文件?合并时是否逐行比较?

我无法确定合并时两个文件的比较是否会产生冲突。

tor*_*rek 6

理解的关键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 开始的最佳共享提交。
  • 共享提交的文件与 Alice 的文件进行比较。这就是爱丽丝改变的地方
  • 共享提交的文件与 Bob 的文件进行比较。这就是鲍勃改变的。

我们将共享提交(Alice 和 Bob 开始的提交)称为合并基础。这是合并的第三个输入。Git 使用存储库中的历史记录(提交)自动找到此合并基础提交。这意味着您需要同时拥有 AliceBob 的提交,以及导​​致这两个分支提示的所有提交,以便您也拥有共同的起点提交。

请记住,每个提交及其快照都记录了有关快照的一些信息:例如,创建者的姓名和电子邮件地址。有一个日期和时间标记的时候,他们做到了,并解释日志消息,他们可以使用,为什么他们把它。它还存储其直接的原始哈希ID母公司承诺:他们使用的承诺,通过git checkout,从他们提出之前开始自己的承诺。这些家长哈希标识形成向后寻找链条:如果Alice和Bob无论从承诺开始H,爱丽丝做两次提交IJ和Bob做两次提交KL,向后链是这样的:

                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

  • Alice 是否更改了文件?Bob 是否更改了文件?
  • 如果两者都没有更改文件,请使用文件的任何副本:所有三个都相同。
  • 如果 Alice 更改了文件而 Bob 没有更改,请使用 Alice 的版本。
  • 如果 Bob 更改了文件而 Alice 没有更改,请使用 Bob 的版本。
  • 如果两者都更改了文件,则合并它们的更改。这是可能发生合并冲突的地方。

[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 进行了重命名,这都是正确的。如果 AliceBob 都重命名了文件,Git 不知道要使用哪个最终名称,并声明重命名/重命名冲突。如果 Alice 或 Bob删除文件而另一个修改它,则会出现类似的问题,如果 Alice 和 Bob 添加一个具有相同名称的文件,则会发生最后一个冲突。这些类型的冲突就是我所说的的高层冲突:他们整个影响文件(和/或自己的名字),而不是单独的行的文件。低级冲突(文件中的行)和高级冲突之间的这种差异在您使用-Xoursor-Xtheirs选项时很重要。


1即使 Alice 只进行了一次提交,例如J,在 Carol 的一次提交上I,例如 Carol 在 上进行的提交,这也有效H。共同的出发点仍然是H。Git 甚至不查看每个提交的作者身份:它只是从两个分支提示向后工作。


ern*_*rky 3

有多种合并策略。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 个插槽:

  • a 行上方:提交 e 添加新行 d
  • 在 a 行下方:提交 f 添加新行 e
  • b 行下方:提交 e 添加新行 f
  • 在 c 行下方:提交 g 添加新行 g

如您所见,只有一个分支(提交 E、提交 F)可能会添加新内容,或者两个分支可能会添加相同的内容。否则,会发生合并冲突。