Git之(四)分支管理

當咱們初始化Git倉庫的時候,Git會默認建立一個名爲master的主分支。在實際工做中,主分支要求是一個穩定、健壯、安全的主線,通常不容許在主分支上直接進行開發,而是拉取一個新的分支,開發、測試完成後,再將分支合併到主分支上。git

使用分支意味着你能夠從開發主線上分離開來,而後在不影響主線的同時繼續工做。在不少版本控制系統中,這是個昂貴的過程,經常須要建立一個源代碼目錄的完整副本,對大型項目來講會花費很長時間。安全

 Git 的分支模型可稱爲「必殺技特性」,而正是由於該特性將 Git 從版本控制系統家族裏區分出來,鶴立雞羣。其餘版本控制系統如SVN等都有分支管理,可是用過以後你會發現,這些版本控制系統建立和切換分支比蝸牛還慢,簡直讓人沒法忍受,結果分支功能成了擺設,你們都不去用。但Git的分支是不同凡響的,不管建立、切換和刪除分支,Git能在瞬間完成!不管你的版本庫是1個文件仍是1萬個文件。app

Git 鼓勵在工做流程中頻繁使用分支與合併,哪怕一天以內進行許屢次都沒有關係。在實際工做中,每每修復一個bug都會使用一個分支來完成。工具

理解分支的概念並熟練運用後,你纔會意識到爲何 Git 是一個如此強大而獨特的工具,並今後真正改變你的開發方式。測試


分支實現原理

在第一篇文章中提到過,Git 保存的不是文件差別或者變化量,而只是一系列文件快照。this

在 Git 中提交時,會保存一個提交(commit)對象,該對象包含一個指向暫存內容快照的指針,包含本次提交的做者等相關附屬信息,包含零個或多個指向該提交對象的父對象指針:首次提交是沒有直接祖先的,普通提交有一個祖先,由兩個或多個分支合併產生的提交則有多個祖先。spa

爲直觀起見,咱們假設在工做目錄中有三個文件,準備將它們暫存後提交。暫存操做會對每個文件計算校驗和(即SHA-1 哈希字串),而後把當前版本的文件快照保存到 Git 倉庫中(Git 使用 blob 類型的對象存儲這些快照),並將校驗和加入暫存區域。版本控制

當使用 Git commit新建一個提交對象前,Git 會先計算每個子目錄(本例中就是項目根目錄)的校驗和,而後在 Git 倉庫中將這些目錄保存爲樹(tree)對象。以後 Git 建立的提交對象,除了包含相關提交信息之外,還包含着指向這個樹對象(項目根目錄)的指針,如此它就能夠在未來須要的時候,重現這次快照的內容了。指針

如今,Git 倉庫中有五個對象:三個表示文件快照內容的 blob 對象;一個記錄着目錄樹內容及其中各個文件對應 blob 對象索引的 tree 對象;以及一個包含指向 tree 對象(根目錄)的索引和其餘提交信息元數據的 commit 對象:code


做些修改後再次提交,那麼此次的提交對象會包含一個指向上次提交對象的指針(即下圖中的 parent 對象)。兩次提交後,倉庫歷史會這個樣子:


Git 中的分支,其實本質上僅僅是個指向 commit 對象的可變指針。Git 會使用 master 做爲分支的默認名字。在若干次提交後,其實已經有了一個指向最後一次提交對象的 master 分支,它在每次提交的時候都會自動向前移動。



建立分支

建立一個新的名爲「testing」分支,可使用「git branch<branchName>」命令:

git branch testing

該命令會在當前 commit 對象上新建一個指針:


那麼,Git 是如何知道你當前在哪一個分支上工做的呢?其實答案也很簡單,它保存着一個名爲HEAD 的特別指針。在 Git 中,它是一個指向正在工做中的本地分支的指針(能夠將 HEAD 想象爲當前分支的別名)。運行Git branch 命令,僅僅是創建了一個新的分支,但不會自動切換到這個分支中去,因此在這個例子中,咱們依然還在 master 分支裏工做。


使用不帶任何參數的「git branch」命令能夠查看當前的分支狀況:

* master
  testing

Git顯示,共有兩個分支,當前工做分支爲master,分支列表中的星號「*」至關於HEAD指針,標註了當前工做分支。


切換分支

命令「git checkout <branchName>」能夠將當前工做分支切換到名爲branchName的分支。好比,運行命令:

