简体中文 ▾ 主题 ▾ 最新版本 ▾ git-merge-base 最后更新于 2.43.0

名称

git-merge-base - 为合并寻找尽可能好的共同祖先

概要

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 寻找两个提交之间用于三路合并的最佳共同祖先。如果一个共同祖先 B 是另一个共同祖先 A 的祖先,那么 A 优于 B。没有任何更好的共同祖先的共同祖先被称为最佳共同祖先,即合并基底(merge base)。请注意,一对提交可能有多个合并基底。

操作模式

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

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

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

--octopus

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

--independent

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

--is-ancestor

检查第一个 <commit> 是否是第二个 <commit> 的祖先,如果是则以状态 0 退出,否则以状态 1 退出。错误由非 0 且非 1 的状态表示。

--fork-point

查找一个分支(或任何指向 <commit> 的历史)从另一个分支(或任何引用) <ref> 分叉的点。这不仅会寻找两个提交的共同祖先,还会考虑 <ref> 的引用日志(reflog),以查看导致 <commit> 的历史是否从分支 <ref> 的早期版本分叉出来(参见下文对此模式的讨论)。

选项

-a
--all

输出提交的所有合并基底,而不是仅输出一个。

讨论

给定两个提交 ABgit merge-base A B 将输出一个可以通过父子关系从 AB 到达的提交。

例如,对于这种拓扑结构

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

AB 之间的合并基底是 1

给定三个提交 ABCgit merge-base A B C 将计算 A 与一个假设的提交 M 之间的合并基底,其中 MBC 的合并。例如,对于这种拓扑结构

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

git merge-base A B C 的结果是 1。这是因为使用 BC 之间的合并提交 M 的等效拓扑为

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

git merge-base A M 的结果是 1。提交 2 也是 AM 之间的共同祖先,但 1 是一个更好的共同祖先,因为 21 的祖先。因此,2 不是合并基底。

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

当历史涉及交叉合并时,两个提交可能有多个最佳共同祖先。例如,对于这种拓扑结构

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

12 都是 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 上所做的工作变基(rebase)到更新后的 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 - updated)
	  \
	   D0---D1---D (topic - old)

一个警告是,您仓库中的旧引用日志条目可能会被 git gc 过期删除。如果 B0 不再出现在远程跟踪分支 origin/master 的引用日志中,--fork-point 模式显然无法找到它并会失败,从而避免给出随机且无用的结果(例如没有 --fork-point 选项的相同命令所给出的 B0 的父节点)。

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

GIT

Git[1] 套件的一部分