簡體中文 ▾ 主題 ▾ 最新版本 ▾ 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 找到在兩個提交之間用於三方合併的最佳共同祖先。如果一個共同祖先是另一個共同祖先的祖先,則前者比後者更好。沒有更好共同祖先的共同祖先是最佳共同祖先,即合併基礎。請注意,一對提交可能存在多個合併基礎。

操作模式

在最常見的特殊情況下,僅在命令列上指定兩個提交表示計算給定兩個提交之間的合併基礎。

更一般地,在用於計算合併基礎的兩個提交中,一個由命令列上的第一個提交引數指定;另一個提交是一個(可能是假設的)提交,它是命令列上所有其餘提交的合併。

因此,如果指定了兩個以上的提交,則合併基礎不一定包含在每個提交引數中。這與使用 --merge-base 選項時的 git-show-branch[1] 不同。

--octopus

計算所有提供的提交的最佳共同祖先,以準備進行 n 路合併。這模仿了 git show-branch --merge-base 的行為。

--independent

不打印合並基礎,而是列印所提供提交的一個最小子集,這些子集具有相同的祖先。換句話說,在給定提交中,列出那些無法從任何其他提交到達的提交。這模仿了 git show-branch --independent 的行為。

--is-ancestor

檢查第一個 <commit> 是否是第二個 <commit> 的祖先,如果為真則以狀態 0 退出,否則以狀態 1 退出。非 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

AB 的合併基礎是 12。兩者之間沒有優劣之分(都是*最佳*合併基礎)。當未提供 --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(即你的倉庫 reflog 所知的遠端跟蹤分支的舊尖端),以檢視你的 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)

需要注意的是,你的倉庫中較舊的 reflog 條目可能會被 git gc 過期。如果 B0 不再出現在遠端跟蹤分支 origin/master 的 reflog 中,--fork-point 模式顯然無法找到它並會失敗,從而避免給出隨機且無用的結果(例如 B0 的父級,就像沒有 --fork-point 選項的相同命令所給出的結果)。

此外,你使用 --fork-point 模式的遠端跟蹤分支必須是你的 topic 分支從其尖端分叉出來的分支。如果你從比尖端更舊的提交分叉,此模式將無法找到分叉點(想象在上面的示例歷史中 B0 不存在,origin/master 從 B1 開始,移動到 B2,然後是 B,而你的 topic 分支是在 origin/master 為 B1 時從 origin/master^ 分叉出來的;歷史形狀將與上面相同,只是沒有 B0,並且 B1 的父級是 git merge-base origin/master topic 正確找到的結果,但 --fork-point 模式不會,因為它不是曾經位於 origin/master 尖端的提交之一)。

GIT

Git[1] 套件的一部分

scroll-to-top