使用分支——Git Merge命令

swearer23發表於2022-02-16

在Git中merge是用來把分叉的提交歷史放回到一起的方式。git merge命令用來將你之前使用git branch命令建立的分支以及在此分支上獨立開發的內容整合為一個分支。

請注意下面的所有命令都會是將其他分支合併到當前所在工作分支上。當前工作分支的內容會由於merge操作產生更新,但是目標分支則完全不受影響。再次強調,這意味著git merge通常與其他幾個git命令一起使用,包括使用git checkout命令來選擇當前工作分支,以及使用git branch -d命令來刪除已經合併過的廢棄分支。

它是如何執行的

git merge會將多個提交序列合併進一個統一的提交歷史。在最常見的使用場景中,git merge被用來合併兩個分支。在本文件接下來的部分,我們會專注於這種合併場景。在這種場景中,git merge接受兩個commit指標,通常是兩個分支的頂部commit,然後向前追溯到這兩個分支最近的一個共同提交。一旦找到這個共同提交,Git就會建立一個新的"merge commit",用來合併兩個分支上各自的提交序列。

比如說我們有一個功能分支由main分支派生出來,現在我們希望將這個功能分支合併回main分支。

執行合併命令會將指定分支合併到當前工作分支上,我們假設當前工作分支為main。Git根據兩個分支自行決定合併提交的演算法(將在下面具體討論)。


合併commit與普通commit不一樣,因為合併commit會有兩個父提交。建立一個合併commit時Git會嘗試自動將兩個獨立的提交歷史合併為一個。不過當Git發現某一塊資料在兩邊的提交歷史中都含有變更,它將無法自動對其進行合併。這種情況被稱為版本衝突,此時Git需要人為介入調整才能繼續進行合併。

準備合併

在實際進行合併操作之前,需要進行一些準備步驟,以保證合併過程能夠順利進行。

確認接收合併的分支

執行git status命令檢視當前分支的狀態,確保HEAD指正指向的是正確的接收合併的分支。如果不是,執行git checkout命令切換到正確的分支。在我們的示例中,執行git checkout main

獲取最新的遠端提交

確保合併操作涉及的兩個分支都更新到遠端倉庫的最新狀態。執行git fetch拉取遠端倉庫的最新提交。一旦fetch操作完成,為了保證main分支與遠端分支同步,還需執行git pull命令。

合併

當上面提及的準備工作都已完備,合併就可以正式開始了。執行git merge <branch>命令,其中<branch>為需要合併到當前分支的目標分支名稱。

快進合併

當前工作分支到合併目標分支之間的提交歷史是線性路徑時,可以進行快進合併。在這種情況下,不需要真實的合併兩個分支,Git只需要把當前分支的頂端指標移動到目標分支的頂端就可以了(也就是快進的意思)。在這種情況下快進合併成功的將提交歷史合併至一處,畢竟目標分支中的提交此時都包含在當前分支的提交歷史中了。對於將功能分支快進合併到main分支的流程可以參見下圖所示:


然而快進合併在兩個分支出現分叉的情況下是不允許執行的。當目標分支相對於當前分支的提交歷史不是線性的,Git只能通過三路合併演算法來決定如何對兩個分支進行合併。三路合併演算法需要使用一個專用commit來整合兩邊的提交歷史。這個名詞源於Git要想生成合並commit,需要用到三個commits:兩個分支的頂端commit,以及它們的共同祖先commit。


雖然實際上可以選擇使用這些不同的合併策略,但是大多數開發者更喜歡快進合併(通過利用 rebasing 命令),尤其是用於小功能的開發或者bug修復;反之對於合併長期開發的功能分支,則更傾向於使用三路合併的方式。在第二種場景中,merge產生的合併commit會作為兩個分支合併的標誌保留在提交歷史中。

接下來我們用下面第一個例子來展示如何進行快進合併。下面的命令會先建立一個新分支,在新分支上進行兩次提交,然後用快進合併把新分支合併回main分支。

# Start a new feature
git checkout -b new-feature main
# Edit some files
git add <file>
git commit -m "Start a feature"
# Edit some files
git add <file>
git commit -m "Finish a feature"
# Merge in the new-feature branch
git checkout main
git merge new-feature
git branch -d new-feature

