这个问题不一定有唯一的答案,因此您必须决定一些约束和/或启发式方法,或者接受多个“下游”合并的可能性。问题的核心与多个合并基候选的问题相同——使用git merge-base --all 将它们全部列出,否则 Git 只会选择其算法中最先弹出的那个。我们可以这样做,或者找到所有最佳合并候选者。
你画了我通常喜欢横向渲染的东西,例如:
B--...--D
/ \
A F--G--H <-- branch1
\ /
C--...--E <-- branch2
但我们可能有这个:
B--C---D--E--... <-- branch1
/ \ /
A X
\ / \
F--G---H--I--... <-- branch2
在这种情况下,如果您允许同时考虑 branch1 和 branch2,则合并 D 和 H 同样是“分支重新合并的地方”的理想候选者。即使你不这样做,如果branch2 稍后合并回branch1:
B--C---D--E---J--... <-- branch1
/ \ / /
A X /
\ / \ /
F--G---H--I--... <-- branch2
那么就从branch1 开始(或结束于)D 和H 都是同样好的候选者。
无论如何,我们在这里需要的是枚举以您要考虑的一个或所有分支结尾的提交。为此,我们可以使用,例如:
git rev-list --ancestry-path ^B ^E branch1 branch2
这会发现提交是branch1的祖先或branch2,也是提交B的后代或提交E。 p>
要真正得到正确答案,我们想添加--children。这样,我们将获得每个提交的哈希 ID,以及该提交的子节点。 Git 通过在遍历链接时反转从子节点到父节点的反向连接来实现--children,这已经足够了;但我们不会看到提交B 或E。这是一个问题。要显示它们,我们可以添加--boundary。这并不理想:--boundary 有时会包含一些我们不想要的提交。幸运的是,它们都标有-,因此我们可以通过剔除那些不是我们关心的提交来排除额外的边界提交。
我不打算展示这些,但如果你这样做了,你现在将有一个列表,每行一个条目,每个节点(顶点)及其连接到其子节点的边。你现在可以问由这些 (V,E) 集合形成的 DAG 的 LCA 是什么?
如果我们可以只使用 Git 的 LCA 算法就好了,但是 Git 没有办法在任意图上调用它——我们只能在提交时调用它,并且实际的提交有父级,而不是子级。所以你必须自己写。请参阅Algorithm to find lowest common ancestor in directed acyclic graph?(不幸的是,没有接受的答案)。 This algorithm looks correct at first blush;它在图中具有 LCA 的两个标准定义之一。
但是,如果我们愿意接受一个不太好的答案,我们可以通过添加 --topo-order 得到在大多数情况下可能就足够的东西(以确保父母在他们所有的孩子之后出来)和--merges(省略所有不是合并提交的内容)。这将获得所有合并的列表。
我在这里做了一个带有简单案例的测试仓库:
$ git log --all --decorate --oneline --graph
* 91fcef6 (HEAD -> master) J
* d1e5905 I
* 5bf18a0 merge
|\
| * 49b2ba7 (sidebr) D
| * 725e5ea C
| * 36b830d (tag: B) B
* | 198a982 (tag: G) G
* | 216bc01 F
* | e905e59 E
|/
* 5df9428 initial
所以我现在可以使用B 和G 命名提交B 和G,而我想要的“朝这个方向移动”的分支就是master。所以:
$ git rev-list --topo-order --merges --ancestry-path ^B ^G master
5bf18a0797dfd78107928a9a4095f357cfabe914
这里的 last 行是“最接近”两个提交的合并。在这种情况下,这也是唯一的行,这就是我们想要的合并。
这里的缺陷一旦我们画出来就很明显了。假设我有一个更复杂的图表,例如:
I--J
/ \
H M--N
/ \ / \
/ K--L \
/ \
A P--Q <-- master
\ /
\ C--D /
\ / \ /
B G--O
\ /
E--F
如果我现在运行git rev-list --topo-order --merges --ancestry-path ^B ^H master,我将枚举提交P,然后以某种顺序同时枚举G 和M。所以 last 行要么是 commit G 要么是 commit M,虽然这两个 都是 合并,但它们不符合正确的标准:它们不不要合并B 和H。只有提交 P 才会这样做。
因此,要检查您是否有正确的答案(不处理多个 LCA 问题),您应该获取此 git rev-list 命令的每个输出行,可能以相反的顺序(考虑添加 --reverse),然后查看如果两个提交都是每个提交的祖先。像G 和M 这样的“内部”合并将只有一个提交作为祖先。要进行 is-ancestor 测试,请使用 git merge-base --is-ancestor:
if git merge-base --is-ancestor $commit1 $mergecommit &&
git merge-base --is-ancestor $commit2 $mergecommit; then
... we've found a correct candidate
else
... move on to another candidate
fi