章節 ▾ 第二版

3.2 Git 分支 - 基本分支與合併

基本分支與合併

讓我們透過一個你在實際工作中可能用到的工作流程,來簡單地走一遍分支和合並的例子。你將遵循以下步驟:

  1. 對網站進行一些工作。

  2. 為正在進行的新使用者故事建立一個分支。

  3. 在該分支中進行一些工作。

在這個階段,你會接到一個電話,說另一個問題很緊急,你需要緊急修復。你將執行以下操作:

  1. 切換到你的生產分支。

  2. 建立一個分支來新增緊急修復。

  3. 測試完成後,合併緊急修復分支,並推送到生產環境。

  4. 切換回你原來的使用者故事,繼續工作。

基本分支

首先,假設你正在進行你的專案,並且在 master 分支上已經有了幾個提交。

A simple commit history
圖 18. 一個簡單的提交歷史

你決定要處理問題 #53,無論你的公司使用什麼問題跟蹤系統。要同時建立一個新分支並切換到它,你可以使用 git checkout 命令和 -b 選項:

$ git checkout -b iss53
Switched to a new branch "iss53"

這是以下命令的簡寫:

$ git branch iss53
$ git checkout iss53
Creating a new branch pointer
圖 19. 建立一個新的分支指標

你開始處理你的網站,並進行了一些提交。這樣做會使 iss53 分支向前移動,因為你已經檢出了它(也就是說,你的 HEAD 指向它)。

$ vim index.html
$ git commit -a -m 'Create new footer [issue 53]'
The `iss53` branch has moved forward with your work
圖 20. iss53 分支隨著你的工作向前移動

現在你接到電話,說網站出現了一個緊急問題,你需要立即修復。使用 Git,你不需要將你的修復與你已經完成的 iss53 更改一起部署,也不需要花費大量精力在撤銷這些更改後再將修復應用到生產環境。你所要做的就是切換回你的 master 分支。

但是,在這樣做之前,請注意,如果你的工作目錄或暫存區中有未提交的更改,並且這些更改與你要切換到的分支衝突,Git 將不允許你切換分支。在切換分支時,最好保持乾淨的工作狀態。有一些方法可以繞過這個問題(即,stashingcommit amending),我們將在後面的 Stashing and Cleaning 部分介紹。現在,讓我們假設你已經提交了所有的更改,所以你可以切換回你的 master 分支。

$ git checkout master
Switched to branch 'master'

此時,你的專案工作目錄與你開始處理問題 #53 之前完全一樣,你可以專注於你的緊急修復。這一點很重要:當你切換分支時,Git 會重置你的工作目錄,使其看起來像上一次在該分支提交時那樣。它會自動新增、刪除和修改檔案,以確保你的工作副本是你上次提交到該分支時的樣子。

接下來,你有一個緊急修復。讓我們建立一個 hotfix 分支,直到修復完成。

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'Fix broken email address'
[hotfix 1fb7853] Fix broken email address
 1 file changed, 2 insertions(+)
Hotfix branch based on `master`
圖 21. 基於 master 的緊急修復分支

你可以執行你的測試,確保緊急修復符合你的要求,最後透過 git merge 命令將 hotfix 分支合併回你的 master 分支以部署到生產環境。

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

你會在合併中注意到“快進”(fast-forward)這個短語。因為 hotfix 分支指向的提交 C4 直接位於你所在的提交 C2 的前面,Git 只是簡單地將指標向前移動。換句話說,當你試圖將一個提交與透過跟隨第一個提交的歷史可以到達的提交合並時,Git 會透過移動指標來簡化事情,因為沒有分叉的工作需要合併——這被稱為“快進”。

你的更改現在位於 master 分支指向的提交快照中,你可以部署修復了。

`master` is fast-forwarded to `hotfix`
圖 22. master 快進到 hotfix

在你部署了超級重要的修復後,你就可以切換回你被打斷之前正在進行的工作了。但是,首先,你需要刪除 hotfix 分支,因為你不再需要它了——master 分支指向同一個位置。你可以使用 git branch-d 選項來刪除它。

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

現在你可以切換回你正在進行的問題 #53 的分支,繼續在那裡工作。

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'Finish the new footer [issue 53]'
[iss53 ad82d7a] Finish the new footer [issue 53]
1 file changed, 1 insertion(+)
Work continues on `iss53`
圖 23. iss53 分支的工作繼續

