首页 > 解决方案 > 合并时 Git 似乎使用不正确的祖先作为基础

问题描述

我正在尝试将我们的发布分支合并到我们的功能分支中,但 git 似乎对共同祖先是什么感到困惑。

我正在使用 SourceTree v3.1.2 和 git v2.21.0.windows.1

在 2 月 7 日之前
,开发分支与发布分支并行运行,对开发进行了更改,并对发布进行了热修复。

2 月 7 日
发布分支到功能。

5 月 9 日
Development 分支并入 Release 分支。

5 月 15 日(今天)
尝试将 Release 分支合并到 Feature 分支。
我最终有 78 次冲突。

在查看冲突时,我可以看到三向合并是多么混乱,所以我不确定非冲突文件是否发生了同样的问题。乍一看,它们看起来还不错,但是它们很多,所以我不能确定。

我运行了以下 git 命令,该命令从 4 月 24 日开始在 Development 分支上返回看似随机的签入。

git merge-base (最新功能分支提交) (最新发布分支提交)

我原以为我会在 2 月 7 日之前的 Development 和 Release 分歧之前获得最后一次提交。

我试过了:

值得注意的是,几周前我尝试将 Development 分支(这次不是 Release 分支)合并到 Feature 分支中,一切似乎都很顺利。从那时起,我将 SourceTree 从 1.7 版升级到 3.1.2 版,这也需要 git 升级。

我对这个 git 的东西不是很好,所以任何帮助都将不胜感激。

标签: gitmerge

解决方案


我一直在尝试确定我是否可以强制使用一个共同的祖先来进行合并,但我不确定我是否可以从我所读到的内容中做到这一点。

不会。Git 会为您计算合并基数。它们基于提交图。

值得坐下来画图(或git log --graph为你画图)。尝试使用git log --all --decorate --oneline --graph(记住这是从 A DOG 获得帮助),并可能添加--simplify-by-decoration以帮助减少大量视觉混乱。

通过眼球寻找合并基地

通过简单的图表,很容易直观地看出哪个提交是其他两个提交的合并基础。例如,给定一个可以以这种方式显示的图表,稍后提交向右:

          o--o--L   <-- branch1 (HEAD)
         /
...--o--*
         \
          o--o--R   <-- branch2

L...提交(您当前签出的提交,HEAD,在 的尖端branch1)和提交R(在 的尖端)的合并基础branch2是 commit *

许多图表都是可怕的和纠结的,而且像这样在视觉上并不容易解析。即使是一些更简单的一开始也有点棘手。重复合并会发生一个非常常见的情况:

...--o--o--o--o--o--o--*--o--L   <-- branch1
         \     \        \
          o--o--A--o--o--B--o--R  <-- branch2

再一次, 和 的合并基础LRcommit *。有两个现有的合并,AB. 合并A是不相关的,因为合并B创建了从两个分支提示可到达的第一个提交:我们从 开始L并返回两个提交到*,我们从 开始R,返回两个提交到B,走到它的父节点*,并且已经达到共享提交。

变基

我试过[变基]

请注意,使用git rebase 副本提交,通常会丢弃合并。假设您有上面的图表,并合并到branch2,并且您决定git rebase branch2 branch1。这将枚举所有可以从branch2但不能从的提交branch1,不包括合并。虽然我们可以*branch2 branch1到达提交,但我们无法从 到达任何底行提交branch1。所以要复制的提交都是底行的所有提交。让我们给它们一个字母的名称,以便我们可以讨论它们:

...--o--o--o--o--o--o--*--o--L   <-- branch1
         \     \        \
          C--D--A--E--F--B--G--R  <-- branch2

(我保留了我们已经为 、 和 这里 使用的单字母A名称BR

变基操作——<code>git checkout branch2;git rebase branch1——选择底行的所有非合并提交并复制它们。副本在 commit 之后L,在 的尖端branch1,所以结果是:

                               C'-D'-E'-F'-G'-R'   <-- branch2
                              /
...--o--o--o--o--o--o--o--o--L   <-- branch1
         \     \        \
          C--D--A--E--F--B--G--R  <-- ???

虽然此时名称 branch2已被移动——它现在指向,即原始提交R'的副本——仍然存在。他们只是无法从名称中访问Rbranch2

如果有另一个分支——比如说branch3——从这些提交中的一部分或全部派生出来,我们可能会将其绘制在:

                               C'-D'-E'-F'-G'-R'   <-- branch2
                              /
...--o--o--o--o--o--o--o--o--L   <-- branch1
         \     \        \
          C--D--A--E--F--B--G--R  <-- ???
                       \
                        H--I   <-- branch3

由于RGB不容易找到,我们可以完全停止绘制它们:

                               C'-D'-E'-F'-G'-R'   <-- branch2
                              /
...--o--o--o--o--o--o--o--o--L   <-- branch1
         \     \
          C--D--A--E--F
                       \
                        H--I   <-- branch3

但是,提交C-D-A-E-F仍然存在并且很好,可以通过从branch3(commit I) 开始并向后工作来实现。

如果您现在选择branch3branch1orbranch2合并,则通过从两个分支提示开始并像往常一样向后工作来找到合并基础。在这种情况下,合并基础是我们通过从提交向上和向左到达的提交A(合并仍然可以从 的尖端到达branch3),因为该提交可以从两个分支尖端到达并且是“最接近末端” ,原来如此。

由于这种多名称到达旧提交的事情,通常不明智地重新设置支持其他分支的分支。重新设置包含合并的分支也很棘手。现代(2.18+)Git 有一个--rebase-merges选项可以让 Git 重新创建合并;较旧的 Git 也--preserve-merges尝试这样做,但比现代方法更脆弱。但是,在所有情况下,重要的是要知道,当 rebase 必须复制合并提交时,它实际上只是git merge再次运行,以便计算的合并基,并从头开始重做合并。

补充说明

请注意,提交的日期和时间戳在这里完全无关紧要。 只有图表很重要。提交及其节点哈希 ID 和父边哈希 ID 构成提交图。每个提交里面的内容是历史;合并基础中的内容和两个分支提示对git merge.

要让 Git 告诉您它用于合并基础的内容,请执行您所做的操作,但--all如果有多个合并基础,请添加:

git merge-base --all branch1 branch2

Git 将进行基于合并的查找:从名称指向的最后一次提交开始,沿着所有路径向后走,并找到最佳提交。然后它将打印出所有合并基的哈希 ID。git diff然后,合并将通过将(单个)合并基础与两个分支提示提交进行比较(与)进行比较。

如果有多个合并库,默认-s recursive策略将首先git merge在合并库本身上运行,1进行递归合并,直到 Git 可以提出单个提交用作合并过程的“虚拟合并库”。您可以通过使用来让 Git 随机(明显)选择其中一个-s resolve。这通常没有任何改进,但在复杂的情况下需要了解。


1除非你真的不走运/对你之前的图表制作非常疯狂,否则最多会有两个合并基础。如果还有更多,Git 合并两个,提交结果,与下一个合并,提交结果,依此类推。


推荐阅读