Git 何謂分支

2019-08-14 17:28 更新

為了理解 Git 分支的實(shí)現(xiàn)方式,我們需要回顧一下 Git 是如何儲(chǔ)存數(shù)據(jù)的。或許你還記得第一章的內(nèi)容,Git 保存的不是文件差異或者變化量,而只是一系列文件快照。

在 Git 中提交時(shí),會(huì)保存一個(gè)提交(commit)對(duì)象,該對(duì)象包含一個(gè)指向暫存內(nèi)容快照的指針,包含本次提交的作者等相關(guān)附屬信息,包含零個(gè)或多個(gè)指向該提交對(duì)象的父對(duì)象指針:首次提交是沒(méi)有直接祖先的,普通提交有一個(gè)祖先,由兩個(gè)或多個(gè)分支合并產(chǎn)生的提交則有多個(gè)祖先。

為直觀起見(jiàn),我們假設(shè)在工作目錄中有三個(gè)文件,準(zhǔn)備將它們暫存后提交。暫存操作會(huì)對(duì)每一個(gè)文件計(jì)算校驗(yàn)和(即第一章中提到的 SHA-1 哈希字串),然后把當(dāng)前版本的文件快照保存到 Git 倉(cāng)庫(kù)中(Git 使用 blob 類(lèi)型的對(duì)象存儲(chǔ)這些快照),并將校驗(yàn)和加入暫存區(qū)域:

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

當(dāng)使用 git commit 新建一個(gè)提交對(duì)象前,Git 會(huì)先計(jì)算每一個(gè)子目錄(本例中就是項(xiàng)目根目錄)的校驗(yàn)和,然后在 Git 倉(cāng)庫(kù)中將這些目錄保存為樹(shù)(tree)對(duì)象。之后 Git 創(chuàng)建的提交對(duì)象,除了包含相關(guān)提交信息以外,還包含著指向這個(gè)樹(shù)對(duì)象(項(xiàng)目根目錄)的指針,如此它就可以在將來(lái)需要的時(shí)候,重現(xiàn)此次快照的內(nèi)容了。

現(xiàn)在,Git 倉(cāng)庫(kù)中有五個(gè)對(duì)象:三個(gè)表示文件快照內(nèi)容的 blob 對(duì)象;一個(gè)記錄著目錄樹(shù)內(nèi)容及其中各個(gè)文件對(duì)應(yīng) blob 對(duì)象索引的 tree 對(duì)象;以及一個(gè)包含指向 tree 對(duì)象(根目錄)的索引和其他提交信息元數(shù)據(jù)的 commit 對(duì)象。概念上來(lái)說(shuō),倉(cāng)庫(kù)中的各個(gè)對(duì)象保存的數(shù)據(jù)和相互關(guān)系看起來(lái)如圖 3-1 所示:

2015-05-18/5559fd1067c0e

圖 3-1. 單個(gè)提交對(duì)象在倉(cāng)庫(kù)中的數(shù)據(jù)結(jié)構(gòu)

作些修改后再次提交,那么這次的提交對(duì)象會(huì)包含一個(gè)指向上次提交對(duì)象的指針(譯注:即下圖中的 parent 對(duì)象)。兩次提交后,倉(cāng)庫(kù)歷史會(huì)變成圖 3-2 的樣子:

2015-05-18/5559ff5ab607f

圖 3-2. 多個(gè)提交對(duì)象之間的鏈接關(guān)系

現(xiàn)在來(lái)談分支。Git 中的分支,其實(shí)本質(zhì)上僅僅是個(gè)指向 commit 對(duì)象的可變指針。Git 會(huì)使用 master 作為分支的默認(rèn)名字。在若干次提交后,你其實(shí)已經(jīng)有了一個(gè)指向最后一次提交對(duì)象的 master 分支,它在每次提交的時(shí)候都會(huì)自動(dòng)向前移動(dòng)。

2015-05-18/5559ff8756754

圖 3-3. 分支其實(shí)就是從某個(gè)提交對(duì)象往回看的歷史

那么,Git 又是如何創(chuàng)建一個(gè)新的分支的呢?答案很簡(jiǎn)單,創(chuàng)建一個(gè)新的分支指針。比如新建一個(gè) testing 分支,可以使用 git branch 命令:

$ git branch testing

這會(huì)在當(dāng)前 commit 對(duì)象上新建一個(gè)分支指針(見(jiàn)圖 3-4)。

2015-05-18/5559ffea76284

圖 3-4. 多個(gè)分支指向提交數(shù)據(jù)的歷史

