簡體中文 ▾ 主題 ▾ 最新版本 ▾ 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 C1。這是因為帶有 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。試想一下,你現在想將你在 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,你在 origin/master 是 B1 時在 origin/master^ 分叉了你的 topic;歷史記錄的形狀將與上面相同,沒有 B0,而 git merge-base origin/master topic 正確找到的是 B1 的父節點,但 --fork-point 模式則不會,因為它不是 origin/master 尖端曾經是其中的一個提交)。

GIT

Git[1] 套件的一部分