淺析Git 分支的新建與合併
分支的新建與合併
現在讓我們來看一個簡單的分支與合併的例子,實際工作中大體也會用到這樣的工作流程:
- 開發某個網站。
- 為實現某個新的需求,建立一個分支。
- 在這個分支上開展工作。
假設此時,你突然接到一個電話說有個很嚴重的問題需要緊急修補,那麼可以按照下面的方式處理:
- 返回到原先已經發布到生產伺服器上的分支。
- 為這次緊急修補建立一個新分支,並在其中修復問題。
- 通過測試後,回到生產伺服器所在的分支,將修補分支合併進來,然後再推送到生產伺服器上。
- 切換到之前實現新需求的分支,繼續工作。
分支的新建與切換
首先,我們假設你正在專案中愉快地工作,並且已經提交了幾次更新(見圖 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
。
刪除遠端分支:
$ git push origin --delete <BranchName>
檢視遠端分支:
$ git branch -a
分支的合併
在問題 #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 protected]</div> ======= <div id="footer"> please contact us at [email protected] </div> >>>>>>> iss53
可以看到=======
隔開的上半部分,是HEAD
(即master
分支,在執行merge
命令時所切換到的分支)中的內容,下半部分是在iss53
分支中的內容。解決衝突的辦法無非是二者選其一或者由你親自整合到一起。比如你可以通過把這段內容替換為下面這樣來解決:
<div id="footer"> please contact us at [email protected] </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. #
如果想給將來看這次合併的人一些方便,可以修改該資訊,提供更多合併細節。比如你都作了哪些改動,以及這麼做的原因。有時候裁決衝突的理由並不直接或明顯,有必要略加註解。
總結
到此這篇關於Git 分支的新建與合併的文章就介紹到這了,更多相關Git 分支內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!