M5 合併與衝突
- 分辨 fast-forward 和三方合併,並預測
git merge會產生哪一種; - 用合併基準解釋 Git 怎樣逐行決定合併結果,以及甚麼時候會衝突;
- 讀懂衝突標記,按步驟解決衝突,或放棄合併;
- 寫出不容易衝突的文件(知識工作的寫法)。
1合併:把另一條分支的工作收進來 What merging does
git merge feature 的意思是:把 feature 的工作,合併進我現在所在的分支。方向很重要:被改變的是目前的分支(HEAD 所指的那條),feature 本身不會動。所以合併之前,先切換到「要收工作的那條分支」,通常是 main。
git switch main # 先到要收工作的分支
git merge feature # 把 feature 收進來
Git 會按兩條分支的關係,選擇兩種做法之一。
2fast-forward:只是把標籤往前推 Fast-forward
如果 main 在 feature 開出之後沒有新的 commit,main 就是 feature 的祖先:feature 的歷史已經包含了 main 的全部。這時 Git 不需要真正「合併」,只要把 main 這張標籤往前推到 feature 的位置。
Fast-forward。3三方合併:用合併基準逐行決定 Three-way merge
如果兩條分支都有新的 commit(歷史分岔了),Git 就要真正合併內容。它找出三個版本:
- ours:目前分支最新的版本;
- theirs:要合併進來的分支最新的版本;
- 合併基準(merge base):兩條分支最近的共同祖先,也就是分岔的那一點。
有了基準,Git 就能判斷每一處是「誰改的」:
| 基準 → ours | 基準 → theirs | 結果 |
|---|---|---|
| 沒改 | 沒改 | 保持原樣 |
| 改了 | 沒改 | 用 ours 的改動 |
| 沒改 | 改了 | 用 theirs 的改動 |
| 改了 | 改了,而且改得一樣 | 用那個共同的改動 |
| 改了 | 改了,改得不一樣 | 衝突:Git 不知道該用哪個,交給你決定 |
合併成功後,Git 建立一個合併 commit:它和普通 commit 一樣是一張快照,唯一的分別是它有兩個 parent,把兩條歷史接回一起。
4衝突:Git 請你做決定 Merge conflicts
兩邊都改了同一處、改得又不一樣時,Git 會停下來,在檔案裏用衝突標記把兩個版本並排寫出:
研究背景:AI 工具在校園普及。
<<<<<<< HEAD ← 從這裏開始是 ours(目前分支)
樣本數:118 人(剔除 2 份無效問卷)。
======= ← 分隔線
樣本數:120 人。
>>>>>>> data ← 到這裏為止是 theirs(被合併的分支 data)
結論:多數學生每天使用。
衝突期間,倉庫處於「合併中」狀態:.git/MERGE_HEAD 記着被合併的分支,git status 會列出 both modified 的檔案。沒有衝突的部分,Git 已經自動合併好。
5沙盒:合併與解決衝突 Sandbox: merge and resolve
6解決衝突的步驟 Resolving step by step
- 看狀態:
git status,找出both modified的檔案。 - 找標記:在編輯器裏搜尋
<<<<<<<。 - 做決定:保留 ours、保留 theirs、兩者結合,或者寫一個新版本。這是內容上的判斷,不確定就問寫那部分的人。
- 刪標記:把
<<<<<<<、=======、>>>>>>>三行全部刪掉。 - 標記已解決:
git add <檔案>。 - 完成合併:
git commit(Git 已預備好說明Merge branch '…')。
處理到一半想重來?git merge --abort 會讓一切回到合併前的樣子。
add 的檔案還有沒有標記。提交前跑一次 git diff --check:它會指出殘留的衝突標記。很多編輯器(例如 VS Code 的 merge editor)會把衝突顯示成「接受目前/接受傳入/接受兩者」的按鈕,原理和沙盒的三個按鈕一樣;也可以用 git mergetool 開啟你設定的合併工具。
7知識工作:寫出不容易衝突的文件 Writing conflict-friendly prose
Git 以「行」為單位合併。程式碼天生一行一句;文章卻常常一整段寫成一行。結果是兩個人改同一段裏不同的句子,Git 也會判為衝突。
所以用 Git 管理文章時,最實用的習慣是一句一行(semantic line breaks):Markdown 顯示時會把相鄰的行接成同一段,讀者看不出分別,但合併和 diff 都會清楚得多。M12 會再談知識庫的其他做法。
8容易混淆的對照 Easily confused
fast-forward 與 --no-ff
| fast-forward(預設,條件許可時) | git merge --no-ff | |
|---|---|---|
| 做了甚麼 | 只把標籤往前推 | 即使可以 fast-forward,也建立合併 commit |
| 歷史形狀 | 一直線,看不出曾經有分支 | 保留「這幾個 commit 是一起從某分支合進來的」 |
| 撤銷整批工作 | 要逐個找出那幾個 commit | 撤銷那一個合併 commit 即可(M6) |
| 常見於 | 個人工作、小修改 | 團隊經 PR 合併功能分支(GitHub 的「Create a merge commit」) |
- 「
git merge feature會把 main 合進 feature。」相反。被改變的是你現在所在的分支。 - 「衝突代表有人做錯了。」不是。兩個人改了同一處,本來就需要一個人判斷;Git 只是誠實地說它判斷不了。
- 「合併後 feature 分支就消失了。」不會。它仍指着原來的 commit;確認不再需要後用
git branch -d刪除(已合併,所以不會被拒絕)。 - 「沒有衝突就代表合併結果一定正確。」不一定。兩邊各自正確的改動合在一起,仍然可能互相矛盾(例如一邊改了函數名稱,另一邊新增了舊名稱的呼叫)。合併後要測試或重讀。
9命令速查 Cheat sheet
| 命令 | 作用 |
|---|---|
git merge feature | 把 feature 合併進目前分支(能 fast-forward 就 fast-forward) |
git merge --no-ff feature | 一定建立合併 commit |
git merge --abort | 放棄進行中的合併,回到合併前 |
git status | 合併中:列出 both modified 的檔案 |
git add <檔案> | 標記該檔案的衝突已解決 |
git diff --check | 找出殘留的衝突標記 |
git merge-base main feature | 顯示兩條分支的合併基準 |
git log --oneline --graph --all | 在終端機看分岔與合併 |
git branch -d feature | 刪除已合併的分支 |
10小測 Quiz
本單元重點 Summary
git merge X把 X 收進目前的分支。- 目前分支是 X 的祖先 → fast-forward,只移標籤;否則 → 三方合併,產生有兩個 parent 的合併 commit。
- 三方合併用合併基準判斷每處是誰改的;兩邊在同一處或相鄰處改得不同,就是衝突。
- 解決衝突:status → 找標記 → 決定 → 刪標記 → add → commit;隨時可以
git merge --abort。 - 寫文章用一句一行,可以大幅減少不必要的衝突。
延伸閱讀
- Pro Git 3.2 Basic Branching and Merging
- Pro Git 7.8 Advanced Merging
- git merge 官方文件(「HOW CONFLICTS ARE PRESENTED」一節)
- Semantic Line Breaks:一句一行的寫法規範
- 有問題?記下來,它很可能也是其他同學的問題 → 學生問題庫