那么,Git 是如何知道你當(dāng)前在哪個(gè)分支上工作的呢?其實(shí)答案也很簡(jiǎn)單,它保存著一個(gè)名為 HEAD 的特別指針。請(qǐng)注意它和你熟知的許多其他版本控制系統(tǒng)(比如 Subversion 或 CVS)里的 HEAD 概念大不相同。在 Git 中,它是一個(gè)指向你正在工作中的本地分支的指針(譯注:將 HEAD 想象為當(dāng)前分支的別名。)。運(yùn)行 git branch 命令,僅僅是建立了一個(gè)新的分支,但不會(huì)自動(dòng)切換到這個(gè)分支中去,所以在這個(gè)例子中,我們依然還在 master 分支里工作(參考圖 3-5)。

2015-05-18/555a000c5bb41

圖 3-5. HEAD 指向當(dāng)前所在的分支

要切換到其他分支,可以執(zhí)行 git checkout 命令。我們現(xiàn)在轉(zhuǎn)換到新建的 testing 分支:

$ git checkout testing

這樣 HEAD 就指向了 testing 分支(見(jiàn)圖3-6)。

2015-05-18/555a010dd9394

圖 3-6. HEAD 在你轉(zhuǎn)換分支時(shí)指向新的分支

這樣的實(shí)現(xiàn)方式會(huì)給我們帶來(lái)什么好處呢?好吧,現(xiàn)在不妨再提交一次:

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

圖 3-7 展示了提交后的結(jié)果。

2015-05-18/555a01365cc25

圖 3-7. 每次提交后 HEAD 隨著分支一起向前移動(dòng)

非常有趣,現(xiàn)在 testing 分支向前移動(dòng)了一格,而 master 分支仍然指向原先 git checkout時(shí)所在的 commit 對(duì)象?,F(xiàn)在我們回到 master 分支看看:

$ git checkout master

圖 3-8 顯示了結(jié)果。

2015-05-18/555a0159cbd7d

圖 3-8. HEAD 在一次 checkout 之后移動(dòng)到了另一個(gè)分支

這條命令做了兩件事。它把 HEAD 指針移回到 master 分支,并把工作目錄中的文件換成了 master 分支所指向的快照內(nèi)容。也就是說(shuō),現(xiàn)在開(kāi)始所做的改動(dòng),將始于本項(xiàng)目中一個(gè)較老的版本。它的主要作用是將 testing 分支里作出的修改暫時(shí)取消,這樣你就可以向另一個(gè)方向進(jìn)行開(kāi)發(fā)。

我們作些修改后再次提交:

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

現(xiàn)在我們的項(xiàng)目提交歷史產(chǎn)生了分叉(如圖 3-9 所示),因?yàn)閯偛盼覀儎?chuàng)建了一個(gè)分支,轉(zhuǎn)換到其中進(jìn)行了一些工作,然后又回到原來(lái)的主分支進(jìn)行了另外一些工作。這些改變分別孤立在不同的分支里:我們可以在不同分支里反復(fù)切換,并在時(shí)機(jī)成熟時(shí)把它們合并到一起。而所有這些工作,僅僅需要 branch 和 checkout 這兩條命令就可以完成。

2015-05-18/555a018d77ee5

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

由于 Git 中的分支實(shí)際上僅是一個(gè)包含所指對(duì)象校驗(yàn)和(40 個(gè)字符長(zhǎng)度 SHA-1 字串)的文件,所以創(chuàng)建和銷(xiāo)毀一個(gè)分支就變得非常廉價(jià)。說(shuō)白了,新建一個(gè)分支就是向一個(gè)文件寫(xiě)入 41 個(gè)字節(jié)(外加一個(gè)換行符)那么簡(jiǎn)單,當(dāng)然也就很快了。

這和大多數(shù)版本控制系統(tǒng)形成了鮮明對(duì)比,它們管理分支大多采取備份所有項(xiàng)目文件到特定目錄的方式,所以根據(jù)項(xiàng)目文件數(shù)量和大小不同,可能花費(fèi)的時(shí)間也會(huì)有相當(dāng)大的差別,快則幾秒,慢則數(shù)分鐘。而 Git 的實(shí)現(xiàn)與項(xiàng)目復(fù)雜度無(wú)關(guān),它永遠(yuǎn)可以在幾毫秒的時(shí)間內(nèi)完成分支的創(chuàng)建和切換。同時(shí),因?yàn)槊看翁峤粫r(shí)都記錄了祖先信息(譯注:即 parent 對(duì)象),將來(lái)要合并分支時(shí),尋找恰當(dāng)?shù)暮喜⒒A(chǔ)(譯注:即共同祖先)的工作其實(shí)已經(jīng)自然而然地?cái)[在那里了,所以實(shí)現(xiàn)起來(lái)非常容易。Git 鼓勵(lì)開(kāi)發(fā)者頻繁使用分支,正是因?yàn)橛兄@些特性作保障。

接下來(lái)看看,我們?yōu)槭裁磻?yīng)該頻繁使用分支。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)