首頁>技術>

幾乎所有的版本控制系統都以某種形式支援分支。 使用分支意味著你可以把你的工作從開發主線上分離開來,以免影響開發主線。 在很多版本控制系統中,這是一個略微低效的過程——常常需要完全建立一個原始碼目錄的副本。對於大專案來說,這樣的過程會耗費很多時間。

有人把 Git 的分支模型稱為它的“必殺技特性”,也正因為這一特性,使得 Git 從眾多版本控制系統中脫穎而出。 為何 Git 的分支模型如此出眾呢? Git 處理分支的方式可謂是難以置信的輕量,建立新分支這一操作幾乎能在瞬間完成,並且在不同分支之間的切換操作也是一樣便捷。 與許多其它版本控制系統不同,Git 鼓勵在工作流程中頻繁地使用分支與合併,哪怕一天之內進行許多次。 理解和精通這一特性,你便會意識到 Git 是如此的強大而又獨特,並且從此真正改變你的開發方式。

分支簡介

為了真正理解 Git 處理分支的方式,我們需要回顧一下 Git 是如何儲存資料的。

或許你還記得 起步 的內容, Git 儲存的不是檔案的變化或者差異,而是一系列不同時刻的 快照

在進行提交操作時,Git 會儲存一個提交物件(commit object)。 知道了 Git 儲存資料的方式,我們可以很自然的想到——該提交物件會包含一個指向暫存內容快照的指標。 但不僅僅是這樣,該提交物件還包含了作者的姓名和郵箱、提交時輸入的資訊以及指向它的父物件的指標。 首次提交產生的提交物件沒有父物件,普通提交操作產生的提交物件有一個父物件, 而由多個分支合併產生的提交物件有多個父物件,

為了更加形象地說明,我們假設現在有一個工作目錄,裡面包含了三個將要被暫存和提交的檔案。 暫存操作會為每一個檔案計算校驗和(使用我們在 起步 中提到的 SHA-1 雜湊演算法),然後會把當前版本的檔案快照儲存到 Git 倉庫中 (Git 使用 blob 物件來儲存它們),最終將校驗和加入到暫存區域等待提交:

$ git add README test.rb LICENSE$ git commit -m 'The initial commit of my project'

當使用 git commit 進行提交操作時,Git 會先計算每一個子目錄(本例中只有專案根目錄)的校驗和, 然後在 Git 倉庫中這些校驗和儲存為樹物件。隨後,Git 便會建立一個提交物件, 它除了包含上面提到的那些資訊外,還包含指向這個樹物件(專案根目錄)的指標。 如此一來,Git 就可以在需要的時候重現此次儲存的快照。

現在,Git 倉庫中有五個物件:三個 blob 物件(儲存著檔案快照)、一個 物件 (記錄著目錄結構和 blob 物件索引)以及一個 提交 物件(包含著指向前述樹物件的指標和所有提交資訊)。

Figure 9. 首次提交物件及其樹結構

做些修改後再次提交,那麼這次產生的提交物件會包含一個指向上次提交物件(父物件)的指標。

Figure 10. 提交物件及其父物件

Git 的分支,其實本質上僅僅是指向提交物件的可變指標。 Git 的預設分支名字是 master。 在多次提交操作之後,你其實已經有一個指向最後那個提交物件的 master 分支。 master 分支會在每次提交時自動向前移動。

Note

Git 的 master 分支並不是一個特殊分支。 它就跟其它分支完全沒有區別。 之所以幾乎每一個倉庫都有 master 分支,是因為 git init 命令預設建立它,並且大多數人都懶得去改動它。

Figure 11. 分支及其提交歷史

分支建立

Git 是怎麼建立新分支的呢? 很簡單,它只是為你建立了一個可以移動的新的指標。 比如,建立一個 testing 分支, 你需要使用 git branch 命令:

$ git branch testing

這會在當前所在的提交物件上建立一個指標。

Figure 12. 兩個指向相同提交歷史的分支

那麼,Git 又是怎麼知道當前在哪一個分支上呢? 也很簡單,它有一個名為 HEAD 的特殊指標。 請注意它和許多其它版本控制系統(如 Subversion 或 CVS)裡的 HEAD 概念完全不同。 在 Git 中,它是一個指標,指向當前所在的本地分支(譯註:將 HEAD 想象為當前分支的別名)。 在本例中,你仍然在 master 分支上。 因為 git branch 命令僅僅 建立 一個新分支,並不會自動切換到新分支中去。

