Skip to content

git-merge-base

为合并查找尽可能好的公共祖先。

概要

txt
'git merge-base' [-a | --all] <commit> <commit>...
'git merge-base' [-a | --all] --octopus <commit>...
'git merge-base' --is-ancestor <commit> <commit>
'git merge-base' --independent <commit>...
'git merge-base' --fork-point <ref> [<commit>]

描述

'git merge-base' 查找两个提交之间用于三方合并的最佳公共祖先。如果后一个公共祖先是前一个公共祖先的祖先,则一个公共祖先比另一个"更好"。没有任何更好公共祖先的公共祖先就是"最佳公共祖先",即"合并基础"。注意,一对提交可能有多个合并基础。

操作模式

在最常见的特殊情况下,命令行上只指定两个提交意味着计算给定两个提交之间的合并基础。

更一般地说,在要计算合并基础的两个提交中,一个由命令行上的第一个提交参数指定;另一个提交是(可能假设的)跨命令行上所有其余提交的合并提交。

因此,如果指定了两个以上的提交,'merge base' 不一定包含在每个提交参数中。这与 git-show-branch(1)--merge-base 选项一起使用时不同。

--octopus

:计算所有提供的提交的最佳公共祖先,为 n 路合并做准备。这模仿了 'git show-branch --merge-base' 的行为。

--independent

:不是打印合并基础,而是打印具有相同祖先的提供的提交的最小子集。换句话说,在给定的提交中,列出不能从任何其他提交到达的那些。这模仿了 'git show-branch --independent' 的行为。

--is-ancestor

:检查第一个 <commit> 是否是第二个 <commit> 的祖先,如果是则以状态 0 退出,否则以状态 1 退出。错误通过非零状态(不是 1)发出信号。

--fork-point

:查找分支(或任何通向 <commit> 的历史)从另一个分支(或任何引用)<ref> 分叉出来的点。这不仅仅查找两个提交的公共祖先,还考虑 <ref> 的引用日志以查看通向 <commit> 的历史是否从分支 <ref> 的较早版本分叉出来(有关此模式的讨论见下文)。

选项

-a, --all

输出所有合并基础,而不仅仅是一个。

讨论

给定两个提交 'A' 和 'B',git merge-base A B 将输出一个从 'A' 和 'B' 都可通过父关系到达的提交。

例如,使用此拓扑:

	 o---o---o---B
	/
---o---1---o---o---o---A

'A' 和 'B' 之间的合并基础是 '1'。

给定三个提交 'A'、'B' 和 'C',git merge-base A B C 将计算 'A' 和假设的提交 'M'('B' 和 'C' 的合并)之间的合并基础。例如,使用此拓扑:

       o---o---o---o---C
      /
     /   o---o---o---B
    /   /
---2---1---o---o---o---A

git merge-base A B C 的结果是 '1'。这是因为与 'B' 和 'C' 之间的合并提交 'M' 等效的拓扑是:

       o---o---o---o---o
      /                 \
     /   o---o---o---o---M
    /   /
---2---1---o---o---o---A

git merge-base A M 的结果是 '1'。提交 '2' 也是 'A' 和 'M' 之间的公共祖先,但 '1' 是更好的公共祖先,因为 '2' 是 '1' 的祖先。因此,'2' 不是合并基础。

git merge-base --octopus A B C 的结果是 '2',因为 '2' 是所有提交的最佳公共祖先。

当历史涉及交叉合并时,两个提交可能有多个"最佳"公共祖先。例如,使用此拓扑:

---1---o---A
    \ /
     X
    / \
---2---o---o---B

'1' 和 '2' 都是 A 和 B 的合并基础。两者都不比另一个好(都是"最佳"合并基础)。当未给出 --all 选项时,输出哪个最佳基础是未指定的。

检查两个提交 A 和 B 之间"快速前进"的常见习惯用法是(或者至少曾经是)计算 A 和 B 之间的合并基础,并检查它是否与 A 相同,在这种情况下,A 是 B 的祖先。您会经常在较旧的脚本中看到这种习惯用法。

A=$(git rev-parse --verify A)
if test "$A" = "$(git merge-base A B)"
then
	... A is an ancestor of B ...
fi

在现代 git 中,您可以更直接地表达:

if git merge-base --is-ancestor A B
then
	... A is an ancestor of B ...
fi

fork-point 模式的讨论

在使用 git switch -c topic origin/master 创建的 topic 分支上工作后,远程跟踪分支 origin/master 的历史可能已被回退和重建,导致如下形状的历史:

		 o---B2
		/
---o---o---B1--o---o---o---B (origin/master)
	\
	 B0
	  \
	   D0---D1---D (topic)

其中 origin/master 曾经指向提交 B0、B1、B2,现在指向 B,而您的 topic 分支是在 origin/master 位于 B0 时在其之上开始的,并且您在其之上构建了三个提交 D0、D1 和 D。想象一下,您现在想将您在 topic 上所做的工作变基到更新后的 origin/master 之上。

在这种情况下,git merge-base origin/master topic 将返回上图中 B0 的父提交,但 B0^..D 不是您想在 B 之上重放的提交范围(它包含 B0,这不是您写的;它是另一方在将其提示从 B0 移动到 B1 时丢弃的提交)。

git merge-base --fork-point origin/master topic 旨在帮助解决此类情况。它不仅考虑 B,还考虑 B0、B1 和 B2(即您的仓库引用日志知道的远程跟踪分支的旧提示),以查看您的 topic 分支是在哪个提交之上构建的,并找到 B0,允许您只重放 topic 上的提交,排除另一方后来丢弃的提交。

因此

$ fork_point=$(git merge-base --fork-point origin/master topic)

将找到 B0,而

$ git rebase --onto origin/master $fork_point topic

将在 B 之上重放 D0、D1 和 D 以创建如下形状的新历史:

		 o---B2
		/
---o---o---B1--o---o---o---B (origin/master)
	\                   \
	 B0                  D0'--D1'--D' (topic - 更新后)
	  \
	   D0---D1---D (topic - 旧)

一个警告是,您仓库中较旧的引用日志条目可能会被 git gc 过期。如果 B0 不再出现在远程跟踪分支 origin/master 的引用日志中,--fork-point 模式显然无法找到它并失败,避免给出随机和无用的结果(如 B0 的父提交,就像不带 --fork-point 选项的同一命令给出的那样)。

此外,您使用 --fork-point 模式的远程跟踪分支必须是您的 topic 从其提示分叉出来的那个。如果您从比提示更旧的提交分叉出来,此模式将找不到分叉点(想象在上面的示例历史中 B0 不存在,origin/master 从 B1 开始,移动到 B2 然后 B,而您在 origin/master^ 时分叉了您的 topic;历史的形状将与上面相同,没有 B0,而 B1 的父提交是 git merge-base origin/master topic 正确找到的,但 --fork-point 模式不会,因为它不是曾经位于 origin/master 提示的提交之一)。

另请参阅

git-rev-list(1)git-show-branch(1)git-merge(1)

Git

git(1) 套件的一部分

基于 CC BY-NC-SA 3.0 许可发布