### 2 Git 分支 - 分支的新建與合并
#### 分支的新建與合并
現在讓我們來看一個簡單的分支與合并的例子,實際工作中大體也會用到這樣的工作流程:
1. 開發某個網站。
2. 為實現某個新的需求,創建一個分支。
3. 在這個分支上開展工作。
假設此時,你突然接到一個電話說有個很嚴重的問題需要緊急修補,那么可以按照下面的方式處理:
1. 返回到原先已經發布到生產服務器上的分支。
2. 為這次緊急修補建立一個新分支,并在其中修復問題。
3. 通過測試后,回到生產服務器所在的分支,將修補分支合并進來,然后再推送到生產服務器上。
4. 切換到之前實現新需求的分支,繼續工作。
[分支的新建與切換](https://git-scm.com/book/zh/v1/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E6%96%B0%E5%BB%BA%E4%B8%8E%E5%90%88%E5%B9%B6#分支的新建與切換)
首先,我們假設你正在項目中愉快地工作,并且已經提交了幾次更新(見圖 3-10)。

圖 3-10. 一個簡短的提交歷史
現在,你決定要修補問題追蹤系統上的 `#53` 問題。順帶說明下,Git 并不同任何特定的問題追蹤系統打交道。這里為了說明要解決的問題,才把新建的分支取名為 `iss53`。要新建并切換到該分支,運行 `git checkout` 并加上 `-b` 參數:
~~~
$ git checkout -b iss53
Switched to a new branch 'iss53'
~~~
這相當于執行下面這兩條命令:
~~~
$ git branch iss53
$ git checkout iss53
~~~
圖 3-11 示意該命令的執行結果。

圖 3-11. 創建了一個新分支的指針
接著你開始嘗試修復問題,在提交了若干次更新后,`iss53` 分支的指針也會隨著向前推進,因為它就是當前分支(換句話說,當前的 `HEAD` 指針正指向 `iss53`,見圖 `3-12`):
~~~
$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'
~~~

圖 3-12. iss53 分支隨工作進展向前推進
現在你就接到了那個網站問題的緊急電話,需要馬上修補。有了 Git ,我們就不需要同時發布這個補丁和 `iss53` 里作出的修改,也不需要在創建和發布該補丁到服務器之前花費大力氣來復原這些修改。唯一需要的僅僅是切換回 `master` 分支。
不過在此之前,留心你的暫存區或者工作目錄里,那些還沒有提交的修改,它會和你即將檢出的分支產生沖突從而阻止 Git 為你切換分支。切換分支的時候最好保持一個清潔的工作區域。稍后會介紹幾個繞過這種問題的辦法(分別叫做 `stashing `和 `commit amending`)。目前已經提交了所有的修改,所以接下來可以正常轉換到 `master` 分支:
~~~
$ git checkout master
Switched to branch 'master'
~~~
此時工作目錄中的內容和你在解決問題 `#53` 之前一模一樣,你可以集中精力進行緊急修補。這一點值得牢記:Git 會把工作目錄的內容恢復為檢出某分支時它所指向的那個提交對象的快照。它會自動添加、刪除和修改文件以確保目錄的內容和你當時提交時完全一樣。
接下來,你得進行緊急修補。我們創建一個緊急修補分支 `hotfix` 來開展工作,直到搞定(見圖 3-13):
~~~
$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix 3a0874c] fixed the broken email address
1 files changed, 1 deletion(-)
~~~

圖 3-13. `hotfix` 分支是從 `master` 分支所在點分化出來的
有必要作些測試,確保修補是成功的,然后回到 `master` 分支并把它合并進來,然后發布到生產服務器。用 `git merge` 命令來進行合并:
~~~
$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
README | 1 -
1 file changed, 1 deletion(-)
~~~
請注意,合并時出現了“`Fast forward`”的提示。由于當前 `master` 分支所在的提交對象是要并入的 `hotfix` 分支的直接上游,`Git` 只需把 `master` 分支指針直接右移。換句話說,如果順著一個分支走下去可以到達另一個分支的話,那么 Git 在合并兩者時,只會簡單地把指針右移,因為這種單線的歷史分支不存在任何需要解決的分歧,所以這種合并過程可以稱為快進(`Fast forward`)。
現在最新的修改已經在當前 `master` 分支所指向的提交對象中了,可以部署到生產服務器上去了(見圖 3-14)。

