git 使用詳解(8) 分支HEAD

2021-09-29 19:43:05 字數 3597 閱讀 7664

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

1  何謂分支

為了理解 git 分支的實現方式,我們需要回顧一下 git 是如何儲存資料的。或許你還記得第一章的內容,git 儲存的不是檔案差異或者變化量,而只是一系列檔案快照。

在 git 中提交時,會儲存乙個提交(commit)物件,該物件包含乙個指向暫存內容快照的指標,包含本次提交的作者等相關附屬資訊,包含零個或多個指向該提交物件的父物件指標:首次提交是沒有直接祖先的,普通提交有乙個祖先,由兩個或多個分支合併產生的提交則有多個祖先。

為直觀起見,我們假設在工作目錄中有三個檔案,準備將它們暫存後提交。暫存操作會對每乙個檔案計算校驗和(即第一章中提到的 sha-1 雜湊字串),然後把當前版本的檔案快照儲存到 git 倉庫中(git 使用 blob 型別的物件儲存這些快照),並將校驗和加入暫存區域:

$ 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 的樣子:

圖 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 所示),因為剛才我們建立了乙個分支,轉換到其中進行了一些工作,然後又回到原來的主分支進行了另外一些工作。這些改變分別孤立在不同的分支裡:我們可以在不同分支裡反覆切換,並在時機成熟時把它們合併到一起。而所有這些工作,僅僅需要branchcheckout這兩條命令就可以完成。

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

由於 git 中的分支實際上僅是乙個包含所指物件校驗和(40 個字元長度 sha-1 字串)的檔案,所以建立和銷毀乙個分支就變得非常廉價。說白了,新建乙個分支就是向乙個檔案寫入 41 個位元組(外加乙個換行符)那麼簡單,當然也就很快了。

這和大多數版本控制系統形成了鮮明對比,它們管理分支大多採取備份所有專案檔案到特定目錄的方式,所以根據專案檔案數量和大小不同,可能花費的時間也會有相當大的差別,快則幾秒,慢則數分鐘。而git 的實現與專案複雜度無關,它永遠可以在幾毫秒的時間內完成分支的建立和切換。同時,因為每次提交時都記錄了祖先資訊(譯註:即parent物件),將來要合併分支時,尋找恰當的合併基礎(譯註:即共同祖先)的工作其實已經自然而然地擺在那裡了,所以實現起來非常容易。git 鼓勵開發者頻繁使用分支,正是因為有著這些特性作保障。

接下來看看,我們為什麼應該頻繁使用分支。

git 使用詳解(8) 分支HEAD

有人把 git 的分支模型稱為 必殺技特性 而正是因為它,將 git 從版本控制系統家族裡區分出來。git 有何特別之處呢?git 的分支可謂是難以置信的輕量級,它的新建操作幾乎可以在瞬間完成,並且在不同分支間切換起來也差不多一樣快。和許多其他版本控制系統不同,git鼓勵在工作流程中頻繁使用分支與合...

Git的使用 6 分支管理

分支的簡單的理解就是分身,就像孫悟空拔出猴毛變出很多跟自己一模一樣的猴子,然後每個猴子做自己的事情互不干涉,等到所有猴子做完之後,猴子集合來合併勞動成果,然後悟空就把那些猴子猴孫門統統收回了。git的分支也是乙個殺手鐗級別的功能!比如 你現在正在a.php裡面做新功能的增加,大約寫了300行 突然,...

Git學習系列 五 分支管理詳解

分支管理在咱們實際工作中經常用到,因此掌握分支對於咱們從事移動開發並且使用到了git這樣的分布式版本管理工具來說是很有必要的。比如當你餓了的時候,甲幫你做飯,一幫你燒菜 當然你也可以自己做飯燒菜。但是咱們仍然可以看到,我是從乙個餓的狀態變成了乙個飽的狀態,也就是我之前空腹,後來我由甲 分支 和乙 分...