# 分支的新建與合并
讓我們來看一個簡單的分支新建與分支合并的例子,實際工作中你可能會用到類似的工作流。你將經歷如下步驟:
1. 開發某個網站。
1. 為實現某個新的需求,創建一個分支。
1. 在這個分支上開展工作。
正在此時,你突然接到一個電話說有個很嚴重的問題需要緊急修補。你將按照如下方式來處理:
1. 切換到你的線上分支(production branch)。
1. 為這個緊急任務新建一個分支,并在其中修復它。
1. 在測試通過之后,切換回線上分支,然后合并這個修補分支,最后將改動推送到線上分支。
1. 切換回你最初工作的分支上,繼續工作。
## 新建分支
首先,我們假設你正在你的項目上工作,并且已經有一些提交。

Figure 3-10. 一個簡單提交歷史
現在,你已經決定要解決你的公司使用的問題追蹤系統中的 #53 問題。想要新建一個分支并同時切換到那個分支上,你可以運行一個帶有 `-b` 參數的 `git checkout` 命令:
~~~
$ git checkout -b iss53
Switched to a new branch "iss53"
~~~
它是下面兩條命令的簡寫:
~~~
$ git branch iss53
$ git checkout iss53
~~~

Figure 3-11. 創建一個新分支指針
你繼續在 #53 問題上工作,并且做了一些提交。在此過程中,`iss53` 分支在不斷的向前推進,因為你已經檢出到該分支(也就是說,你的 `HEAD` 指針指向了 `iss53` 分支)
~~~
$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'
~~~

Figure 3-12. iss53 分支隨著工作的進展向前推進
現在你接到那個電話,有個緊急問題等待你來解決。有了 Git 的幫助,你不必把這個緊急問題和 `iss53` 的修改混在一起,你也不需要花大力氣來還原關于 53# 問題的修改,然后再添加關于這個緊急問題的修改,最后將這個修改提交到線上分支。你所要做的僅僅是切換回 `master` 分支。
但是,在你這么做之前,要留意你的工作目錄和暫存區里那些還沒有被提交的修改,它可能會和你即將檢出的分支產生沖突從而阻止 Git 切換到該分支。最好的方法是,在你切換分支之前,保持好一個干凈的狀態。有一些方法可以繞過這個問題(即,保存進度(stashing) 和 修補提交(commit amending)),我們會在 [“儲藏與清理”](#) 中看到關于這兩個命令的介紹。現在,我們假設你已經把你的修改全部提交了,這時你可以切換回 `master` 分支了:
~~~
$ git checkout master
Switched to branch 'master'
~~~
這個時候,你的工作目錄和你在開始 #53 問題之前一模一樣,現在你可以專心修復緊急問題了。請牢記:當你切換分支的時候,Git 會重置你的工作目錄,使其看起來像回到了你在那個分支上最后一次提交的樣子。Git 會自動添加、刪除、修改文件以確保此時你的工作目錄和這個分支最后一次提交時的樣子一模一樣。
接下來,你要修復這個緊急問題。讓我們建立一個針對該緊急問題的分支(hotfix branch),在該分支上工作直到問題解決:
~~~
$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix 1fb7853] fixed the broken email address
1 file changed, 2 insertions(+)
~~~

Figure 3-13. 基于 `master` 分支的緊急問題分支 `hotfix branch`
你可以運行你的測試,確保你的修改是正確的,然后將其合并回你的 `master` 分支來部署到線上。你可以使用 `git merge` 命令來達到上述目的:
~~~
$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
index.html | 2 ++
1 file changed, 2 insertions(+)
~~~
在合并的時候,你應該注意到了"快進(fast-forward)"這個詞。由于當前 `master` 分支所指向的提交是你當前提交(有關 hotfix 的提交)的直接上游,所以 Git 只是簡單的將指針向前移動。換句話說,當你試圖合并兩個分支時,如果順著一個分支走下去能夠到達另一個分支,那么 Git 在合并兩者的時候,只會簡單的將指針向前推進(指針右移),因為這種情況下的合并操作沒有需要解決的分歧——這就叫做 “快進(fast-forward)”。
現在,最新的修改已經在 `master` 分支所指向的提交快照中,你可以著手發布該修復了。

Figure 3-14. `master` 被快進到 `hotfix`
關于這個緊急問題的解決方案發布之后,你準備回到被打斷之前時的工作中。然而,你應該先刪除 `hotfix` 分支,因為你已經不再需要它了 —— `master` 分支已經指向了同一個位置。你可以使用帶 `-d` 選項的 `git branch` 命令來刪除分支:
~~~
$ git branch -d hotfix
Deleted branch hotfix (3a0874c).
~~~
現在你可以切換回你正在工作的分支繼續你的工作,也就是針對 #53 問題的那個分支(iss53 分支)。
~~~
$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)
~~~

Figure 3-15. 繼續在 `iss53` 分支上的工作
你在 `hotfix` 分支上所做的工作并沒有包含到 `iss53` 分支中。如果你需要拉取 `hotfix` 所做的修改,你可以使用 `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` 分支的時候看起來有一點不一樣。在這種情況下,你的開發歷史從一個更早的地方開始分叉開來(diverged)。因為,`master` 分支所在提交并不是 `iss53` 分支所在提交的直接祖先,Git 不得不做一些額外的工作。出現這種情況的時候,Git 會使用兩個分支的末端所指的快照(`C4` 和 `C5`)以及這兩個分支的工作祖先(`C2`),做一個簡單的三方合并。