圖 3-14. 合并之后,master 分支和 hotfix 分支指向同一位置。
在那個超級重要的修補發布以后,你想要回到被打擾之前的工作。由于當前 `hotfix` 分支和 `master` 都指向相同的提交對象,所以 `hotfix` 已經完成了歷史使命,可以刪掉了。使用 `git branch` 的 `-d` 選項執行刪除操作:
~~~
$ git branch -d hotfix
Deleted branch hotfix (was 3a0874c).
~~~
現在回到之前未完成的 `#53` 問題修復分支上繼續工作(圖 3-15):
~~~
$ 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(+)
~~~

圖 3-15. iss53 分支可以不受影響繼續推進。
值得注意的是之前 `hotfix` 分支的修改內容尚未包含到 `iss53` 中來。如果需要納入此次修補,可以用 `git merge master` 把 `master` 分支合并到 `iss53`;或者等` iss53` 完成之后,再將 `iss53` 分支中的更新并入 `master`。
[分支的合并](https://git-scm.com/book/zh/v1/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E6%96%B0%E5%BB%BA%E4%B8%8E%E5%90%88%E5%B9%B6#分支的合并)
在問題 `#53` 相關的工作完成之后,可以合并回 `master` 分支。實際操作同前面合并 `hotfix` 分支差不多,只需回到 `master` 分支,運行 `git merge` 命令指定要合并進來的分支:
~~~
$ git checkout master
$ git merge iss53
Auto-merging README
Merge made by the 'recursive' strategy.
README | 1 +
1 file changed, 1 insertion(+)
~~~
請注意,這次合并操作的底層實現,并不同于之前 hotfix 的并入方式。因為這次你的開發歷史是從更早的地方開始分叉的。由于當前 master 分支所指向的提交對象(C4)并不是 iss53 分支的直接祖先,Git 不得不進行一些額外處理。
就此例而言,Git 會用兩個分支的末端(C4 和 C5)以及它們的共同祖先(C2)進行一次簡單的三方合并計算。圖 3-16 用紅框標出了 Git 用于合并的三個提交對象:

圖 3-16. Git 為分支合并自動識別出最佳的同源合并點。
這次,Git 沒有簡單地把分支指針右移,而是對三方合并后的結果重新做一個新的快照,并自動創建一個指向它的提交對象`(C6)`(見圖 3-17)。這個提交對象比較特殊,它有兩個祖先(`C4` 和 `C5`)。
值得一提的是 `Git` 可以自己裁決哪個共同祖先才是最佳合并基礎;這和 `CVS` 或 `Subversion`(1.5 以后的版本)不同,它們需要開發者手工指定合并基礎。所以此特性讓 `Git` 的合并操作比其他系統都要簡單不少。

圖 3-17. Git 自動創建了一個包含了合并結果的提交對象。
既然之前的工作成果已經合并到 master 了,那么 iss53 也就沒用了。你可以就此刪除它,并在問題追蹤系統里關閉該問題。
~~~
$ git branch -d iss53
~~~
[遇到沖突時的分支合并](https://git-scm.com/book/zh/v1/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E6%96%B0%E5%BB%BA%E4%B8%8E%E5%90%88%E5%B9%B6#遇到沖突時的分支合并)
有時候合并操作并不會如此順利。如果在不同的分支中都修改了同一個文件的同一部分,Git 就無法干凈地把兩者合到一起(譯注:邏輯上說,這種問題只能由人來裁決。)。
- 目錄
- 撤銷
- Git reset
- Git revert
- Git reset 命令
- Git的撤消操作 - 重置, 簽出和撤消
- 沖突
- 解決Git合并分支發生的沖突
- BASH
- 復制黏貼
- 教程
- 合并
- 合并提交
- 分支
- Gitee(碼云)
- Github
- 免費私有庫
- 本地倉庫推送到github倉庫
- 工具
- SourceTree
- 添加git-bash.exe到環境變量
- Git
- 安裝
- 配置
- 命令行
- 10個技巧讓開發者的Git水平迅速提升
- 3.2 Git 分支 - 分支的新建與合并
- 遠程分支
- Git幫助
- Git幫助
- 顯示命令幫助
- Git 命令
- 參數配置
- 還原至上次提交
- Git 操作命令
- Git tag
- Git 分支管理
- 認識分支
- 賣主分支
- 分支操作
- 本地倉庫遠程提交
- Git 分區
- 認識里程碑
- 忽略文件
- 已有項目
- 《Git權威指南》