章節 ▾ 第二版

3.1 Git 分支 - 分支簡述

幾乎每個版本控制系統(VCS)都某種形式的分支支援。分支意味著你脫離主開發線,繼續工作而不會干擾主線。在許多VCS工具中,這是一個相對昂貴的過程,通常需要你建立原始碼目錄的新副本,這對於大型專案可能需要很長時間。

有些人將Git的分支模型稱為其“殺手級功能”,它確實使Git在VCS社群中脫穎而出。為什麼它如此特別?Git分支的方式極其輕量級,使得分支操作幾乎是瞬時完成的,並且在分支之間切換通常也同樣快速。與許多其他VCS不同,Git鼓勵頻繁地進行分支和合並工作流,甚至一天內多次。理解和掌握此功能為你提供了強大而獨特的工具,並可以完全改變你的開發方式。

分支簡述

要真正理解Git分支的方式,我們需要退一步,檢查Git如何儲存其資料。

正如你可能從什麼是Git?中記住的那樣,Git不是將資料儲存為一系列變更集或差異,而是儲存為一系列快照

當你進行一次提交時,Git會儲存一個提交物件,其中包含指向你已暫存內容的快照的指標。此物件還包含作者的姓名和電子郵件地址,你輸入的提交資訊,以及指向此提交直接之前的一個或多個提交的指標(它的父提交或父提交們):初始提交有零個父提交,普通提交有一個父提交,以及由兩個或多個分支合併產生的提交有多個父提交。

為了形象化地說明這一點,假設你有一個包含三個檔案的目錄,你將它們全部暫存並提交。暫存檔案會計算每個檔案的校驗和(我們在什麼是Git?中提到的SHA-1雜湊),將該檔案版本儲存在Git倉庫中(Git將它們稱為blob物件),並將該校驗和新增到暫存區

$ git add README test.rb LICENSE
$ git commit -m 'Initial commit'

當你執行git commit建立提交時,Git會為每個子目錄(在本例中,只是根專案目錄)計算校驗和,並將它們作為樹物件儲存在Git倉庫中。然後,Git會建立一個提交物件,其中包含元資料和指向根專案樹的指標,以便在需要時重新建立該快照。

你的Git倉庫現在包含五個物件:三個blob物件(每個代表三個檔案之一的內容),一個樹物件,它列出目錄內容並指定哪些檔名儲存為哪些blob物件,以及一個提交物件,它包含指向該根樹的指標和所有提交元資料。

A commit and its tree
圖 9. 一個提交及其樹物件

如果你進行一些更改並再次提交,下一個提交會儲存一個指向緊接在其之前的提交的指標。

Commits and their parents
圖 10. 提交及其父提交

Git中的分支只是一個輕量級的可移動指標,指向這些提交之一。Git中預設的分支名稱是master。當你開始提交時,你會得到一個指向你所做最後一個提交的master分支。每次提交時,master分支指標都會自動向前移動。

注意

Git中的“master”分支不是一個特殊分支。它與任何其他分支完全一樣。幾乎每個倉庫都有一個master分支的唯一原因是git init命令預設建立它,並且大多數人懶得更改它。

A branch and its commit history
圖 11. 一個分支及其提交歷史

建立新分支

當你建立一個新分支時會發生什麼?這樣做會為你建立一個新的可移動指標。假設你想建立一個名為testing的新分支。你可以使用git branch命令完成此操作

$ git branch testing

這會建立一個指向你當前所在提交的新指標。

Two branches pointing into the same series of commits
圖 12. 兩個分支指向同一系列提交

Git如何知道你當前在哪個分支上?它保留一個名為HEAD的特殊指標。請注意,這與你可能習慣的Subversion或CVS等其他VCS中HEAD的概念有很大不同。在Git中,這是一個指向你當前所在本地分支的指標。在這種情況下,你仍然在master分支上。git branch命令只是建立了一個新分支,但沒有切換到該分支。

HEAD pointing to a branch
圖 13. HEAD指向一個分支

你可以透過執行一個簡單的git log命令來輕鬆檢視這一點,它會顯示分支指標指向的位置。這個選項稱為--decorate

