【Git 協同篇】三方合併(Three-Way Merge)揭秘:Git 到底如何尋找共同祖先(Merge Base)與判定每一行衝突? / [Git Three-Way Merge Internals] How Git Finds the Merge Base and Determines Line-by-Line Conflicts

在多名工程師協同開發大型系統時,「合併代碼」幾乎是每天都會執行的核心動作。當你在終端機下達 git merge feature,看到終端機回傳「Merge made by the 'ort' strategy.」並且幾十個檔案順暢無阻地合為一體時,你是否曾好奇:Git 究竟是憑藉什麼魔法,能精準知道哪一行是誰新增的、哪一行是誰刪除的?又是依據什麼規則,在雙方改動重疊時精準拋出衝突?

When multiple engineers collaborate on large-scale systems, merging branches is an indispensable daily routine. When you run git merge feature and watch the console announce "Merge made by the 'ort' strategy" while dozens of files seamlessly combine, have you ever wondered: What mathematical mechanics allow Git to discern exactly who added or removed each line? By what underlying rules does it flag conflicts when edits overlap?

承接我們上一篇深入探討的 Git Rebase 補丁重播與雜湊移植機制,今天我們將焦點轉向分支整合的另一大基石——「三方合併」(Three-Way Merge)。許多開發者以為合併只是把兩個分支的最新快照拿來互相比對,然而單純的「雙方比對」在數學邏輯上根本無法判定檔案到底是新增還是刪除。要解開這個謎題,必須深入探究 Git 如何在有向無環圖中尋找「共同祖先」(Merge Base),以及 Diff3 演算法如何在每一行代碼中進行裁決。

Continuing our previous deep dive into Git Rebase patch replay and hash recomputation, today we pivot to the other foundational pillar of branch integration: the Three-Way Merge. Many developers intuit merging as merely diffing two latest branch snapshots; however, two-way diffing is mathematically incapable of distinguishing additions from deletions. Resolving this conundrum requires examining how Git searches the DAG for the Merge Base and how Diff3 algorithms adjudicate changes line by line.

Git 三方合併核心拓撲結構與共同祖先定位示意圖 / Git Three-Way Merge core topology and Merge Base discovery diagram

圖一|三方合併拓撲:透過 git merge-base 錨定基準祖先,精準合成兩端差異快照(原創架構示意圖,非產品截圖)

Figure 1 | Three-way merge topology: Anchoring baseline ancestor via git merge-base to synthesize dual-side diff snapshots (Original architecture diagram, not a product screenshot).

現象與機制:為什麼必須是「三方」而非「雙方」? / Mechanism: Why Three-Way Rather Than Two-Way?

為什麼一定要有「第三個快照」?如果只做兩方比對(Two-Way Diff),假設在你的 main 分支裡某個檔案有 100 行,而在隊友的 feature 分支裡只剩下 80 行。僅憑這兩個分支的頂端快照,演算法根本無法判斷:究竟是隊友「刪除了 20 行舊代碼」,還是你「新增了 20 行新代碼」?只有引入雙方分叉當下的「共同祖先」(Base),這道題才有唯一解。只要對照 Base,發現 Base 原本就是 100 行,便能確信是隊友刪除了 20 行,Git 就能安心自動執行刪除操作。

Why is a third snapshot strictly necessary? In a naive two-way diff, suppose a file has 100 lines in your main branch but only 80 lines in your teammate's feature branch. Looking solely at these two endpoints, an algorithm cannot know whether your teammate deleted 20 legacy lines or you authored 20 new lines. Introducing the common ancestor (Base) provides the deterministic anchor: if Base had 100 lines, Git proves your teammate deleted 20 lines, allowing automatic and safe deletion.

這個關鍵的共同祖先,在 Git 底層由 git merge-base 指令負責定位。如圖一所示,Git 會在提交物件的有向無環圖(DAG)中,從兩個分支頂端反向搜尋,透過 LCA(Lowest Common Ancestor,最近共同祖先)圖演算法,找出兩者最近的一個共同父節點。在一般的單純分叉中,這個祖先是唯一的;即使在複雜的多重交叉合併(Criss-Cross Merge)中,現代 Git 預設的 ORT 合併引擎也能透過遞迴方式,為多個候選祖先合成分擬的虛擬祖先快照,確保基準定位精準無誤。

This pivotal common ancestor is computed by the git merge-base command under the hood. As illustrated in Figure 1, Git traverses backwards across the directed acyclic graph (DAG) from both branch tips, using Lowest Common Ancestor (LCA) algorithms to locate the closest shared parent node. In simple branching, this ancestor is unique; in complex criss-cross merges, Git's modern default ORT merge engine recursively synthesizes a virtual base snapshot across multiple candidate ancestors, guaranteeing unambiguous baseline alignment.

Diff3 行級衝突判定真值表與決策邏輯圖 / Diff3 line-level conflict resolution truth table and decision logic diagram

圖二|Diff3 行級決策真值表:BASE、OURS、THEIRS 六大情境判定與衝突標籤剖析(原創決策邏輯圖)

Figure 2 | Diff3 line-level decision truth table: Six-scenario evaluation across BASE, OURS, THEIRS and conflict marker anatomy (Original decision diagram).

底層裁決:Diff3 演算法的行級決策真值表 / Diff3 Line-Level Decision Truth Table