這個例子中的工作流程通常用於短期功能的開發,這種開發流程更多地被當做是比較獨立的一次開發流程,與之對應的則是需要協調和管理的長期功能開發分支。

另外還需注意到,在此例中Git不會對git branch -d命令發出警告,因為new-feature的內容已經合併到主分支裡了。

在某些情況下,雖然目標分支的提交歷史相對於當前分支是線性的,可以進行快進合併,但你仍然希望有一個合併commit來標誌合併在此commit發生過,那麼可以在執行git merge命令時使用--no-ff選項。

git merge --no-ff <branch>

以上命令將指定分支合併到當前分支,但總會生成一個合併commit(即便這一合併操作可以快進)。當你需要在倉庫的提交歷史中標記合併事件時這一命令相當有用。

三路合併

接下來的例子與上面比較像,但是因為main分支在feature分支向前發展的過程中,自身也發生的改變,因此在合併時需要進行三路合併。在進行大的功能開發或者有多個開發者同時進行開發時這種場景相當常見。

Start a new feature
git checkout -b new-feature main
# Edit some files
git add <file>
git commit -m "Start a feature"
# Edit some files
git add <file>
git commit -m "Finish a feature"
# Develop the main branch
git checkout main
# Edit some files
git add <file>
git commit -m "Make some super-stable changes to main"
# Merge in the new-feature branch
git merge new-feature
git branch -d new-feature

需注意在這種情況下,由於沒有辦法直接把main的頂端指標移動到new-feature分支上,因此Git無法執行快進合併。

在大多數實際工作場景中,new-feature應該是一個很大的功能,開發過程持續了相當長的時間,這也就難免同時期在main分支上也有新的提交。如果你的功能分支大小像上面的例子一樣小,則完全可以使用rebase將new-feature分支變基到main分支上,然後再執行一次快進合併。這樣也會避免在專案提交歷史中產生過多的冗餘。

解決衝突

如果將要合併的兩個分支都修改了同一個而檔案的同一個部分內容,Git就無法確定應該使用哪個版本的內容。當這種情況發生時,合併過程會停止在合併commit提交之前,以便給使用者留下機會手動修復這些衝突。

在Git的合併過程中,很棒的一點是它使用人們熟知的 編輯 / 暫存 / 提交 這樣的工作流程來解決衝突。當碰到合併衝突時,執行git status命令會列出哪些檔案含有衝突並需要手動解決。比如說當兩個分支都修改了hello.py檔案的同一部分,你會看到類似下面這樣的資訊:

On branch main
Unmerged paths:
(use "git add/rm ..." as appropriate to mark resolution)
both modified: hello.py

衝突是如何顯示的

當Git在合併過程中碰到了衝突,它會編輯受影響的檔案中的相關內容,並新增視覺標記用以展示衝突中雙方在此部分的不同內容。這些視覺標記為:<<<<<<<,=======,>>>>>>>。要找到衝突發生的具體位置,在檔案中搜尋這些視覺標記會非常便捷地達成目的。

here is some content not affected by the conflict
<<<<<<< main
this is conflicted text from main
=======
this is conflicted text from feature branch
>>>>>>> feature branch;

通常來說在 ====== 標記之前的內容來自於接收合併的分支,而在這之後的內容來自於要合併的分支。

一旦找到衝突的部分,就可以根據需要來修正衝突。當你完成了衝突的修復並準備好繼續進行合併,只需要執行git add命令把已經解決好衝突的檔案新增暫存區,告訴Git這些衝突已經解決完畢即可。這之後就像正常提交程式碼一樣執行git commit完成合並commit。這個過程跟正常情況下提交程式碼是完全一樣的,也就是說對於普通開發者來說處理衝突也是小菜一碟。

還需注意合併衝突只可能出現在三路合併過程中,在快進合併中不會出現衝突。

總結

本文是關於git merge命令的概覽。在使用Git的過程中,合併是非常重要的操作。本文討論了合併操作背後的機制,以及快進合併與三路合併的區別。需要讀者記住的要點如下:

  1. Git 合併流程是把不同的提交序列合併到一個統一的提交歷史中
  2. Git合併過程中有兩個主要的方式:快進合併 和 三路合併
  3. 除非兩個提交序列中出現衝突,Git通常可以自動對提交進行合併

相關文章