Git
英语 ▾ 主题 ▾ 最新版本 ▾ 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 查找两个提交之间的最佳公共祖先,用于三路合并。如果一个公共祖先是另一个公共祖先的祖先,那么它就比另一个公共祖先更好。没有更优的公共祖先的公共祖先是最佳公共祖先,即合并基。请注意,一对提交可能有多个合并基。

操作模式

在最常见的特殊情况下,仅在命令行上指定两个提交表示计算给定的两个提交之间的合并基。

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

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

--octopus

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

--independent

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

--is-ancestor

检查第一个 <提交> 是否是第二个 <提交> 的祖先,如果为真,则退出状态为 0,否则退出状态为 1。错误由非零状态(不是 1)表示。

--fork-point

找到分支(或通向 <提交> 的任何历史记录)从另一个分支(或任何引用)<引用> 分叉的位置。这不仅会寻找两个提交的公共祖先,还会考虑 <引用> 的 reflog,以查看通向 <提交> 的历史记录是否从分支 <引用> 的早期版本分叉(见下文对此模式的讨论)。

选项

-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 之间的合并基,该提交是 BC 的合并。例如,使用此拓扑结构

       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

代替。

关于分叉点模式的讨论

在使用 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。假设你现在想将你在主题上所做的工作重新基到更新后的 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(即你的存储库 reflog 知道关于远程跟踪分支的旧顶端),以查看你的主题分支是在哪个提交上构建的,并找到 B0,使你只重播主题上的提交,不包括对方后来丢弃的提交。

因此

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

将找到 B0,并且

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

将 D0、D1 和 D 重播到 B 之上,以创建一个新的历史记录,其形状为

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

需要注意的是,你的存储库中的较旧 reflog 条目可能会被 git gc 过期。如果 B0 不再出现在远程跟踪分支 origin/master 的 reflog 中,则 --fork-point 模式显然无法找到它,并会失败,以避免给出随机且无用的结果(例如 B0 的父级,就像没有 --fork-point 选项的相同命令所给出的结果)。

此外,你使用 --fork-point 模式使用的远程跟踪分支必须是你的主题从其顶端分叉的分支。如果你从比顶端更旧的提交分叉,此模式将无法找到分叉点(假设在上例中 B0 不存在,origin/master 从 B1 开始,移动到 B2 然后到 B,并且你在 origin/master^ 上分叉了主题,当时 origin/master 在 B1;历史记录的形状与上面相同,没有 B0,B1 的父级是 git merge-base origin/master topic 正确找到的,但 --fork-point 模式不会,因为它不是曾经位于 origin/master 顶端的提交之一)。

GIT

git[1] 套件的一部分

scroll-to-top