一旦確定了 BASE 快照,Git 就會啟動 Diff3 演算法,針對每個檔案依序計算「BASE 到 OURS(本地分支)」與「BASE 到 THEIRS(目標分支)」的差異區塊。如圖二的決策真值表所示,判定的核心邏輯非常直觀:如果某一行在 BASE 是 A,而 OURS 維持 A、THEIRS 改成了 B,代表這是目標分支的全新改動,Git 會自動合入 B;反之,若 OURS 改成 B 而 THEIRS 維持 A,Git 則會自動保留 B;甚至若雙方巧合改成了完全相同的內容,Git 也會順利採納。唯有當雙方在同一行「各自改成了不同內容」(例如一邊改為 B、另一邊改為 C),Git 才會宣告觸發衝突,並在原始碼中插入 <<<<<<< HEAD、======= 與 >>>>>>> 標記交由開發者決策。

Once the BASE snapshot is locked, Git engages the Diff3 algorithm to compute chunk differentials: BASE-to-OURS and BASE-to-THEIRS. As outlined in Figure 2's truth table, the adjudication logic is straightforward: If a line was A in BASE, stayed A in OURS, and became B in THEIRS, Git safely adopts B; conversely, if OURS changed to B while THEIRS stayed A, Git retains B; if both independently made identical changes, Git reconciles without hesitation. Only when both sides modify the exact same line into divergent values (e.g., B vs C) does Git flag a true conflict, generating <<<<<<< HEAD, =======, and >>>>>>> markers.

實測驗證紀錄(環境:Windows 11 / Git 2.52.0.windows.1):

本文在獨立沙盒建立基礎提交 A(雜湊 10e04dc)並分叉出 main 與 feature 分支。實測執行 git merge-base main feature,輸出精準對應共同祖先 A。隨後於兩分支分別修改互不相鄰的變數設定,執行 git merge feature 觸發 Git 2.52 內建之 ort 合併引擎,終端機回傳 Merge made by the 'ort' strategy 並自動合成完整配置;接續模擬同位置相斥寫入,成功觸發 Diff3 衝突標記防線,證實底層演算法行為與規格全然吻合。

Test Verification (Windows 11 / Git 2.52.0.windows.1): In an isolated sandbox repository, base commit A (SHA 10e04dc) was branched into main and feature. Executing git merge-base main feature precisely returned commit A. Non-overlapping modifications merged automatically via Git's native ort strategy engine ("Merge made by the 'ort' strategy"). Deliberate simultaneous edits on identical lines triggered the expected Diff3 conflict markers, confirming exact alignment with theoretical specifications.

工程師排查秘訣:遇到怪異衝突時如何精準除錯 / Troubleshooting: Reclaiming Clarity in Merge Conflicts

在工程實踐中,當團隊遇到看似莫名的合併衝突時,熟練的工程師能透過簡單的底層指令快速還原真相。首先,使用 git merge-base main feature 找出共同祖先提交雜湊;接著下達 git diff <base-sha> HEAD 與 git diff <base-sha> feature,就能立即獨立檢視雙方各自從祖先節點分叉後的所有實質變更,徹底釐清究竟是哪一段修改踩到了同一塊程式邏輯。理解三方合併的底層演算法,不僅能讓我們在面對衝突時胸有成竹,更是掌握大型團隊程式碼協同流程的必備內功。

In day-to-day engineering, when encountering baffling merge conflicts across hundreds of commits, experienced developers restore clarity via low-level primitives. First, locate the common ancestor using git merge-base main feature; then execute git diff <base-sha> HEAD and git diff <base-sha> feature to inspect the independent delta each branch accrued since diverging. Demystifying Three-Way Merge internals transforms intimidating conflict markers into transparent set-theoretic decisions, cementing robust architectural mastery.

# 實用排查指令清單:
# 1. 查找兩分支最近共同祖先 SHA
git merge-base main feature

# 2. 檢視本地分支相對於祖先的所有改動
git diff $(git merge-base main feature) HEAD

# 3. 檢視目標分支相對於祖先的所有改動
git diff $(git merge-base main feature) feature

# 4. 強烈推薦:設定三方衝突樣式(顯示祖先原本內容)
git config --global merge.conflictstyle zdiff3

📚 布萊嗯研究所・Git 底層架構專題導流:

👉 【下一篇預告】:【Git 暫存篇】git stash 底層揭秘:工作區暫存時究竟在背後建構了哪兩顆隱藏 Commit 物件?
工程師桌面選品 工作站與辦公好物:桌面整理、人體工學與清涼周邊

長時間排查多分支合併衝突、比對三方快照與閱讀代碼,合適的高清雙螢幕懸臂支架、人體工學垂直滑鼠與清涼桌面風扇能顯著減輕身心疲勞。點擊下方連結可前往蝦皮查看熱門工程師選品。

※ 利益揭露:本篇文章參與蝦皮分潤計畫,透過上方推廣連結成立有效訂單,本站可能獲得微薄分潤收益支持持續營運創作。商品即時售價、活動優惠與庫存以蝦皮賣場當下頁面為準。

官方查核與文獻依據 / Sources and Verification:

查核日期:2026-11-07。本文核心機制依據 Git 官方維護文檔與資料模型規格:

References: Official Git manual on git-merge-base, git-merge (ort merge driver), diff3 algorithm, and Pro Git branching fundamentals (Verified 2026-11-07). Original architectural diagrams produced for this article; not product screenshots.

留言

熱門文章