【问题标题】:How does git compare two files while merging?git在合并时如何比较两个文件?
【发布时间】:2019-11-15 06:46:18
【问题描述】:

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

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

【问题讨论】:

标签: git git-merge


【解决方案1】:

理解git merge 的关键是Git 不会比较两个 的东西。 Git 比较 三个 事物。

Git 无法直接比较所有三个。它必须一次比较它们两个。其中两件事是文件的两个分支提示版本(或分支提示提交;我稍后会详细讨论),但 Git 不会将它们相互比较。这是第三个出现的地方:第三个文件是文件的 merge base 版本。

请记住,合并的目标是合并更改。但是 Git 不存储更改。 Git 存储快照。每个提交都完整且完整地存储每个文件:给定一个提交,Git 获取整个 README.md、整个 main.py,无论此特定提交中的其他文件是什么,这就是提交中的版本。

要从快照中获取更改,我们需要 两个 快照:旧的和新的。然后我们玩Spot the Difference的游戏。对于 Git,它是 git diff:你给它旧提交的哈希 ID 和新提交的哈希 ID,它会为两者之间更改的每个文件创建一个差异。 git diff 的输出是一系列指令:删除这些行,添加这些其他行。如果您拍摄原始快照并应用说明,您将获得新快照。

但是,当我们合并时,我们希望将(比如说)Alice 所做的工作与 Bob 所做的工作结合。所以 Git 所做的是:

  • 找到最佳的共享提交,爱丽丝和鲍勃都开始使用。
  • 比较 shared 提交的文件和 Alice 的文件。这就是爱丽丝改变的地方。
  • 比较 shared 提交的文件和 Bob 的文件。这是鲍勃改变的地方。

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

