git 使用詳解(8)-- 分支HEAD

         有人把 Git 的分支模型稱爲「必殺技特性」,而正是由於它,將 Git 從版本控制系統家族裏區分出來。Git 有何特別之處呢?Git 的分支可謂是難以置信的輕量級,它的新建操做幾乎能夠在瞬間完成,而且在不一樣分支間切換起來也差很少同樣快。和許多其餘版本控制系統不一樣,Git鼓勵在工做流程中頻繁使用分支與合併,哪怕一天以內進行許屢次都沒有關係。理解分支的概念並熟練運用後,你纔會意識到爲何 Git 是一個如此強大而獨特的工具,並今後真正改變你的開發方式。git

1  何謂分支vim

爲了理解 Git 分支的實現方式,咱們須要回顧一下 Git 是如何儲存數據的。或許你還記得第一章的內容,Git 保存的不是文件差別或者變化量,而只是一系列文件快照。數據結構

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

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

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

如今,Git 倉庫中有五個對象:三個表示文件快照內容的 blob 對象;一個記錄着目錄樹內容及其中各個文件對應 blob 對象索引的 tree 對象;以及一個包含指向 tree 對象(根目錄)的索引和其餘提交信息元數據的 commit 對象。概念上來講,倉庫中的各個對象保存的數據和相互關係看起來如圖 3-1 所示:指針

圖 3-1. 單個提交對象在倉庫中的數據結構對象

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

圖 3-2. 多個提交對象之間的連接關係索引

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

圖 3-3. 分支其實就是從某個提交對象往回看的歷史

那麼,Git 又是如何建立一個新的分支的呢?答案很簡單,建立一個新的分支指針。好比新建一個 testing 分支,可使用 git branch 命令:

$ git branch testing
這會在當前 commit 對象上新建一個分支指針(見圖 3-4)。

圖 3-4. 多個分支指向提交數據的歷史

那麼,Git 是如何知道你當前在哪一個分支上工做的呢?其實答案也很簡單,它保存着一個名爲 HEAD 的特別指針。請注意它和你熟知的許多其餘版本控制系統(好比 Subversion 或 CVS)裏的 HEAD 概念大不相同。在 Git 中,它是一個指向你正在工做中的本地分支的指針(譯註:將 HEAD 想象爲當前分支的別名。)。運行git branch 命令,僅僅是創建了一個新的分支,但不會自動切換到這個分支中去,因此在這個例子中,咱們依然還在 master 分支裏工做(參考圖 3-5)。

圖 3-5. HEAD 指向當前所在的分支

要切換到其餘分支,能夠執行 git checkout 命令。咱們如今轉換到新建的 testing 分支:

$ git checkout testing
這樣 HEAD 就指向了 testing 分支(見圖3-6)。

圖 3-6. HEAD 在你轉換分支時 指向 新的分支

這樣的實現方式會給咱們帶來什麼好處呢?好吧,如今不妨再提交一次:

$ vim test.rb
$ git commit -a -m 'made a change'
圖 3-7 展現了提交後的結果。

圖 3-7. 每次提交後 HEAD 隨着分支一塊兒向前移動

很是有趣,如今 testing 分支向前移動了一格,而 master 分支仍然指向原先 git checkout 時所在的 commit 對象。如今咱們回到 master 分支看看:

$ git checkout master
圖 3-8 顯示告終果。

圖 3-8. HEAD 在一次 checkout 以後移動到了另外一個分支

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

咱們做些修改後再次提交:

$ vim test.rb
$ git commit -a -m 'made other changes'
如今咱們的項目提交歷史產生了分叉(如圖 3-9 所示),由於剛纔咱們建立了一個分支,轉換到其中進行了一些工做,而後又回到原來的主分支進行了另一些工做。這些改變分別孤立在不一樣的分支裏:咱們能夠在不一樣分支裏反覆切換,並在時機成熟時把它們合併到一塊兒。而全部這些工做,僅僅須要branch 和checkout 這兩條命令就能夠完成。

圖 3-9. 不一樣流向的分支歷史

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

這和大多數版本控制系統造成了鮮明對比,它們管理分支大多采起備份全部項目文件到特定目錄的方式,因此根據項目文件數量和大小不一樣,可能花費的時間也會有至關大的差異,快則幾秒,慢則數分鐘。而 Git 的實現與項目複雜度無關,它永遠能夠在幾毫秒的時間內完成分支的建立和切換。同時,由於每次提交時都記錄了祖先信息(譯註:即parent 對象),未來要合併分支時,尋找恰當的合併基礎(譯註:即共同祖先)的工做其實已經天然而然地擺在那裏了,因此實現起來很是容易。Git 鼓勵開發者頻繁使用分支,正是由於有着這些特性做保障。

接下來看看,咱們爲何應該頻繁使用分支。

相關文章
相關標籤/搜索