Figure 3-16. 一次典型合并中所用到的三個快照
和之間將分支指針向前推進所不同的是,Git 將此次三方合并的結果做了一個新的快照并且自動創建一個新的提交指向它。這個被稱作一次合并提交,它的特別之處在于他有不止一個父提交。

Figure 3-17. 一個合并提交
需要指出的是,Git 會自行決定選取哪一個提交作為最優的共同祖先,并以此作為合并的基礎;這和更加古老的 CVS 系統或者 Subversion (1.5 版本之前)不同,在這些古老的版本管理系統中,用戶需要自己選擇最佳的合并基礎。Git 的這個優勢使其在合并操作上比其他系統要簡單很多。
既然你的修改已經合并進來了,你已經不再需要 `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 會暫停下來,等待你去解決合并產生的沖突。你可以在合并沖突后的任意時刻使用 `git status` 命令來查看那些因包含合并沖突而處于未合并(unmerged)狀態的文件:
~~~
$ 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` 分支所在的位置,因為你在運行 merge 命令的時候已經檢出到了這個分支)在這個區段的上半部分(`=======` 的上半部分),而 `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 使用 `opendiff` 做為默認的合并工具,因為作者在 Mac 上運行該程序)外的其他合并工具,你可以在 “下列工具中(one of the following tools)” 這句后面看到所有支持的合并工具。然后輸入你喜歡的工具名字就可以了。
如果你需要更加高級的工具來解決復雜的合并沖突,我們會在 [“高級合并”](#) 介紹更多關于分支合并的內容。
等你退出合并工具之后,Git 會詢問剛才的合并是否成功。如果你回答是,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
#
~~~
如果你覺得上述的信息不夠充分,不能完全體現分支合并的過程,你可以修改上述信息,添加一些細節給未來檢視這個合并的讀者一些幫助,告訴他們你是如何解決合并沖突的,以及理由是什么。
- 前言
- Scott Chacon 序
- Ben Straub 序
- 獻辭
- 貢獻者
- 引言
- 1. 起步
- 1.1 關于版本控制
- 1.2 Git 簡史
- 1.3 Git 基礎
- 1.4 命令行
- 1.5 安裝 Git
- 1.6 初次運行 Git 前的配置
- 1.7 獲取幫助
- 1.8 總結
- 2. Git 基礎
- 2.1 獲取 Git 倉庫
- 2.2 記錄每次更新到倉庫
- 2.3 查看提交歷史
- 2.4 撤消操作
- 2.5 遠程倉庫的使用
- 2.6 打標簽
- 2.7 Git 別名
- 2.8 總結
- 3. Git 分支
- 3.1 分支簡介
- 3.2 分支的新建與合并
- 3.3 分支管理
- 3.4 分支開發工作流
- 3.5 遠程分支
- 3.6 變基
- 3.7 總結
- 4. 服務器上的 Git
- 4.1 協議
- 4.2 在服務器上搭建 Git
- 4.3 生成 SSH 公鑰
- 4.4 配置服務器
- 4.5 Git 守護進程
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管的選擇
- 4.10 總結
- 5. 分布式 Git
- 5.1 分布式工作流程
- 5.2 向一個項目貢獻
- 5.3 維護項目
- 5.4 總結
- 6. GitHub
- 6.1 賬戶的創建和配置
- 6.2 對項目做出貢獻
- 6.3 維護項目
- 6.4 管理組織
- 6.5 腳本 GitHub
- 6.6 總結
- 7. Git 工具
- 7.1 選擇修訂版本
- 7.2 交互式暫存
- 7.3 儲藏與清理
- 7.4 簽署工作
- 7.5 搜索
- 7.6 重寫歷史
- 7.7 重置揭密
- 7.8 高級合并
- 7.9 Rerere
- 7.10 使用 Git 調試
- 7.11 子模塊
- 7.12 打包
- 7.13 替換
- 7.14 憑證存儲
- 7.15 總結
- 8. 自定義 Git
- 8.1 配置 Git
- 8.2 Git 屬性
- 8.3 Git 鉤子
- 8.4 使用強制策略的一個例子
- 8.5 總結
- 9. Git 與其他系統
- 9.1 作為客戶端的 Git
- 9.2 遷移到 Git
- 9.3 總結
- 10. Git 內部原理
- 10.1 底層命令和高層命令
- 10.2 Git 對象
- 10.3 Git 引用
- 10.4 包文件
- 10.5 引用規格
- 10.6 傳輸協議
- 10.7 維護與數據恢復
- 10.8 環境變量
- 10.9 總結
- A. 其它環境中的 Git
- A1.1 圖形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Eclipse 中的 Git
- A1.4 Bash 中的 Git
- A1.5 Zsh 中的 Git
- A1.6 Powershell 中的 Git
- A1.7 總結
- B. 將 Git 嵌入你的應用
- A2.1 命令行 Git 方式
- A2.2 Libgit2
- A2.3 JGit
- C. Git 命令
- A3.1 設置與配置
- A3.2 獲取與創建項目
- A3.3 快照基礎
- A3.4 分支與合并
- A3.5 項目分享與更新
- A3.6 檢查與比較
- A3.7 調試
- A3.8 補丁
- A3.9 郵件
- A3.10 外部系統
- A3.11 管理
- A3.12 底層命令