$ git log --oneline --decorate
f30ab (HEAD -> master, testing) Add feature #32 - ability to add new formats to the central interface
34ac2 Fix bug #1328 - stack overflow under certain conditions
98ca9 Initial commit

你可以看到mastertesting分支就在f30ab提交旁邊。

切換分支

要切換到現有分支,請執行git checkout命令。讓我們切換到新的testing分支

$ git checkout testing

這會將HEAD移動指向testing分支。

HEAD points to the current branch
圖 14. HEAD指向當前分支

這有什麼意義呢?好吧,讓我們再進行一次提交

$ vim test.rb
$ git commit -a -m 'Make a change'
The HEAD branch moves forward when a commit is made
圖 15. 進行提交時HEAD分支向前移動

這很有趣,因為現在你的testing分支已經向前移動,但你的master分支仍然指向你在執行git checkout切換分支時所處的提交。讓我們切換回master分支

$ git checkout master
注意
git log不會一直顯示所有分支

如果你現在執行git log,你可能會想你剛剛建立的“testing”分支去哪了,因為它不會出現在輸出中。

該分支並沒有消失;Git只是不知道你對該分支感興趣,它正在嘗試顯示它認為你感興趣的內容。換句話說,預設情況下,git log只會顯示你已檢出分支下方的提交歷史。

要顯示所需分支的提交歷史,你必須明確指定它:git log testing。要顯示所有分支,請在git log命令中新增--all

HEAD moves when you checkout
圖 16. 檢出時HEAD移動

該命令做了兩件事。它將HEAD指標移回指向master分支,並將你的工作目錄中的檔案還原到master所指向的快照。這也意味著你從此時起所做的更改將與專案的舊版本有所不同。它本質上是回溯你在testing分支中所做的工作,以便你可以走向不同的方向。

注意
切換分支會更改你工作目錄中的檔案

值得注意的是,當你在Git中切換分支時,你工作目錄中的檔案會發生變化。如果你切換到舊的分支,你的工作目錄將恢復到你上次在該分支上提交時的樣子。如果Git無法乾淨地完成此操作,它將根本不允許你切換。

讓我們再進行一些更改並再次提交

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

現在你的專案歷史已經分叉(參見分叉歷史)。你建立並切換到一個分支,在上面做了一些工作,然後切換回主分支並做了其他工作。所有這些更改都隔離在單獨的分支中:你可以在分支之間來回切換,並在準備好時將它們合併。而你所有這些操作都只用了簡單的branchcheckoutcommit命令就完成了。

Divergent history
圖 17. 分叉歷史

你也可以使用git log命令輕鬆檢視這一點。如果你執行git log --oneline --decorate --graph --all,它將打印出你的提交歷史,顯示你的分支指標在哪裡以及你的歷史是如何分叉的。

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

因為Git中的分支實際上是一個簡單的檔案,其中包含它所指向的提交的40個字元的SHA-1校驗和,所以建立和銷燬分支的成本很低。建立一個新分支就像向檔案寫入41個位元組(40個字元加一個換行符)一樣快速和簡單。

這與大多數舊VCS工具的分支方式形成鮮明對比,後者涉及將專案的所有檔案複製到第二個目錄中。這可能需要幾秒甚至幾分鐘,具體取決於專案的大小,而在Git中,這個過程總是瞬時完成的。此外,由於我們在提交時記錄了父提交,因此自動為我們找到了合適的合併基礎,並且通常非常容易操作。這些功能有助於鼓勵開發人員頻繁建立和使用分支。

讓我們看看為什麼要這樣做。

注意
同時建立新分支並切換到它

通常會同時建立一個新分支並希望切換到該新分支——這可以透過一個操作完成:git checkout -b <newbranchname>

注意

從Git 2.23版本開始,你可以使用git switch代替git checkout

  • 切換到現有分支:git switch testing-branch

  • 建立新分支並切換到它:git switch -c new-branch-c標誌代表建立(create),你也可以使用完整標誌:--create

  • 返回你之前檢出的分支:git switch -

scroll-to-top