Git
章節 ▾ 第二版

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" 分支並不是一個特殊的分支。它與任何其他分支完全相同。幾乎每個儲存庫都有它的唯一原因是 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 的特殊指標。請注意,這與您可能習慣的其他 VCS(例如 Subversion 或 CVS)中的 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。若要顯示所有分支,請將 --all 新增至您的 git log 命令。

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