我們中的許多人每天都在使用 ?git
,但是有多少人知道它的內部是怎么運作的呢?
例如我們使用 ?git commit
?時發生了什么?提交(commit)與提交之間保存的是什么?兩次提交之間難道只是文件的差異(diff)嗎?如果是,這個差異是如何編碼的?還是說每次提交都會保存一個當前倉庫的完整快照(snapshot)呢?我們使用 ?git init
?時到底發生了什么?
很多 ?git
?的使用者都不知道這幾個問題的答案,但這又有什么關系呢?
首先,作為專業人員,我們應當努力弄清楚手中使用的工具,尤其是那些我們一直都在使用的——比如 ?git
。
但是我深刻地意識到,理解 Git 的工作原理在很多情況下都非常有用——不管是解決合并沖突、進行有趣的變基(rebase)操作,還是在某些東西變得有點不對勁的時候。
如果你有足夠的 ?git
?經驗,對 ?git pull
、git push
、git add
?或 ?git commit
?這些命令得心應手,你會從本文中獲益。
不過,為了確保我們在 ?git
?的原理(尤其是本文上下所使用的術語)上步調一致,我們將從概覽開始。
我也在 YouTube 上傳了一個涵蓋本文所有內容的系列視頻——歡迎在此點擊觀看。
本教程的內容
我們將對日常使用的 ?git
?的內部運行原理有一個比較深入的理解。
我們會從對象(object)——blob、樹對象(tree) ?和 ?提交對象(commit) ?開始,然后簡單討論一下 ?分支(branch) ?及其實現方式,之后會深入 ?工作目錄(working directory)、暫存區(staging area) ?和 ?倉庫(repository)。
我們會確保理解了這些術語是與我們用來創建新倉庫的那些命令之間是如何關聯的。
接下來,我們會從零開始創建一個倉庫——不使用 ?git init
、git add
?或 ?git commit
。這會在我們使用 ?git
?的過程中,加深我們對其內部正在發生的事情的理解。
我們也會創建新的分支、在分支間切換,再進行一些提交——全程不使用 ?git branch
?或 ?git checkout
。
在本文結束之前,你會覺得自己真的 ?理解了 ?git
。你準備好了嗎?????
譯者注:建議讀者配合?Git 內部原理閱讀本文。
Git 對象——blob、tree 和 commit
譯者注:譯文中的 ?數據對象、樹對象 ?和 ?提交對象 ?指的就是 blob、tree 和 commit 這三者。因為 Git 官網的文檔?Git 內部原理 - Git 對象對三者進行了這樣的翻譯,本文是為了與其保持一致。但由于 blob 一詞的特殊性,譯文會直接保留原詞,而不是將其翻譯為“數據對象”。
將 ?git
?看成一個文件系統(尤其是該系統的實時快照)是很有用的。
一個文件系統從 ?根目錄(root directory) ?開始(在基于 UNIX 的系統中是 ?/
),通常也會包含其它的目錄(例如 ?/usr
?或 ?/bin
)。這些目錄會包含其它的目錄和(或)文件(例如 ?/usr/1.txt
)。
在 ?git
?中,文件的內容存儲在一些被稱為 ?blob ?(二進制大對象)的對象中。
blob ?與文件的不同在于,文件還會包含元數據(meta-data)。例如一個文件會“記住”它的創建時間,如果你把它移動到另一個目錄,它的創建時間是不會改變的。
相反,blob ?只是內容——數據的二進制流。除了內容以外,blob ?不會記錄它的創建時間、名字或任何其它東西。
git
?中的 ?blob ?通過 SHA-1 哈希值 唯一標識。SHA-1 哈希值由 20 個字節(byte)組成,通常表示成 40 個十六進制形式的字符。在這篇文章中,我們有時只會展示這個哈希值的前幾個字符。
在 ?git
?中,樹對象(tree) ?相當于目錄。一個 ?樹對象 ?基本上就是一個目錄列表,它引用著 ?blob ?和其它的 ?樹對象。
樹對象 ?也用 SHA-1 哈希值唯一標識,它通過其它對象(blob ?或 ?樹對象)的 SHA-1 哈希值引用它們。
注意 ?CAFE7 ?這個 ?樹對象 ?指向了 ?blob F92A0(pic.png),在另一個 ?樹對象 ?中,同一個 ?blob ?可能會有不同的名字。
上面這張圖相當于一個文件系統,這個文件系統有一個根目錄,根目錄下有一個位于 ?/test.js
?的文件和一個名為 ?/docs
?的目錄,/docs
?目錄下有兩個文件:/docs/pic.png
?和 ?/docs/1.txt
。
現在是時候捕獲該文件系統的一個快照了,把那個時刻存在的所有文件連同它們的內容保存下來。
在 ?git
?中,一個快照就是一個 ?提交(commit)。一個 ?提交 ?對象包括一個指向主要 ?樹對象(根目錄)的指針和一些像 ?提交者、提交信息 ?和 ?提交時間 ?這樣的元數據。
在大多數情況下,一個 ?提交 ?還會有一個或多個父 ?提交——之前的快照。當然,提交 ?對象也通過它們的 SHA-1 哈希值唯一標識。這些哈希值就是我們使用 ?git log
?命令時看到的那些哈希值。
每個 ?提交 ?都持有 ?完整的快照,并不只是與之前 ?提交 ?之前的差異。
那么它是怎么工作的呢?難道它不代表我們每次提交都必須保存很多數據嗎?
讓我們來看看改變一個文件的內容會發生什么。我們編輯 ?1.txt
,加一個感嘆號——也就是把文件的內容由 ?HELLO WORLD
?變為 ?HELLO WORLD!
。
這個改變意味著我們會有一個新的 ?blob,它有新的 SHA-1 哈希值。這是有意義的,因為 ?sha1("HELLO WORLD")
?與 ?sha1("HELLO WORLD!")
?并不相同。
由于我們得到了一個新的哈希值,所以對應 ?樹對象 ?的目錄也會改變。畢竟,我們的 ?樹對象 ?不再指向 ?blob 73D8A ?了,而是指向了 ?blob 62E7A。當我們改變 ?樹對象 ?的內容時,我們也改變了它的哈希值。
現在,由于原來那個?樹對象?的哈希值已經不同了,我們也需要改變它的父樹對象——后者不再指向?tree CAFE7?了,而是指向了?tree 246001。最終,父樹對象也會有一個新的哈希值。
幾乎做好創建一個新提交對象的準備了,我們好像會再一次保存很多的數據——整個文件系統。但是真的有必要這么做嗎?
實際上,一些對象(尤其是?blob?對象)相比起之前的提交來說沒有任何改變——blob F92A0仍然原封不動,blob F00D1?也一樣。
這就是其中的秘訣——只有對象改變了,我們才再次保存它。在這個例子中,我們不需要再次保存 ?blob F92A0 ?和 ?blob F00b1。我們只需要通過它們的哈希值引用它們,然后我們可以創建 ?提交 ?對象。
由于這次 ?提交 ?不是第一次 ?提交,所以它有一個父節點——commit A1337。
回顧一下,我們介紹了三種 git 對象:
blob——文件的內容。
樹對象——一個(由 ?blob ?和 ?樹對象 ?組成的)目錄列表。
提交對象——工作樹的一個快照。
讓我們思考一下這些對象的哈希值吧。如果我寫了 ?git is awesome!
?并從它創建了一個 ?blob。你也在自己的系統上這么做,我們會有相同的哈希值嗎?
答案是肯定的。因為這兩個 ?blob ?有相同的內容,自然也會有相同的 SHA-1 哈希值。
如果我創建了一個引用 ?git is awesome!
?這個 ?blob ?的 ?樹對象 ?,賦給它一個特定的名字和元數據,你也在自己的系統上重復我的操作。我們會有相同的哈希值嗎?
答案還是肯定的。因為這兩個樹對象是相同的,它們會有同樣的哈希值。
如果我創建了一個指向那個?樹對象的提交對象,提交信息為 ?Hello
,你也在自己的系統上重復了一遍這個操作,結果會怎樣呢?我們的哈希值還會相同嗎?
這個時候的答案是否定的。即使我們的提交對象指向了相同的樹對象,它們也會有不同的提交詳情——時間、提交者,等等。
Git 中的分支
分支(branch)只不過是提交對象的命名引用。
譯者注:分支引用的是提交對象,為了簡單起見,下文在談分支時,有時候會將提交對象 ?簡稱為提交。
我們可以一直用 SHA-1 哈希值引用一個提交,但是人們通常喜歡以其他形式命名對象。分支恰好是引用提交的一種方式,實際上也只是這樣。
在大多數倉庫中,主線開發都是在一個叫做 ?master
?的分支上完成的。master
?只是一個名字,它是在我們使用 ?git init
?命令的時候被創建的。正因為如此,它被廣泛使用。然而,它并不特別,我們可以用任何我們喜歡的名字代替它。
通常,分支指向的是當前開發線上的最近一次提交。
git branch
命令創建一個新分支,而我們實際創建的卻是另一個指針(pointer)。假設我們使用 ?git branch test
命令創建了一個名為 ?test
的分支,我們實際上是創建了另一個指針,它指向當前分支上的同一 ?提交。使用 ?git branch
?創建另一個指針
git
?是怎么知道我們當前所在的分支呢?答案是它維護了一個名為 ?HEAD
?的特殊指針。通常情況下,HEAD
?會指向一個分支,這個分支指向一個提交。有時候,HEAD
?也能直接指向一個提交,不過這不是我們的重點。
HEAD 指向我們當前所在的分支
譯者注:活動分支(active branch)指的是我們當前所在的分支,也就是 ?
HEAD
?指向的分支。
要將活動分支切換到 ?test
,我們可以使用命令 ?git checkout test
。現在我們已經能猜到這條命令真正做的事情了——它只不過是把 ?HEAD
?指向的分支改成了 ?test
。
git checkout test
?改變 ?HEAD
?指向的分支
在創建 ?test
?分支之前,我們也可以使用 ?git checkout -b test
,這條命令等價于先運行 ?git branch test
?創建分支,再運行 ?git checkout test
?使 ?HEAD
?指向新的分支。
如果我們做了一些改動并使用 ?git commit
?創建了一個新提交呢?這個新提交會被添加到哪個分支上呢?
答案是 ?test
?分支,因為它是當前的活動分支(因為 ?HEAD
?指向了它)。之后,test
?指針會移動至新添加的 ?提交 ?上。注意 ?HEAD
?仍然指向 ?test
。
每次執行 ?git commit
?命令都會讓分支的指針移動到新創建的提交上
因此,如果我們使用 ?git checkout master
?回到 master 分支,我們就讓 ?HEAD
?的再次指向 ?master
?了。
如果我們現在創建一個新的 ?提交,它就會被添加到 ?master
?分支,commit B2424 ?會成為新提交的父節點。
如何在 Git 中記錄變化
通常,我們在工作目錄(working dir) ?中編寫源代碼。工作目錄(或工作樹(working tree))可以是文件系統上的任何一個目錄,它關聯著一個倉庫(repository)。目錄內不僅包含工程的文件夾和文件,還包含一個名為 ?.git
?的目錄。稍后我們會再討論 ?git
?這個目錄。
在做了一些改動之后,我們想把這些改動記錄到我們的倉庫中。一個倉庫(縮寫:repo)就是一系列提交的集合,每個提交都是工程工作樹的歸檔。除了我們自己機器上的提交外,倉庫也會包含他人機器上的提交。
倉庫 ?也包含除代碼文件以外的其它東西,例如 ?HEAD
?指針、分支等等。
你可能使用過的其它和 ?git
?類似工具,但是 ?git
?并不會像其它工具那樣直接將變化從工作樹提交到倉庫。相反,它會先把這些變化注冊到一個被稱為索引(index)或暫存區(staging area)的地方。
這兩個術語指的都是同一個東西,它們也經常被 ?git
?的文檔使用,我們將會在這篇文章中交替使用它們。
當我們 ?checkout
?到一個分支時,git
?會將上一次檢出到工作目錄中的所有文件填充到索引,它們看起來就像最初被檢出時的樣子。之后執行 ?git commit
?時,提交會在當前 ?索引 ?的基礎上創建。
索引允許我們精心準備每次 ?提交。舉個例子,自上一次提交以來,我們的工作目錄中可能有兩個文件發生了變化,但是我們可能只想將其中的一個添加到索引(使用 ?git add
),然后使用 ?git commit
?記錄這一個文件的變化。
工作目錄下文件的狀態不外乎有兩種:已跟蹤(tracked)或未跟蹤(untracked)。
已跟蹤文件是指那些 ?git
?已經知道的文件。它們要么已經在上一次快照(提交)中,要么已經被 ?暫存(staged)(換句話說,它們已經在 ?暫存區 ?中)。
工作目錄中除已跟蹤文件以外的所有其它文件都屬于未跟蹤文件(untracked),它們既沒有在上次快照(提交)中,也沒有在暫存區中。
創建倉庫的常規方式
讓我們確認下我們已經理解了“創建倉庫”時介紹的相關術語。在我們更加深入這個過程之前,這只是一個非常高階的視角。
注意:大多數帶有 shell 命令的文章展示的都是 UNIX 命令。我將同時給出 Windows 和 UNIX 下的命令。為了換換花樣,我會給出 Windows 下面的截圖。當兩種環境下的命令完全一樣時,我只會給出一次命令。
我們用 ?git init repo_1
?初始化一個新的倉庫,然后用 ?cd repo_1
?切換到倉庫所在目錄。借助 ?tree /f .git
?命令,我們可以看到運行 ?git init
?之后 ?.git
?目錄下面出現了很多子目錄(/f
?表示在 ?tree
?的輸出中包含文件)。
讓我們在 ?repo_1
?目錄中創建一個文件吧:
Linux 系統:
這個文件已經在我們的 ?工作目錄 ?中了。目前,我們還沒有將它添加到 ?暫存區,所以它是 ?未跟蹤 ?狀態。讓我們用 ?git status
?驗證一下:
因為我們沒有將新的文件添加到暫存區,所以它還是未跟蹤狀態,它也沒有在之前的提交中
我們現在用 ?git add new_file.txt
?將這個文件添加到 ?暫存區,再用 ?git status
?驗證一下它是否已經被暫存了:
添加新的文件到暫存區
我們可現在可以用 ?git commit
?創建一個 ?提交:
.git
?目錄有變化嗎?我們用 ?tree /f .git
?檢查一下:
git
?目錄中的很多東西已經變了
很明顯,很多東西都變了。是時候深入 ?.git
?的結構,理解執行 ?git init
、git add
?或 ?git commit
?之后發生的什么事情了。
是時候上干貨了
目前我們已經講了一些 Git 的基礎知識,現在已經做好 Git 上路 的準備了。
為了深入理解 ?git
?是如何工作的,我們將從零開始創建一個倉庫。
我們不會使用 ?git init
、git add
?或 ?git commit
,這會讓我們更好地理解這個過程。
如何設置 .git
先創建一個新目錄,然后在里面運行 ?git status
:
好吧,因為我們沒有 ?.git
?文件夾,git
?好像不怎么高興。我們先把這個目錄創建出來:
很明顯,只創建一個 ?.git
?目錄還不夠。我們需要往這個目錄添加一些東西。
一個 git 倉庫有兩個主要組成部分:
一組對象——blob、樹對象 ?和 ?提交對象。
一個命名這些對象的方式——稱為 ?引用。
譯者注:引用是 Git 中的一個重要概念,讀者可以進一步閱讀 ?Git 引用。
一個 ?倉庫 ?可能還包含一些其它的東西,比如 git 鉤子(hooks)。不過,倉庫至少必須要有對象和引用。
讓我們分別為對象和引用(簡稱:refs)各創建一個目錄,Windows 下的兩個目錄分別為 ?.git\objects
?和 ?.git\refs
(UNIX 下的兩個目錄分別為 ?.git/objects
?和 ?.git/refs
)。
分支 ?是引用的一種,git
?內部將 ?分支 ?稱為 ?heads,所以我們會為它們創建一個目錄 ?git\refs\heads
。
然而 ?git status
?的輸出還是紋絲不動:
在尋找 ?倉庫 ?中的 ?提交 ?時,git
?怎么知道該從何開始呢?我之前解釋過,它會尋找 ?HEAD
,而 ?HEAD
?指向著活動分支。
所以,我們需要創建 ?HEAD
,它是一個位于 ?.git\HEAD
?的文件。我們可以這么做:
Windows:> echo ref: refs/heads/master > .git\HEAD
UNIX:$ echo "ref: refs/heads/master" > .git/HEAD
? 所以我們現在知道 ?HEAD
?是如何實現的了——它只是一個文件,文件內容描述了它所指向的分支。
執行上面的命令以后,git status
?似乎改變它的主意了:
HEAD 只不過是一個文件
注意:雖然我們還沒有創建 ?master
?分支,但是 ?git
?相信我們就在這個分支上。之前有講過,master
只是一個名字。如果我們想的話,也可以讓 ?git
?認為我們在 ?banana
?分支上:
????
按照慣例,我們將在本文的剩余部分中切回 ?master
?分支。
我們已經準備好了 ?git
?目錄,現在繼續往下,來一次 ?提交(同樣地,不使用 ?git add
?或 ?git commit
)。
Git 中的底層命令與上層命令
這個時候,區分 ?底層(plumbing) ?和 ?上層(poreclain) ?兩類 ?git
?命令會對你很有幫助。這兩個術語的應用奇怪地來自于馬桶(沒錯,就是????)。馬桶通常是用陶瓷(porcelain)做的,它的基本結構是管道(plumbing,上水道和下水道)。
我們可以說上層命令為底層命令提供了一個用戶友好的接口。大多數人只會涉及到上層命令。然而,當事情變得(非常)糟糕時,有人可能就會想知道為什么,他們會卷起袖子去檢查底層命令。(注意:這些術語并不是我發明的,它們在 ?git
?中的使用非常廣泛)。
譯者注:讀者若想更好的理解這兩個術語,建議閱讀 ?Git 內部原理 - 底層命令與上層命令。
git
?使用這些術語進行類比,從而將用戶不常使用的底層命令(plumbing)和那些更友好的高層(porcelain)命令區分開。
目前,我們已經接觸過上層命令——git init
、git add
?和 ?git commit
。接下來,我們轉到底層命令。
如何創建對象
讓我們從創建對象并將其寫入 ?git
?的對象數據庫開始吧,git
?的對象數據庫位于 ?.git\objects
?中。第一條底層命令 ?git hash-object
?會讓我們將找到 ?blob 對象 ?的 SHA-1 哈希值。方式如下:
Windows:
> echo git is awesome | git hash-object --stdin
UNIX:
$ echo "git is awesome" | git hash-object --stdin
我們使用 ?--stdin
?告知 ?git hash-object
?從標準輸入(standard input)獲取輸入內容,這將給我們提供相應的哈希值。
為了真的將該 ?blob 對象 ?寫入 ?git
?的對象數據庫,我們可以簡單地給 ?git hash-object
?加一個 ?-w
?開關。然后,檢查 ?.git
?目錄中的內容,看看它們有沒有改變。
將一個 blob 對象寫入對象數據庫
我們現在可以看到,這個 ?blob ?的哈希值為 ?54f6...36
, ?.git\objects
?下也多出來了一個名為 ?54
?的目錄,目錄內有一個名為 ?f6..36
?的文件。
所以,git
?實際上是使用 SHA-1 哈希值的前兩個字符作為目錄的名字,剩余字符用作 ?blob ?所在文件的文件名。
為什么要這樣呢?考慮一個非常大的倉庫,倉庫的數據庫內存有三十萬個對象(blob 對象、樹對象 ?和 ?提交對象)。從這三十萬個哈希值中找出一個值會花些時間,因此,git
?將這個問題劃分成了 256 份。
為了查找上面的那個哈希值,git
?會先尋找 ?.git\objects
?目錄下名為 ?54
?的目錄,然后搜索那個目錄,這進一步縮小了搜索范圍。.git\objects
?目錄下最多可能會有 256 個子目錄(從 ?00
?到 ?FF
)。
回到生成 ?提交對象 ?的過程中來,現在我們已經創建了一個對象,它的類型是什么呢?我們可以通過另一個底層命令 ?git cat-file -t
?(-t
?代表“type”)瞧一瞧:
不出所料,這個對象是一個 ?blob。我們還可以使用 ?git cat-file -p
?(-p
?代表“pretty-print”)查看它的內容:
創建 ?blob ?這個過程通常發生在我們將一些東西添加到 ?暫存區 ?的時候——也就是我們使用 ?git add
?的時候。
記住:git
?是為 ?整個 ?暫存的文件創建 ?blob。即使文件中只有修改或添加了一個字符(如同我們在之前的例子紅添加 ?!
?一樣),該文件也會有一個新的 ?blob,這個 ?blob ?有著新的哈希值。
git status
?會有任何改變嗎?
顯然沒有。向 ?git
?的內部數據庫中添加一個 ?blob ?對象并不會改變狀態,因為 ?git
?在這個階段是不知道任何已跟蹤或未跟蹤文件的。
我們需要跟蹤這個文件——把它添加到 ?暫存區。為此,我們可以使用底層命令 ?git update-index
,例如:git update-index --add --cacheinfo 100644 <blob-hash> <filename>
。
注意:cacheinfo
?是一個?git 存儲的十六位的文件模式,這個模式遵循 POSIX 類型和模式 ?的布局。這超出了本文討論的范圍。
運行上述命令會改變 ?.git
?目錄的內容:
你能發現變化嗎?多了一個名為 ?index
?的新文件。這就是著名的索引(或 ?暫存區),它基本上是一個位于 ?.git\index
?中的文件。
既然 ?blob ?已經被添加到了 ?索引,我們希望 ?git status
?看起來會有所不同,像這樣:
真有趣!這里發生了兩件事。
第一件事,我們可以在 ?changes to be committed
?中看到綠色的 ?new_file.txt
。這是因為索引中有了 ?new_file.txt
,它正等著被提交。
第二件事,我們可以看到紅色的 ?new_file.txt
——因為 ?git
?相信 ?my_file.txt
?這個 ?文件 ?已經被刪除了,并且它沒有被暫存。
這發生在我們將內容為 ?git is awesome
?的 ?blob ?添加到對象數據庫中的時候,我們告訴 ?索引 ?,那個 ?blob ?的內容在文件 ?my_file.txt
?中,但是我們從未創建過那個文件。
通過將那個 ?blob ?的內容寫入我們文件系統中名為 ?my_file.txt
?的文件,我們可以很容易地解決這個問題:
執行 ?git status
?后,它將不再出現在紅色內容中:
現在是時候從我們的暫存區創建一個提交對象了。如上所述,一個提交 ?對象引用著一個樹對象,所以我們需要創建一個樹對象。
我們可以用 ?git write-tree
?做這件事,它會在一個樹對象中記錄索引的內容。當然,我們可以使用 ?git cat-file -t
?進行確認:
創建索引的樹對象
我們還可以用 ?git cat-file -p
?查看它的內容:
太棒了!我們創建了一個樹對象,現在我們需要創建一個引用這個樹對象的提交對象。為此,我們可以使用 ?git commit-tree <tree-hash> -m <commit message>
:
你現在應該對查看對象類型和打印對象內容的命令感到得心應手了:
創建一個提交對象
注意這個 ?提交 ?并沒有 ?父節點,因為它是第一個 ?提交。當我們添加另一個 ?提交 ?時,我們就得聲明它的 ?父節點了——我們稍后會做這個。
我們剛得到的哈希值(80e...8f
)是一個 ?提交對象 ?的哈希值。實際上我們非常習慣使用這些哈希值——我們一直都在看它們。注意這個 ?提交對象 ?擁有一個 ?樹對象,樹對象有自己的哈希值,不過我們幾乎不會顯式地指定這個哈希值。
git status
?會有所變化嗎?
并沒有。????
為什么呢?git
?需要知道最近一次 ?提交,才能知道文件已經被提交。那么 ?git
?是怎么做的呢?它會去找 ?HEAD
:
在 Windows 上查看 ?HEAD
在 UNIX 上查看 ?HEAD
HEAD
?指向 ?master
,但是 ?master
?是什么呢?我們還沒有創建它呢。
如同我們在前面解釋的那樣,分支只是 ?提交對象 ?的命名引用。這時,我們想要讓 ?master
?指向哈希值為 ?80e8ed4fb0bfc3e7ba88ec417ecf2f6e6324998f
?的 ?提交對象。
這實現起來很簡單,在 ?\refs\heads\master
?創建一個文件,文件內容為這個哈希值。像這樣:
? 總而言之,分支 ?只是 ?.git\refs\heads
?中的一個文件,文件內容為該分支所指向的 ?提交對象 ?的哈希值。
現在,git status
?和 ?git log
?終于欣賞我們的付出了:
我們已經成功創建出了一個提交,全程沒有使用上層命令!是不是很酷?????
與 Git 分支一起工作——背后的故事
就像我們不借助 ?git init
、git add
?或 ?git commit
?創建 ?倉庫 ?和 ?提交 ?一樣,我們將要創建 ?分支,在不同 ?分支 ?間來回切換,整個過程也不使用上層命令(git branch
?或 ?git checkout
)。
如果你很興奮,這是完全可以理解的。我也很興奮 ????
咱們開始吧:
目前我們只有一個名為 ?master
?的分支。要創建另一個名為 ?test
?的分支(等價于執行 ?git branch test
),我需要在 ?.git\refs\heads
?下創建一個名為 ?test
?的文件,文件的內容應該和 ?master
?分支指向的那個 ?提交 ?的哈希值一致。
如果我們使用 ?git log
,就可以看到 ?master
?和 ?test
?確實是指向同一個 ?提交:
我們也切換到新創建的分支吧(等價于執行 ?git branch test
)。為此,我們需要改變 ?HEAD
?的指向,讓它指向我們的新分支:
通過修改 ?HEAD
?切換到 ?test
?分支
我們可以看到:git status
?和 ?git log
?都確認 ?HEAD
?現在指向的是 ?test
?分支(活動分支)。
我們現在可以使用之前的命令去創建另一個文件,然后將它添加到索引:
我們用上面的命令創建了一個名為 ?test.txt
?的文件,文件內容為 ?Testing
。我們還創建了相應的 ?blob,將它添加了到 ?索引。我們還創建了代表這個 ?索引 ?的 ?樹對象。
現在是時候創建引用這個 ?樹對象 ?的 ?提交 ?了。這一次,我們還應該聲明這個提交的 ?父提交,也就是之前的那次 ?提交。我們用 ?git commit-tree
?命令的 ?-p
?開關聲明父節點:
可以看到,我們剛剛創建了一個 ?提交,還有它的 ?樹對象 ?和父節點:
git log
?會展示我們的新 ?提交 ?嗎?
可以看到:git log
?并沒有展示任何新的東西。為什么呢????? 還記得 ?git log
?會跟蹤 ?分支 ?,查找要展示的相關提交嗎?它現在給我們展示了 ?test
?和它指向的那個 ?提交,還展示了指向同一個提交的 ?master
。
沒錯,我們需要讓 ?test
?指向我們的新 ?提交。我們只需要稍微改變一下 ?.git\refs\heads\test
?的內容:
成功了! ????????
git log
?找到 ?HEAD
,HEAD
?告訴它去 ?test
?分支,test
?分支指向著 ?提交 ?465...5e
,這個提交又鏈接到它的父 ?提交 ?80e...8f
。
盡情欣賞美吧,we ?git ?you。????
總結
本文向你介紹了 ?git
?的內部原理,我們一開始講了基本對象——blob、樹對象 ?和 ?提交對象 ?。
我們了解到 ?blob ?持有文件的內容,樹對象 ?是一個包含 ?blob 對象 ?和 ?子樹對象 ?的目錄列表,提交對象 ?是工作目錄的一個快照,包含了一些像時間或提交信息這樣的元數據。
我們接著討論了 ?分支,它們不過是 ?提交對象 ?的命名引用。
我們繼續描述了 ?工作目錄,它是一個目錄,有著相應的倉庫。暫存區(索引) ?為下一個 ?提交對象 ?持有對應的 ?樹對象,而倉庫就是一個 ?提交對象 ?的集合。
我們闡明了這些術語與 ?git init
、git add
?和 ?git commit
?之間的關系,我們用這幾條著名的命令創建新倉庫、提交文件。
然后,我們大膽地深入 ?git
?內部,停止使用上層命令,轉而使用底層命令。
借助 ?echo
?和 ?git bash-object
?這類的底層命令,我們創建了 ?blob,把它添加到 ?索引,創建了 ?索引 ?的 ?樹對象,以及指向這個 ?樹對象 ?的 ?提交對象。
我們還創建了 ?分支,在 ?分支 ?間來回切換。為你們中那些親身嘗試這個過程的人鼓個掌!????
希望你在跟著本文操作一遍之后,對使用 ?git
?過程中背后發生的事情有了更深入的理解。
感謝閱讀本文! ?如果你喜歡這篇文章,你可以在?swimm.io blog 這個主題的內容。
Omer Rosenbaum ?是 ?Swimm ?的首席技術官、網絡培訓專家、Checkpoint 安全學院的創始人和計算機網絡(希伯來語)的作者。
歡迎訪問我的 YouTube 頻道。
附加資源
git
?相關的資源已經有的很多了,我發現下面這些參考資料特別有用:
Git Internals YouTube playlist?—?by Brief
Tim Berglund’s lecture?—?“Git From the Bits Up”
Git from the Bottom Up?—?by John Wiegley
as promised, docs: git for the confused
Git Internals?—?Git Objects?—?from Pro Git book, by Scott Chacon and Ben Straub
原文鏈接:https://www.freecodecamp.org/news/git-internals-objects-branches-create-repo/
作者:Omer Rosenbaum
譯者:Nicholas Zhan
點擊 ??????加入 freeCodeCamp Chat 中文開發者社區聊天室
非營利組織 freeCodeCamp.org 自 2014 年成立以來,以“幫助人們免費學習編程”為使命,創建了大量免費的編程教程,包括交互式課程、視頻課程、文章等。我們正在幫助全球數百萬人學習編程,希望讓世界上每個人都有機會獲得免費的優質的編程教育資源,成為開發者或者運用編程去解決問題。
你也想成為
freeCodeCamp 社區的貢獻者嗎
歡迎點擊以下文章了解
??
freeCodeCamp 在線翻譯工作坊丨學編程,練英語,成為開源貢獻者
成為 freeCodeCamp 專欄作者,與世界各地的開發者分享技術知識
點擊“閱讀原文”
在?freeCodeCamp 專欄
推薦閱讀
若川知乎高贊:有哪些必看的 JS庫?
我在阿里招前端,我該怎么幫你?(現在還可以加模擬面試群)
如何拿下阿里巴巴 P6 的前端 Offer
如何準備阿里P6/P7前端面試--項目經歷準備篇
大廠面試官常問的亮點,該如何做出?
如何從初級到專家(P4-P7)打破成長瓶頸和有效突破
若川知乎問答:2年前端經驗,做的項目沒什么技術含量,怎么辦?
末尾
你好,我是若川,江湖人稱菜如若川,歷時一年只寫了一個學習源碼整體架構系列~(點擊藍字了解我)
關注
若川視野
,回復"pdf" 領取優質前端書籍pdf,回復"1",可加群長期交流學習我的博客地址:https://lxchuan12.gitee.io?歡迎收藏
覺得文章不錯,可以點個
在看
呀^_^另外歡迎留言
交流~
精選前端好文,伴你不斷成長
若川原創文章精選!可點擊
小提醒:若川視野公眾號面試、源碼等文章合集在菜單欄中間
【源碼精選】
按鈕,歡迎點擊閱讀,也可以星標我的公眾號,便于查找