git checkout testing

Git會提示:

Switched to branch 'testing'

這樣 HEAD 就指向了 testing 分支:


如今咱們若是修改了工做區的文件,全部commit操做都是提交到testing分支,而非master。


如今 testing 分支向前移動了一步,而 master 分支仍然指向原先 git checkout 時所在的 commit 對象。如今從新切換到master分支:

git checkout master


這條命令作了兩件事。它把 HEAD 指針移回到 master 分支,並把工做目區的文件換成了 master 分支所指向的快照內容。也就是說,如今開始所作的改動,將始於本項目中一個較老的版本。它的主要做用是將 testing 分支裏做出的修改暫時取消,這樣你就能夠向另外一個方向進行開發。

在mast分支上再作些修改,而後提交。如今咱們的項目提交歷史產生了分叉,由於剛纔咱們建立了一個分支testing,轉換到其中進行了一些工做,而後又回到原來的master主分支進行了另一些工做。


這些改變分別孤立在不一樣的分支裏。咱們能夠在不一樣分支裏反覆切換,並在時機成熟時把它們合併到一塊兒。

因爲 Git 中的分支實際上僅是一個包含所指對象校驗和(40 個字符長度 SHA-1 字串)的文件,因此建立和銷燬一個分支就變得很是廉價。說白了,新建一個分支就是向一個文件寫入 41 個字節(外加一個換行符)那麼簡單,固然也就很快。


合併分支

模擬這樣的一個場景,早上到了公司接到新任務,新建一個名爲「iss53」的分支來進行開發工做。要新建並切換到該分支,運行git checkout 並加上 -b 參數:

git checkout -b iss53

這至關於執行下面這兩條命令:

git branch iss53
git checkout iss53


而後不斷地寫代碼,提交代碼:


忽然,接到通知,須要當即修復master分支上的一個嚴重bug。

第一步確定須要切換到master。若是當前工做區與暫存區都是乾淨的,OK,直接切換回master便可。可是若是iss53分支上的開發尚未完成,而且不便於commit到版本庫,怎麼辦?一旦切回到其餘分支,工做區與暫存區就會被清空、覆蓋。實際上,若是工做區或暫存區不是乾淨的,存在沒有提交到版本庫的更改,Git是不容許切換分支的,會提示:

error: Your local changes to the following files wouldbe overwritten by checkout:
      readme.txt
Please, commit your changes or stash them before you can switch branches.

解決這個問題的辦法就是git stash命令。

該命令能夠獲取工做目錄的中間狀態——也就是修改過的被追蹤的文件和暫存的變動——並將它保存到一個未完結變動的堆棧中,隨時能夠從新應用。

運行「git stash」命令以後,iss53分支上未commit得變動就會被「儲藏」起來,能夠順利地切換到master分支了。要查看現有的儲藏,你可使用 git stash list,會的到這樣的一個列表:

stash@{0}: WIP on testing: 049d078 …
stash@{1}: WIP on testing: c264051 …
stash@{2}: WIP on testing: 21d80a5 …

列出的是該分支上全部被stash過的編號,使用命令「git stash apply」便可恢復到最新stash過的場景。若是想應用更早的儲藏,能夠經過名字指定它,像這樣:git stash apply stash@{2}。若是不指明編號,Git 默認使用最近的儲藏並嘗試應用它。

題歸正轉,咱們切換到master分支,拉去一個名爲「hotfix」的分支來緊急修復bug。

git checkout -b 'hotfix'

修復好以後,commit到版本庫,則如今Git的分支結構以下圖所示:


經測試以後,該bug成功修復,而後須要將該分支合併到master,首先依然要切換到master,而後使用命令「git merge」合併分支:

git checkout master
git merge hotfix

Git提示:

Updating 771f6de..adea62a
Fast-forward
 …

請注意,合併時出現了「Fast forward」的提示。因爲當前 master 分支所在的提交對象是要併入的 hotfix 分支的直接上游,Git 只需把master 分支指針直接右移。換句話說,若是順着一個分支走下去能夠到達另外一個分支的話,那麼 Git在合併二者時,只會簡單地把指針右移,由於這種單線的歷史分支不存在任何須要解決的分歧,因此這種合併過程能夠稱爲快進(Fast forward)

如今最新的修改已經在當前master 分支所指向的提交對象中了:


