如今讓咱們來看一個簡單的分支與合併的例子,實際工做中大致也會用到這樣的工做流程:html
假設此時,你忽然接到一個電話說有個很嚴重的問題須要緊急修補,那麼能夠按照下面的方式處理:git
首先,咱們假設你正在項目中愉快地工做,而且已經提交了幾回更新(見圖 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. #
若是想給未來看此次合併的人一些方便,能夠修改該信息,提供更多合併細節。好比你都做了哪些改動,以及這麼作的緣由。有時候裁決衝突的理由並不直接或明顯,有必要略加註解。