值得注意的是,你在 hotfix 分支中所做的工作並未包含在你 iss53 分支的檔案中。如果你需要引入這些更改,你可以透過執行 git merge master 將你的 master 分支合併到你的 iss53 分支,或者你可以等到稍後決定將 iss53 分支合併回 master 時再整合這些更改。

基本合併

假設你決定你的問題 #53 工作已完成,並準備好合併到你的 master 分支。要做到這一點,你將合併你的 iss53 分支到 master,這和你之前合併 hotfix 分支非常相似。你只需要檢出你想要合併到的分支,然後執行 git merge 命令。

$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

這看起來和你之前進行的 hotfix 合併有些不同。在這種情況下,你的開發歷史已經從某個較早的節點分叉了。因為你所在分支的提交併不是你要合併的分支的直接祖先,Git 需要做一些工作。在這種情況下,Git 會進行一個簡單的三方合併,使用分支尖端指向的兩個快照以及它們的共同祖先。

Three snapshots used in a typical merge
圖 24. 用於典型合併的三個快照

Git 沒有僅僅向前移動分支指標,而是建立了一個由這個三方合併產生的新快照,並自動建立一個指向它的新提交。這被稱為合併提交,其特殊之處在於它有多個父提交。

A merge commit
圖 25. 合併提交

現在你的工作已經合併,你不再需要 iss53 分支了。你可以在你的問題跟蹤系統中關閉該問題,並刪除該分支。

$ git branch -d iss53

基本合併衝突

偶爾,這個過程不會一帆風順。如果你在要合併的兩個分支中以不同的方式修改了同一個檔案的同一部分,Git 將無法乾淨地合併它們。如果你的問題 #53 的修復與 hotfix 分支修改了同一個檔案的同一部分,你將遇到一個合併衝突,看起來像這樣:

$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

Git 沒有自動建立一個新的合併提交。它暫停了過程,讓你來解決衝突。如果你想在合併衝突後隨時檢視哪些檔案未合併,你可以執行 git status

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

任何有合併衝突且尚未解決的檔案都會被列為未合併。Git 會向有衝突的檔案新增標準的衝突解決標記,這樣你就可以手動開啟它們並解決這些衝突。你的檔案將包含一個看起來像這樣的部分:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

這意味著 HEAD 中的版本(你的 master 分支,因為你在執行合併命令時檢出的是它)是該塊的上半部分(======= 以上的所有內容),而你的 iss53 分支中的版本是底部的內容。為了解決衝突,你必須選擇其中一個版本,或者自己合併內容。例如,你可能會透過用以下內容替換整個塊來解決此衝突:

<div id="footer">
please contact us at email.support@github.com
</div>

這個解決方案包含了兩個部分的一些內容,並且 <<<<<<<=======>>>>>>> 行已被完全刪除。在解決了每個衝突檔案中的每個部分後,執行 git add 標記該檔案為已解決。將檔案暫存就表示 Git 已將其標記為已解決。

如果你想使用圖形化工具來解決這些問題,你可以執行 git mergetool,它會啟動一個合適的視覺合併工具,並引導你解決衝突。

$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

如果你想使用 Git 選擇的預設工具以外的合併工具(在這種情況下,因為命令是在 macOS 上執行的,Git 選擇 opendiff),你可以在“以下工具之一”的頂部找到所有支援的工具列表。只需輸入你更喜歡的工具的名稱即可。

注意

如果你需要更高階的工具來解決棘手的合併衝突,我們將在 Advanced Merging 部分更詳細地介紹合併。

退出合併工具後,Git 會詢問你合併是否成功。如果你告訴指令碼它成功了,它會暫存檔案,為你標記為已解決。你可以再次執行 git status 來驗證所有衝突都已解決。

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

如果你對此感到滿意,並且確認所有有衝突的檔案都已暫存,你可以輸入 git commit 來完成合並提交。預設情況下,提交訊息看起來像這樣:

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#	.git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#	modified:   index.html
#

如果你認為這能幫助未來檢視此合併的人,你可以修改此提交訊息,新增有關你如何解決合併以及你為什麼進行這些更改的詳細資訊(如果這些更改不明顯)。