在那個超級重要的修補發佈之後,就能夠回繼續以前未完成的工做。因爲當前 hotfix 分支和 master 都指向相同的提交對象,因此hotfix 已經完成了歷史使命,能夠刪掉了。使用 git branch 的 -d 選項執行刪除操做:

git branch -d hotfix

不用擔憂以前 hotfix 分支的修改內容還沒有包含到 iss53 中來。若是確實須要歸入這次修補,能夠用git merge master 把 master 分支合併到 iss53;或者等 iss53 完成以後,再將iss53 分支中的更新併入 master。

如今回到以前未完成的 iss53分支上繼續工做,完成後commit到版本庫。


在問iss53 分支上的工做完成以後,能夠合併回 master 分支。實際操做同前面合併 hotfix 分支差很少,只需回到master分支,運行 git merge 命令指定要合併進來的分支。

請注意,此次合併操做的底層實現,並不一樣於以前 hotfix 的併入方式。由於此次開發歷史是從更早的地方開始分叉的。因爲當前master 分支所指向的提交對象(C4)並非 iss53 分支的直接祖先,Git 不得不進行一些額外處理。就此例而言,Git 會用兩個分支的末端(C4 和 C5)以及它們的共同祖先(C2)進行一次簡單的三方合併計算。下圖用紅框標出了Git 用於合併的三個提交對象:


此次,Git 沒有簡單地把分支指針右移,而是對三方合併後的結果從新作一個新的快照,並自動建立一個指向它的提交對象(C6)。這個提交對象比較特殊,它有兩個祖先(C4 和 C5)。

值得一提的是 Git 能夠本身裁決哪一個共同祖先纔是最佳合併基礎,不須要開發者手工指定合併基礎。此特性讓Git 的合併操做比其餘系統都要簡單很多。


解決衝突

有時候合併操做並不會如此順利。若是在不一樣的分支中都修改了同一個文件的同一部分,Git 就沒法乾淨地把二者合到一塊兒,邏輯上說,這種問題只能由人來裁決。這時候若是合併分支就會出現下面的結果:

Auto-merging readme.txt
CONFLICT (content): Merge conflictin readme.txt
Automatic merge failed; fix conflicts and then committhe result.

Git 做了合併,但沒有提交,它會停下來等你解決衝突。要看看哪些文件在合併時發生衝突,能夠用git status 查閱:

On branch master
You have unmerged paths.
  (fix conflictsand run "git commit")
 
Unmerged paths:
  (use "gitadd <file>..." to mark resolution)
      bothmodified:   readme.txt
no changes added to commit (use "git add"and/or "git commit -a")

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

this is my first git project
<<<<<<<HEAD
add row on master branch
add annother row on master branch
=======
add row on testing branch
add another row on testing branch
>>>>>>>testing

能夠看到 ======= 隔開的上半部分,是 HEAD(即 master 分支,在運行merge 命令時所切換到的分支)中的內容,下半部分是在 iss53 分支中的內容。解決衝突的辦法無非是兩者選其一或者由你親自整合到一塊兒。固然,Git插入的額外標記行也須要刪除。

在解決了全部文件裏的全部衝突後,運行 git add將把它們標記爲已解決狀態(實際上就是來一次快照保存到暫存區域)。由於一旦暫存,就表示衝突已經解決。

刪除分支

分支合併到master以後,若是無特殊用途,應該及時刪除分支。

要從該清單中篩選出已經(或還沒有)與當前分支合併的分支,能夠用 「--merged」和「--no-merged」 選項。

好比用「git branch --merged」 查看哪些分支已被併入當前分支,也就是說哪些分支是當前分支的直接上游:

* master
  testing

證實testing分支已經合併到master分支當中了,能夠刪除:

git branch –d testing


使用「git branch --no-merge」查看尚未合併的分支: 

 newTesting

若是使用命令「git branch–d newTesting」刪除該分支,Git會提示:

error: The branch 'newTesting' is not fully merged.
If you are sure you want to delete it, run 'git branch -D newTesting'.

因爲這些分支中還包含着還沒有合併進來的工做成果,因此簡單地用 Git branch -d 刪除該分支會提示錯誤,由於那樣作會丟失數據。

不過,若是你確實想要刪除該分支上的改動,能夠用大寫的刪除選項 -D 強制執行,就像上面提示信息中給出的那樣。

相關文章
相關標籤/搜索