1. 程式人生 > >Git詳解——merge

Git詳解——merge

merge:合併 commits

前面說到, pull 的內部操作其實是把遠端倉庫取到本地後(使用的是 fetch ),再用一次 merge 來把遠端倉庫的新 commits 合併到本地。這一節就說一下, merge 到底是什麼。

 

含義和用法

merge 的意思是「合併」,它做的事也是合併:指定一個 commit ,把它合併到當前的 commit 來。

 

具體來講, merge 做的事是: 從目標 commit 和當前 commit (即 HEAD 所指向的 commit )分叉的位置起,把目標 commit 的 路徑上的所有 commit 的內容一併應用到當前 commit

,然後自動生成一個新的 commit 。 例如下面這個圖中:

 

 

HEAD 指向了 master ,所以如果這時執行:

git merge branch1

 

Git 會把 56 這兩個 commit 的內容一併應用到 4 上,然後生成一個新的提交,並跳轉到提交資訊填寫的介面:

 

merge 操作會幫你自動地填寫簡要的提交資訊。在提交資訊修改完成後(或者你打算不修改預設的提 交資訊),就可以退出這個介面,然後這次 merge 就算完成了。

 

 

適用場景

merge 有什麼用?最常用的場景有兩處:

  1. 合併分支 當一個 branch 的開發已經完成,需要把內容合併回去時,用 merge 來進行合併。 那 branch 又應該怎麼用呢? 下節就說。
  2. 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"(把衝突解決掉後提交)。那麼你現在需要做兩件事:

  1. 解決掉衝突
  2. 手動 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 時的目標 commitHEAD 處的 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/masterorigin/HEAD 是什麼鬼:它們是對遠端倉庫的 masterHEAD 的本地映象,在 git pull 的「兩步走」中的第一步—— git fetch 下載遠端倉庫內容時,這兩個映象引用得到了更新,也就是上面這個動圖中的第一步: origin/masterorigin/HEAD 移動到了最新的 commit

 

為什麼前面的圖里面從來都沒有這兩個「映象引用」?因為我沒有畫呀!其實它們是一直存在的。

 

git pull 的第二步操作 merge 的目標 commit ,是遠端倉庫的 HEAD ,也就是 origin/HEAD ,所以 git pull 的第二步的完整內容是:

git merge origin/HEAD

 

因此 HEAD 就會帶著 master 一起,也指向圖中綠色的最新 commit 了。

 

小結

本節對 merge 進行了介紹,內容大概有這麼幾點:

  1. merge 的含義:從兩個 commit 「分叉」的位置起,把目標 commit 的內容應用到當前 commitHEAD 所指向的 commit ),並生成一個新的 commit
  2. merge 的適用場景: ① 單獨開發的 branch 用完了以後,合併回原先的 branch ; ② git pull 的內部自動操作。
  3. merge 的三種特殊情況:① 衝突 。 原因:當前分支和目標分支修改了同一部分內容,Git 無法確定應該怎樣合併。應對方法:解決衝突後手動 commit 。 ② HEAD 領先於目標 commit :Git 什麼也不做,空操作;③ HEAD 落後於目標 commit :fast-forward。