Git 分支 - 分支的新建與合併

分支的新建與合併

如今讓咱們來看一個簡單的分支與合併的例子,實際工做中大致也會用到這樣的工做流程:html

  1. 開發某個網站。
  2. 爲實現某個新的需求,建立一個分支。
  3. 在這個分支上開展工做。

假設此時,你忽然接到一個電話說有個很嚴重的問題須要緊急修補,那麼能夠按照下面的方式處理:git

  1. 返回到原先已經發布到生產服務器上的分支。
  2. 爲此次緊急修補創建一個新分支,並在其中修復問題。
  3. 經過測試後,回到生產服務器所在的分支,將修補分支合併進來,而後再推送到生產服務器上。
  4. 切換到以前實現新需求的分支,繼續工做。

分支的新建與切換

首先,咱們假設你正在項目中愉快地工做,而且已經提交了幾回更新(見圖 3-10)。github

 


圖 3-10. 一個簡短的提交歷史vim

 

如今,你決定要修補問題追蹤系統上的 #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):this

$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'

 


圖 3-12. iss53 分支隨工做進展向前推動spa

 

如今你就接到了那個網站問題的緊急電話,須要立刻修補。有了 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

分支的合併

在問題 #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

遇到衝突時的分支合併

有時候合併操做並不會如此順利。若是在不一樣的分支中都修改了同一個文件的同一部分,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")

任何包含未解決衝突的文件都會以未合併(unmerged)的狀態列出。Git 會在有衝突的文件里加入標準的衝突解決標記,能夠經過它們來手工定位並解決這些衝突。能夠看到此文件包含相似下面這樣的部分:

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

能夠看到 ======= 隔開的上半部分,是 HEAD(即 master 分支,在運行 merge 命令時所切換到的分支)中的內容,下半部分是在 iss53 分支中的內容。解決衝突的辦法無非是兩者選其一或者由你親自整合到一塊兒。好比你能夠經過把這段內容替換爲下面這樣來解決:

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

這個解決方案各採納了兩個分支中的一部份內容,並且我還刪除了 <<<<<<<======= 和 >>>>>>> 這些行。在解決了全部文件裏的全部衝突後,運行 git add 將把它們標記爲已解決狀態(譯註:實際上就是來一次快照保存到暫存區域。)。由於一旦暫存,就表示衝突已經解決。若是你想用一個有圖形界面的工具來解決這些問題,不妨運行 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 上運行了該命令),你能夠在上方"merge tool candidates"裏找到可用的合併工具列表,輸入你想用的工具名。咱們將在第七章討論怎樣改變環境中的默認值。

退出合併工具之後,Git 會詢問你合併是否成功。若是回答是,它會爲你把相關文件暫存起來,以代表狀態爲已解決。

再運行一次 git status 來確認全部衝突都已解決:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        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.
#

若是想給未來看此次合併的人一些方便,能夠修改該信息,提供更多合併細節。好比你都做了哪些改動,以及這麼作的緣由。有時候裁決衝突的理由並不直接或明顯,有必要略加註解。

相關文章
相關標籤/搜索