Git
简体中文 ▾ Topics ▾ Latest version ▾ git-merge-base last updated in 2.43.0

名称

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

概述

git merge-base [-a | --all] <提交> <提交>…​
git merge-base [-a | --all] --octopus <提交>…​
git merge-base --is-ancestor <提交> <提交>
git merge-base --independent <提交>…​
git merge-base --fork-point <引用> [<提交>]

描述

git merge-base 会找出两个提交之间的最佳共同祖先,用于三方合并。 如果一个公共祖先是另一个公共祖先的祖先,那么后者就比前者 “更好”。 没有更好的共同祖先的共同祖先就是 “最佳共同祖先”,即 “合并基础”。 请注意,一对提交可以有多个合并基础。

运行模式

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

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

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

--octopus

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

--independent

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

--is-ancestor

检查第一个 <提交> 是否是第二个 <提交> 的祖先,如果是,则以状态 0 退出;如果不是,则以状态 1 退出。 如果状态不是 0,则表示出错。

--fork-point

查找一个分支(或导致 <提交> 的任何历史)从另一个分支(或任何引用)<引用> 分支出去的时间点。这不仅仅是查找两个提交的共同祖先,而且还要考虑 <引用> 的引用日志,以查看导致 <提交> 的历史是否从分支 <引用> 的早期化身分叉而来(请参阅下文对该模式的讨论)。

选项

-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 是 B 的一个祖先 ...
fi

在现代 Git 中,可以用更直接的方式来表达:

if git merge-base --is-ancestor A B
then
	... A 是 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 并不是你写的内容;它是另一个提交从 B0 移到 B1 时丢弃的提交)。

git merge-base --fork-point origin/master topic 就是为这种情况而设计的。 它不仅会考虑 B,还会考虑 B0、B1 和 B2(即仓库的引用日志所知道的远程跟踪分支的旧提示),以查看你的特性分支是在哪个提交上建立的,并找到 B0,从而只重放你的特性上的提交,而不包括对方后来丢弃的提交。

因此

$ 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 模式显然就找不到它,并会失败,从而避免给出一个随机而无用的结果(例如 B0 的父分支,就像不使用 --fork-point 选项的同一命令给出的结果一样)。

另外,使用 --fork-point(分叉点)"模式的远程跟踪分支必须是你的主题从其顶点分叉出来的分支。 如果你从比顶端更早的提交中分叉,该模式将无法找到分叉点(想象一下,在上面的示例历史中,B0 并不存在,origin/master 从 B1 开始,移动到 B2,然后是 B,而你在 origin/master^ 分叉你的主题时,origin/master 是 B1;B1 的父提交会被 git merge-base origin/master topic 正确找到,但在 `-fork-point`模式下不会,因为它不是 origin/master 最前沿的提交之一)。

GIT

属于 git[1] 文档

scroll-to-top