【问题标题】:Git merge affects on working directory and staging areaGit合并影响工作目录和暂存区
【发布时间】:2016-03-12 06:15:01
【问题描述】:

我是Git 的新手,想了解git merge 命令的工作原理。所以我尝试了一个简单的git项目,例如:

我有两个分支:masterb。您可以在此处查看两者的 git 日志(1,2,4 是在分支中创建的示例文件):

* cf3456b (HEAD, b) add and modify 4 in b
* 68b9086 edit 1 in branch b
| * 81e6490 (master) remove 1 from branch b1
| * e0a6844 modify 2 in branch b1
| * 06bad1d add2 in branch b1
|/  
* c667d3b add 1 in branch master

所以
master只有一个文件:2
b只有两个文件:1,4

现在当我尝试这样做时:

git merge master

我看到了这条消息:

CONFLICT(修改/删除):1 在 master 中删除并在 HEAD 中修改。 版本 HEAD 1 留在树中。自动合并失败;修复冲突 然后提交结果。

这对我来说很奇怪,我之前认为:

  1. merge 就像checkout 尝试将内容从merge in 分支的最新提交复制到merge into 分支的最新提交,但与checkout 相反,merge 试图不更改merge into 中的任何文件。最后,如果merge inmerge into 分支具有相同的文件但状态不同,则会发生冲突。
  2. 我知道git 只保存文件的snapshot 而不是differences。那么 git 是如何知道分支最新提交的变化的呢? (在消息中你可以看到1 deleted in master and modified in HEAD 有区别)

现在,如果我的想法是正确的,那么文件 1 应该不会发生冲突。所以我的想法是不正确的,但是merge 是如何工作的呢?

【问题讨论】:

标签: git version-control merge


【解决方案1】:

关于第 2 点,git 保存快照是正确的。然而,给定任意两个快照 ab,我们可以通过比较 ba 来获得一个变更集(差异)——git 会根据需要这样做。

至于合并本身,首先,我们必须注意git保留了一个提交图(也称为“DAG”或“提交DAG”,因为图是一个D 直接A循环Graph或DAG)。这就是您的git log --graph 输出显示的内容,尽管在这种特殊情况下,我们有一个非常简单的图形形式,它只是一棵树(直到提交合并)。

对于任何树,并且有许多 DAG,1 给定树中的两个节点,我们可以找到唯一的 Lowest Common 一个祖先节点,或LCA。这里的两个节点是两个分支的提示——提交cf3456b(您当前分支的提示)和81e6490master 的提示)——在这种特殊情况下,LCA 是第一次提交,@ 987654331@。在像这样的简单情况下,LCA 很容易在视觉上发现:您只需查看提交图以找到两个分支连接的位置(从那里返回到根的所有提交都在 both em> 分支)。

这个 LCA 节点是 合并基础。 Git 首先找到当前提交的合并基础和你给它的参数。 (对于“章鱼合并”,您可以指示 git 将多个提交合并到当前分支,这项工作涉及更多一点,但我们可以在这里忽略这些。)

接下来,给定现有的合并基础和两个不同的提示提交,git 必须计算两个变更集:一个从合并基础到当前提交,一个从合并基础到参数提交。请注意,git 会在整个过程中预先执行一次此操作,之后它可以继续执行合并操作。

现在,对于每个有更改的文件,git 必须合并更改。对于大多数简单的修改,方法很简单:如果文件1 仅在一个分支中被修改,则按原样修改。如果在两个分支中都对其进行了修改,请尝试合并修改,在两个分支都进行相同更改的情况下只复制一份。当然,如果两个分支对单个文件的同一区域进行了不同的更改,就会发生冲突。

对于文件创建或删除情况,或者当有重命名时,事情会变得有点棘手。如果一个文件在一个分支中被删除并且在另一个分支中未被触及,git 可以通过删除该文件来解决这个问题(如果它已经在HEAD 中删除,则“保持删除”,如果它在另一个提交中被删除,则删除它)。如果文件在一个分支中重命名,并在另一个分支中修改,git 也可以组合这些更改(在导入或保留其他更改的同时进行或保留重命名)。然而,对于其他任何事情,git 只是简单地声明一个冲突,举起它的比喻手,然后让你解决冲突。

在这种情况下,文件1 确实在您当前的分支中被修改,并且确实在master 中被删除。 Git 不确定是删除文件(根据 merge-base 对master diff 的指示),还是保留更改(根据 merge-base 对HEAD diff 的指示),所以它只剩下文件了和冲突。

如果你在两个分支中都创建了一个新文件5,git 会再次给你一个冲突(除非,如果文件的新内容在两个分支中相同——我没有测试过这种情况)。


1对于复杂的 DAG,可能有多个最低公共祖先候选者。对于 git 的“递归”合并,git 通过合并每个 LCA 候选来形成一个新的“虚拟基础”来处理这个问题。这个虚拟基础成为比较两个提交的合并基础。如果只有两个 LCA 候选者,则通过粗略等价获得虚拟合并基:

git checkout -b temp candidate_1
git merge candidate_2
git commit -a -m ""

在此“内部合并”期间发生的任何合并冲突都将被忽略:冲突的合并基础与它的冲突一起使用。这可能会导致一些看起来很奇怪的冲突,尤其是当“外部”合并的同一区域存在冲突时:请参阅this SO question

如果有两个以上的 LCA 候选者,merge-recursive 将前两个合并,然后合并第三个,然后合并第四个,以此类推,迭代。即使它可以合并对以将数字减半,然后合并合并的对以将其再次减半,它也会这样做,依此类推。也就是说,给定 N 个 LCA 候选者,可以进行 ceil(log2(N)) 合并,而不是 N-1 次合并,但 git 不会——无论如何 N 很少超过 2。

【讨论】:

【解决方案2】:

冲突信息:

CONFLICT(删除/修改):1 在 master 中删除并在 HEAD 中修改

表示 1 在你正在合并的主分支中被删除,但在 HEAD 中被修改(在你现在实际所在的分支中)。

所以你必须决定是否

remove file using "git rm 1"

accept version from HEAD with "git add 1"

【讨论】:

    猜你喜欢
    • 2016-06-16
    • 1970-01-01
    • 1970-01-01
    • 2014-10-21
    • 2020-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-03
    相关资源
    最近更新 更多