Figure 13. HEAD 指向當前所在的分支

你可以簡單地使用 git log 命令檢視各個分支當前所指的物件。 提供這一功能的引數是 --decorate。

$ git log --oneline --decoratef30ab (HEAD -> master, testing) add feature #32 - ability to add new formats to the central interface34ac2 Fixed bug #1328 - stack overflow under certain conditions98ca9 The initial commit of my project

正如你所見,當前 master 和 testing 分支均指向校驗和以 f30ab 開頭的提交物件。

分支切換

要切換到一個已存在的分支,你需要使用 git checkout 命令。 我們現在切換到新建立的 testing 分支去:

$ git checkout testing

這樣 HEAD 就指向 testing 分支了。

Figure 14. HEAD 指向當前所在的分支

那麼,這樣的實現方式會給我們帶來什麼好處呢? 現在不妨再提交一次:

$ vim test.rb$ git commit -a -m 'made a change'

Figure 15. HEAD 分支隨著提交操作自動向前移動

如圖所示,你的 testing 分支向前移動了,但是 master 分支卻沒有,它仍然指向執行 git checkout 時所指的物件。 這就有意思了,現在我們切換回 master 分支看看:

$ git checkout master

Figure 16. 檢出時 HEAD 隨之移動

這條命令做了兩件事。 一是使 HEAD 指回 master 分支,二是將工作目錄恢復成 master 分支所指向的快照內容。 也就是說,你現在做修改的話,專案將始於一個較舊的版本。 本質上來講,這就是忽略 testing 分支所做的修改,以便於向另一個方向進行開發。

Note

分支切換會改變你工作目錄中的檔案

在切換分支時,一定要注意你工作目錄裡的檔案會被改變。 如果是切換到一個較舊的分支,你的工作目錄會恢復到該分支最後一次提交時的樣子。 如果 Git 不能幹淨利落地完成這個任務,它將禁止切換分支。

我們不妨再稍微做些修改並提交:

$ vim test.rb$ git commit -a -m 'made other changes'

現在,這個專案的提交歷史已經產生了分叉(參見 專案分叉歷史)。 因為剛才你建立了一個新分支,並切換過去進行了一些工作,隨後又切換回 master 分支進行了另外一些工作。 上述兩次改動針對的是不同分支:你可以在不同分支間不斷地來回切換和工作,並在時機成熟時將它們合併起來。 而所有這些工作,你需要的命令只有 branch、checkout 和 commit。

Figure 17. 專案分叉歷史

你可以簡單地使用 git log 命令檢視分叉歷史。 執行 git log --oneline --decorate --graph --all ,它會輸出你的提交歷史、各個分支的指向以及專案的分支分叉情況。

$ git log --oneline --decorate --graph --all* c2b9e (HEAD, master) made other changes| * 87ab2 (testing) made a change|/* f30ab add feature #32 - ability to add new formats to the* 34ac2 fixed bug #1328 - stack overflow under certain conditions* 98ca9 initial commit of my project

由於 Git 的分支實質上僅是包含所指物件校驗和(長度為 40 的 SHA-1 值字串)的檔案,所以它的建立和銷燬都異常高效。 建立一個新分支就相當於往一個檔案中寫入 41 個位元組(40 個字元和 1 個換行符),如此的簡單能不快嗎?

這與過去大多數版本控制系統形成了鮮明的對比,它們在建立分支時,將所有的專案檔案都複製一遍,並儲存到一個特定的目錄。 完成這樣繁瑣的過程通常需要好幾秒鐘,有時甚至需要好幾分鐘。所需時間的長短,完全取決於專案的規模。 而在 Git 中,任何規模的專案都能在瞬間建立新分支。 同時,由於每次提交都會記錄父物件,所以尋找恰當的合併基礎(譯註:即共同祖先)也是同樣的簡單和高效。 這些高效的特性使得 Git 鼓勵開發人員頻繁地建立和使用分支。

接下來,讓我們看看你為什麼應該這樣做。

Note

建立新分支的同時切換過去

通常我們會在建立一個新分支後立即切換過去,這可以用 git checkout -b <newbranchname> 一條命令搞定。

17
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • ES 和Kibana 輕鬆載入示例資料並匯入到新索引