Git詳解——merge
merge:合併 commits
前面說到, pull 的內部操作其實是把遠端倉庫取到本地後(使用的是 fetch ),再用一次 merge 來把遠端倉庫的新 commits 合併到本地。這一節就說一下, merge 到底是什麼。
含義和用法
merge 的意思是「合併」,它做的事也是合併:指定一個 commit ,把它合併到當前的 commit 來。
具體來講, merge 做的事是: 從目標 commit 和當前 commit (即 HEAD 所指向的 commit )分叉的位置起,把目標 commit 的 路徑上的所有 commit 的內容一併應用到當前 commit
HEAD 指向了 master ,所以如果這時執行:
git merge branch1
Git 會把 5 和 6 這兩個 commit 的內容一併應用到 4 上,然後生成一個新的提交,並跳轉到提交資訊填寫的介面:
merge 操作會幫你自動地填寫簡要的提交資訊。在提交資訊修改完成後(或者你打算不修改預設的提 交資訊),就可以退出這個介面,然後這次 merge 就算完成了。
適用場景
merge 有什麼用?最常用的場景有兩處:
- 合併分支 當一個 branch 的開發已經完成,需要把內容合併回去時,用 merge 來進行合併。 那 branch 又應該怎麼用呢? 下節就說。
- pull 的內部操作。之前說過, pull 的實際操作其實是把遠端倉庫的內容用 fetch 取下來之後,用 merge 來合 並。
特殊情況1:衝突
merge 在做合併的時候,是有一定的自動合併能力的:如果一個分支改了 A 檔案,另一個分支改了 B 檔案,那麼合併後就是既改 A 也改 B,這個動作會自動完成;如果兩個分支都改了同一個檔案,但一個改的是第 1 行,另一個改的是第 2 行,那麼合併後就是第 1 行和第 2 行都改,也是自動完成。
但,如果兩個分支修改了同一部分內容, merge 的自動演算法就搞不定了。這種情況 Git 稱之為:衝突 (Conflict)。
直白點說就是,你的兩個分支改了相同的內容,Git 不知道應該以哪個為準。如果在 merge 的時候發生了這種情況,Git 就會把問題交給你來決定。具體地,它會告訴你 merge 失敗,以及失敗的原因:
git merge feature1
提示資訊說,在 test.txt 中出現了 "merge conflict",自動合併失敗,要求 "fix conflicts and then commit the result"(把衝突解決掉後提交)。那麼你現在需要做兩件事:
- 解決掉衝突
- 手動 commit 一下
1、解決衝突
解決掉衝突的方式有多個,我現在說最直接的一個。你現在再開啟 test.txt 看一下,會發現它的內容變了:
可以看到,Git 雖然沒有幫你完成自動 merge ,但它對檔案還是做了一些工作:它把兩個分支衝突的內容放在了一起,並用符號標記出了它們的邊界以及它們的出處。上面圖中表示, HEAD 中的內容是 行動硬碟(已買) ,而 feature1 中的內容則是 行動硬碟(不買了) 。這兩個改動 Git 不知道應該怎樣合併,於是把它們放在一起,由你來決定。假設你決定保留 HEAD 的修改,那麼只要刪除掉feature1 的修改,再把 Git 新增的那三行 <<< === >>> 輔助文字也刪掉,儲存檔案退出,所謂 的「解決掉衝突」就完成了。
你也可以選擇使用更方便的 merge 工具來解決衝突,這個你可以自己搜尋一下。
2、 手動提交
解決完衝突以後,就可以進行第二步—— commit 了。
# 這裡commit 前需要 add
git add test.txt
git commit
可以看到,被衝突中斷的 merge ,在手動 commit 的時候依然會自動填寫提交資訊。這是因為在發生衝突後,Git 倉庫處於一個「merge 衝突待解決」的中間狀態,在這種狀態下 commit ,Git 就會自動 地幫你新增「這是一個 merge commit」的提交資訊。
放棄解決衝突,取消 merge?
同理,由於現在 Git 倉庫處於衝突待解決的中間狀態,所以如果你最終決定放棄這次 merge ,也需要 執行一次 merge --abort 來手動取消它:
git merge --abort
輸入這行程式碼,你的 Git 倉庫就會回到 merge 前的狀態。
特殊情況 2:HEAD 領先於目標 commit
如果 merge 時的目標 commit 和 HEAD 處的 commit 並不存在分叉,而是 HEAD 領先於目標 commit :
那麼 merge 就沒必要再建立一個新的 commit 來進行合併操作,因為並沒有什麼需要合併的。在這 種情況下, Git 什麼也不會做, merge 是一個空操作。
特殊情況 3:HEAD 落後於 目標 commit——fast-forward
而另一種情況:如果 HEAD 和目標 commit 依然是不存在分叉,但 HEAD 不是領先於目標 commit ,而是落後於目標 commit :
那麼 Git 會直接把 HEAD (以及它所指向的 branch ,如果有的話)移動到目標 commit :
git merge feature1
這種操作有一個專有稱謂,叫做 "fast-forward"(快速前移)。
一般情況下,建立新的 branch 都是會和原 branch (例如上圖中的 master )並行開發的,不然沒必要開 branch ,直接在原 branch 上開發就好。但事實上,上圖中的情形其實很常見,因為這其實是 pull 操作的一種經典情形:本地的 master 沒有新提交,而遠端倉庫中有同事提交了新內容到 master :
那麼這時如果在本地執行一次 pull 操作,就會由於 HEAD 落後於目標 commit (也就是遠端的 master )而造成 "fast-forward":
git pull
簡單解釋一下上圖中的 origin/master 和 origin/HEAD 是什麼鬼:它們是對遠端倉庫的 master 和 HEAD 的本地映象,在 git pull 的「兩步走」中的第一步—— git fetch 下載遠端倉庫內容時,這兩個映象引用得到了更新,也就是上面這個動圖中的第一步: origin/master 和 origin/HEAD 移動到了最新的 commit 。
為什麼前面的圖里面從來都沒有這兩個「映象引用」?因為我沒有畫呀!其實它們是一直存在的。
而 git pull 的第二步操作 merge 的目標 commit ,是遠端倉庫的 HEAD ,也就是 origin/HEAD ,所以 git pull 的第二步的完整內容是:
git merge origin/HEAD
因此 HEAD 就會帶著 master 一起,也指向圖中綠色的最新 commit 了。
小結
本節對 merge 進行了介紹,內容大概有這麼幾點:
- merge 的含義:從兩個 commit 「分叉」的位置起,把目標 commit 的內容應用到當前 commit ( HEAD 所指向的 commit ),並生成一個新的 commit ;
- merge 的適用場景: ① 單獨開發的 branch 用完了以後,合併回原先的 branch ; ② git pull 的內部自動操作。
- merge 的三種特殊情況:① 衝突 。 原因:當前分支和目標分支修改了同一部分內容,Git 無法確定應該怎樣合併。應對方法:解決衝突後手動 commit 。 ② HEAD 領先於目標 commit :Git 什麼也不做,空操作;③ HEAD 落後於目標 commit :fast-forward。