请记住,每个提交及其快照都会记录一些关于快照的信息:例如,制作者的姓名和电子邮件地址。 当他们制作它时有一个日期和时间戳,以及一个他们可以用来解释他们制作它的为什么的日志消息。它还存储其直接父提交的原始哈希ID:他们使用的提交,通过git checkout,从他们进行他们的提交之前开始。这些父哈希 ID 形成一个向后看的链:如果 Alice 和 Bob 都从提交 H 开始,并且 Alice 做了两个提交 I 和 J 并且 Bob 做了两个提交 K 和 L,则向后链看起来像这样:

                I <-J   <-- (Alice's latest)
               /
... <-F <-G <-H
               \
                K <-L   <-- (Bob's latest)

Git 会自动找到H,Alice 和 Bob 都是从这里开始的。1

找到H,Git 现在实际上运行这两个git diff 命令:

  • git diff --find-renames <em>hash-of-H</em> <em>hash-of-J</em>:爱丽丝改变了什么
  • git diff --find-renames <em>hash-of-H</em> <em>hash-of-L</em>: Bob 改变了什么

合并过程现在结合了这些更改。对于H中的每个文件:

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

[Git] 合并时是否逐行比较?

这个问题的答案是“不”和“是”。如您现在所见,Alice 的版本与 Bob 的版本没有可比性。 有一个比较——一种逐行的比较;这是git diff 所做的比较——base 版本与 Alice 的比较,base 版本与 Bob 的比较。整个过程通过对两对 commits 进行完整的提交范围比较来开始。在该提交范围的比较中,发现 Alice 和 Bob 都更改了某些特定文件,现在逐行更改,或者真正的 diff-hunk- by-diff-hunk,比较很重要。但它们来自第三个​​版本。

我不想每次都使用“git diff”手动检查。

您不必这样做。如果您想要可以这样做,但要做到这一点,您需要找到合并基础提交,也许使用git merge-base。但如果你不想,那么……不要。 Git 会找到基于合并的提交; Git 将执行两个独立的git diff 操作; Git 会将 Alice 的更改与 Bob 的更改结合起来,如果更改的行重叠,或者在某些情况下,abut,或者如果两者都跨越到文件末尾,则会声明冲突。

(对于 Git,如果 Alice 和 Bob exactly 对 exactly 相同的行进行了相同的更改,Git 只需复制一份更改。其他 VCS 可能会声明这里有冲突,要么是因为懒惰——他们不检查更改是否相同,只是因为它们重叠——要么是偏执狂:如果两者都更改了相同的行,那么正确的结果可能是 not使用更改的一份副本。Git 只是说“正确的结果是更改的一份副本”。)

在任何情况下,Git 都会将 combined 更改应用到文件的 merge base 版本。这就是结果,可能存在合并冲突(以及文件的工作树副本中的合并冲突标记)。

最后,注意两个git diff 命令中的--find-renames。 Git 将尝试判断 Alice 和/或 Bob 是否重命名合并基础提交中的任何文件。如果是这样,Git 将尝试在最终结果中保留重命名。无论是 Alice 还是 Bob 进行了重命名,这都是正确的。如果 Alice 和 Bob 都重命名了文件,Git 不知道要使用哪个最终名称,并声明 rename/rename 冲突。如果 Alice 或 Bob deletes 文件而另一个人修改它,则会出现类似的问题,并且如果 Alice 和 Bob 添加一个 new 文件,则会发生最后一个冲突一样的名字。这些类型的冲突就是我所说的高级冲突:它们影响整个文件(和/或它们的名称),而不是文件中的单个行。当您使用-Xours 或-Xtheirs 选项时,低级别冲突(文件中的行)和高级别冲突之间的差异很重要。


1即使 Alice 只在 J 上进行了 一个 提交,也可以在 Carol 的一次提交 I 之上(比如)Carol 在 H 之上提交.共同的起点仍然是H。 Git 甚至不查看每个提交的作者身份:它只是从两个分支提示向后工作。

【讨论】:

  • 谢谢!!这是迄今为止我发现的最美丽、最根本的解释。
【解决方案2】:

有几种合并策略。 Git 中默认使用 3-way 合并算法递归。

三向算法使用最后一次共同提交。

例如:

master: A -> B -> C

创建新分支

master: A -> B -> C
                   \
branch:             D

一些新的提交

master: A -> B -> C -> E
                   \
branch:             D -> F

假设在 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

如果我们合并两个分支(提交 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

在上表中,更改是对齐的。提交 C(祖先提交)中的行是我们的参考。 git 比较参考线的邻居。在示例中,我们有 4 个插槽:

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

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

【讨论】:

    【解决方案3】:

    它使用delta compression。我们必须明白,当我们add 获取一个文件时,我们创建了一个对象,该对象的 sha 总和计算并记录在索引中。 git 所做的是,通过git-repack,它将压缩对象(使用增量压缩压缩)放入一个包(一个文件)中。当您进行提交时,git 正在获取未压缩的对象并使用一些内部规则,它正在创建一个包含对象之间差异和相似之处的文件。这种包的创建使用增量压缩。

    这种 delta 压缩,也就是 delta 差分,就是您要问的问题。我猜这个算法如何工作的范围超出了这个问题,所以这里有一些参考资料可以帮助你。

    Algorithms for Delta Compression

    How git treats each file

    git-repack

    delta differencing

    【讨论】:

    • 问题是关于合并的差异,而不是打包的差异。
    • @RaymondChen 没有。问题是关于用于产生差异的算法。我只是为该主题提供了更多信息。我的回答确实涵盖了手头的问题。
    • Delta 压缩是一种区分二进制文件的算法。我不知道git 是否使用它来存储 blob,但它肯定不会将它用于差异和合并。
    • 很公平,也很重要。如果我花时间更好地搜索,我会找到这两个来源 - artitle 和 documentation
    猜你喜欢
    • 1970-01-01
    • 2014-12-20
    • 2015-12-05
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    • 2015-04-10
    • 2017-10-25
    • 1970-01-01
    相关资源
    最近更新 更多