Git 存儲原理及相關實現

Git 是目前最流行的版本控制系統,從本地開發到生產部署,我們每天都在使用 Git 進行我們的版本控制,除了日常使用的命令之外,如果想要對 Git 有更深一步的了解,那么研究下 Git 的底層存儲原理將會對理解 Git 及其使用非常有幫助,就算你不是一個 Git 開發者,也推薦你了解下 Git 的底層原理,你會對 Git 的強大有一個全新的認識,并且將會在日常的 Git 使用過程中更加得心應手。

?

這篇文章面向的讀者主要是對 Git 有一定的了解的群體,并不會介紹具體 Git 的作用及其使用,也不會介紹與其它版本控制系統如 Subversion 之間的差異,主要是介紹下 Git 的本質以及他的存儲實現的相關原理,旨在幫助 Git 使用者更加清晰的了解在使用 Git 進行版本控制的時候其內部實現。

?

Git 本質是什么

圖片

?

Git 本質上是一個內容尋址的 Key-Value 數據庫,我們可以向 Git 倉庫內插入任意類型的內容,Git 會返回給我們一個唯一的鍵值,可以通過這個鍵取出當時我們插入的值,我們可以通過底層命令git hash-object命令來嘗試:

 

???Zoker?git:(master)???cat?testfile
Hello?Git
???Zoker?git:(master)???git?hash-object?testfile?-w
9f4d96d5b00d98959ea9960f069585ce42b1349a

可以看到我們目錄下有一個名為testfile的文件,內容是Hello Git! 我們使用git hash-object命令將這個文件的內容寫入到 Git 倉庫,-w 選項告訴 Git 把這個內容寫到 Git 的.git/objects對象數據庫目錄,并且 Git 返回了一個 SHA 值,這個 SHA 值就是后續我們要取出這個文件的鍵值:

 

???Zoker?git:(master)???git?cat-file?-p?9f4d96d5b00d98959ea9960f069585ce42b1349a
Hello?Git

我們使用了git cat-file命令取回剛剛存入到 Git 倉庫的內容,雖然不像 Redis 的命令 get set 那么直觀,但是它確實是一個 KV 數據庫,不是嗎?

?

我們剛剛嘗試插入的這種數據是基礎的blob類型的對象,Git 還有其它如 tree、commit 等對象類型,這些不同的對象類型之間有特定的關聯關系,它們將不同的對象有邏輯的關聯起來,才能夠幫我們進行不同版本的控制和檢出。稍后會展開講解這幾種不同的對象類型,我們先來了解下 Git 的目錄結構,看看在 Git 中數據是如何存放的。

?

Git 目錄結構

圖片

?

通過上一節的介紹,我們知道了 Git 本質就是一個 KV 數據庫,而且還提到了內容都是寫到 .git/objects對象目錄,那么這個目錄放在哪里?Git 又是如何存儲這些數據的呢?本節我們重點介紹一下 Git 的存儲目錄結構,了解下 Git 是如何存放不同類型的數據的。

?

更詳細的介紹參見:https://github.com/git/git/blob/master/Documentation/gitrepository-layout.txt

?

通過 git init 我們可以在當前目錄初始化一個空的 Git 倉庫,Git 會自動生成 .git 目錄,這個 .git 目錄就是后續所有的 Git 元數據的存儲中心,我們來看一下它的目錄結構:

 

???Zoker?git?init
Initialized?empty?Git?repository?in?/Users/zoker/tmp/Zoker/.git/
???Zoker?git:(master)???tree?.git
.git
├──?HEAD??????????????//?是一個符號引用,指明當前工作目錄的版本引用信息,我們平時執行?checkout?命令時就會改變?HEAD?的內容
├── config ????????????//?配置當前存儲庫的一些信息,如:Proxy、用戶信息、引用等,此處的配置項相對于全局配置權重更高
├──?description??????//?倉庫描述信息
├──?hooks?????????????//?鉤子目錄,執行?Git?相關命令后的回調腳本,默認會有一些模板
│???├──?update.sample
│???├──?pre-receive.sample
│???└──?...
├──?info????????????????//?存儲一些額外的倉庫信息如?refs、exclude、attributes?等
│???└──?exclude
├──?objects???????????//?元數據存儲中心
│???├──?info
│???└──?pack
└──?refs???????????????//?存放引用信息,也就是分支、標簽
????├──?heads
????└──?tags

默認初始化生成的 Git 倉庫就只有這些文件,除此之外還存在一些其它類型的文件和目錄如packed-refs modules logs等,這些文件都有特定的用途,都是在特定的操作或者配置后才會出現,這里我們只關注核心存儲的實現,這些額外文件或目錄的作用及使用場景再可自行翻閱文檔,這里僅介紹核心的一些文件。

?

hooks 目錄

?

hooks 目錄主要存儲的是 Git 鉤子,Git 鉤子可以在很多事件發生后或者發生前觸發,能夠提供給我們非常靈活的使用方式,默認情況下全部都是帶.sample后綴的,需要移除這個后綴并賦予可執行權限方可生效,下面列舉下常用的一些鉤子及其常見的用途:

?

客戶端鉤子:

?

  • pre-commit:提交前觸發,比如檢查提交信息是否規范,測試是否運行完畢,代碼格式是否符合要求

  • post-commit:相反,這個是整個提交完成后觸發,可以用來發通知

?

服務端鉤子:

?

  • pre-receive:服務端接收推送請求首先被調用的腳本,可以檢測這些被推送的引用是否符合要求

  • update:與 pre-receive 相似,但是 pre-receive 只會運行一次,而 update 將會為每一個推送的分支分別運行一次

  • post-receive:整個推送過程完成后觸發,可以用來發送通知、觸發構建系統等

?

objects 目錄

?

如上一節我們提到的,Git 將所有接收到的內容生成對象文件存儲在這個目錄下,我們通過 git hash-object 生成了一個對象并寫入了 Git 倉庫,這個對象的鍵值是 9f4d96d5b00d98959ea9960f069585ce42b1349a,這個時候我們來查看下 objects 目錄的結構:

 

???Zoker?git:(master)???git?hash-object?testfile?-w
9f4d96d5b00d98959ea9960f069585ce42b1349a
???Zoker?git:(master)???tree?.git/objects
.git/objects
├──?9f
│???└──?4d96d5b00d98959ea9960f069585ce42b1349a
├──?info
└──?pack

可以看到 objects 目錄已經有了新的內容,多了一個 9f 的文件夾以及其中的文件,這個文件就是插入到 Git 倉庫的內容的對象文件,Git 取其鍵值的前兩個字母作為文件夾,將后面的字母作為對象文件的文件名進行存儲,這里(也就是objects/[0-9a-f][0-9a-f])所存儲的對象我們一般稱為 loose objects 或者 unpacked objects,也就是松散對象。

?

除了對象的存儲文件夾,細心的同學應該已經注意到了 objects/pack 文件夾的存在,這里對應的是打包后的文件,為了節省空間和提升效率,當存儲庫中有過多的松散對象文件或者手動執行 git gc 命令時,亦或是推送拉取的傳輸過程中,Git 都會將這些松散的對象文件打包成pack文件來提升效率,這里存放的就是這些打包后的文件:

 

???objects?git:(master)?git?gc
...
Compressing?objects:?100%?(75/75),?done.
...
???objects?git:(master)?tree
.
├─?pack
????├──?pack-fe24a22b0313342a6732cff4759bedb25c2ea55d.idx
????└──?pack-fe24a22b0313342a6732cff4759bedb25c2ea55d.pack
└──?...

可以看到 objects 目錄已經沒有了松散對象,取而代之的是 pack 目錄的兩個文件,一個是打包后的文件,另一個是對這個打包的內容進行索引的 idx 文件,方便查詢某個對象是否在這個對應的 pack 包內。

?

需要注意的是,如果在剛剛我們手動創建的一個 blob 對象的倉庫進行 GC,將不會產生任何效果,因為這個時候整個 Git 倉庫并沒有任何一個引用指向這個對象,我們說這個對象是游離的,下面我們來介紹下存儲引用的目錄。

?

refs 目錄

?

refs 目錄存儲我們的引用(references),引用可以看做是對一個版本號的別名,它存儲的實際就是某一個 Commit 的 SHA 值,上面我們用來測試的倉庫并沒有任何一個提交,所以只有一個空的目錄結構。

 

└──?refs
????├──?heads
????└──?tags

我們隨便找一個包含提交的倉庫查看他的默認分支 master。

 

???.git?git:(master)?cat?refs/heads/master
87e917616712189ecac8c4890fe7d2dc2d554ac6

可以看到這個master的引用只是存儲了一個 Commit 的 SHA 值,好處當然就是我們不需要記著那長長的一串 SHA 值,我們只需要用master這個別名就可以獲取到這個版本。同樣的 tags 目錄下存儲的就是我們的標簽,與分支不同的是,標簽的所記錄的引用值一般是不會變化的,而分支可以我們的版本變化而變化。除此之外,還可能會看到 refs/remotes refs/fetch 等目錄,這些里面存儲的是特定命名空間的引用。

?

還有一種情況,就是上面我們講到的 GC 機制,如果一個倉庫執行了 GC,那么不僅objects目錄下的松散對象會被打包,refs下面的引用同樣也會被打包,只不過它存放在裸倉庫的根目錄下 .git/packed-refs

 

???.git?git:(master)?cat?packed-refs
#?pack-refs?with:?peeled?fully-peeled?sorted
87e917616712189ecac8c4890fe7d2dc2d554ac6?refs/heads/master

當我們需要訪問分支 master 的時候,Git 會首先去 refs/heads 里面進行查找,如果找不到就會前往 .git/packed-refs 進行查找,將所有的引用打包到一個文件無疑提升了不少效率。需要注意的是,如果我們在這個時候往 master 分支上更新了一些提交,這個時候 Git 并不會直接修改 .git/packed-refs文件,它會直接在refs/heads/下重新創建一個master引用,包含最新的提交的 SHA 值,根據剛剛我們介紹的 Git 的機制,Git 會首先在 refs/heads/ 查找,找不到才會去 .git/packed-refs 查找。

?

那么引用里面存儲的 Commit 的這串 SHA 值到底是指向什么內容呢,我們可以使用之前查看 blob 對象內容的 cat-file 命令進行查看:

 

???.git?git:(master)?git?cat-file?-p?87e917616712189ecac8c4890fe7d2dc2d554ac6
tree?aab1a9217aa6896ef46d3e1a90bc64e8178e1662?//?指向的?tree?對象
parent?7d000309cb780fa27898b4d103afcfa95a8c04db?//?父提交
author?Zoker?<kaixuanguiqu@gmail.com>?1607958804?+0800?//?作者信息
committer?Zoker?<kaixuanguiqu@gmail.com>?1607958804?+0800?//?提交者信息

test?ssh?//?提交信息

它是一個 commit 類型的對象,主要的屬性是它指向的 tree 對象,它的父提交(如果它是第一個提交,那么這里是 0000000...),以及作者和提交信息。

?

那么 commit 對象是什么?它所指向的 tree 對象又是什么?與之前我們手工創建的 blob 對象有什么差別?接下來我們來談談 Git 存儲對象。

?

Git 存儲對象

圖片

?

在 Git 的世界里,一共有四種類型的存儲對象:文件(blob)、樹(tree)、提交(commit)、標簽(tag),這里我們主要探討頭三種類型,因為這三種是最基礎的 Git 元數據,而標簽對象只是一個包含了額外屬性信息的 Tag 而已,也就是附注標簽(annotated tag),這里不再過多的介紹。

?

輕量標簽(lightweight)與附注標簽(annotated)介紹:https://git-scm.com/book/zh/v2/Git-基礎-打標簽

?

Blob 對象

?

在介紹 Git 本質的時候,為了演示 Git 是一個基于內容尋址的 KV 數據庫,我們向 Git 倉庫插入了一個文件的內容:

 

???Zoker?git:(master)???cat?testfile
Hello?Git
???Zoker?git:(master)???git?hash-object?testfile?-w
9f4d96d5b00d98959ea9960f069585ce42b1349a

這個 Key 為 9f4d96d5b00d98959ea9960f069585ce42b1349a 的 Git 對象實際上就是一個 Blob 對象,他存儲了這個 testfile 文件的值,我們可以使用 cat-file 命令來進行查看:

 

???Zoker?git:(master)???git?cat-file?-p?9f4d96d5b00d98959ea9960f069585ce42b1349a
Hello?Git

每一次我們修改文件,Git 都會完整的保存一份這個文件的快照而非記錄差異,所以如果我們修改了testfile文件的內容再次存入到 Git 倉庫中的時候,Git 會基于當前最新的內容來生成它的 Key,需要注意的是當內容不變的時候,它的 Key 值是固定的,畢竟我們前面也說了,Git 是一個基于內容尋址的 KV 數據庫。

?

另外,這里的 Blob 對象存儲的是文本內容,它還可以是二進制內容,但是這里并不建議使用 Git 管理二進制文件的版本。我們 Gitee 平臺在日常運營過程中遇到最多的問題就是用戶倉庫過大,這種情況一般都是用戶提交了大的二進制文件導致的,因為每次文件的變更記錄的是快照,所以這個二進制文件如果變更頻繁,它占用的空間是倍增的。而且對于文本內容的 Blob,Git 在 GC 的過程中會只保存兩次提交之間的文件差異,是可以達到節省空間的效果的,但是對于二進制內容的 Blob 是無法像文本內容的 Blob 那樣處理的,所以盡量不要把頻繁變動的二進制內容存儲到 Git 倉庫,可以使用 LFS 的方式進行存儲。如果已經存在了大量的二進制文件,可以使用filter-branch進行瘦身,新加入的同事在首次 Clone 倉庫的時候肯定會感激你的。

?

LFS 的使用:https://gitee.com/help/articles/4235,大倉庫的瘦身:https://gitee.com/help/articles/4232,filter-branch:https://github.com/git/git/blob/master/Documentation/git-filter-branch.txt

?

到了這里是不是覺得哪里不對勁?沒錯,這個 Blob 對象只存儲了這個文件的內容,卻沒有記錄文件名,那我們該怎么知道這個內容是屬于哪個文件的啊?答案是 Git 的另外一個重要的對象:Tree 對象。

?

Tree 對象

?

在 Git 中,Tree 對象主要的作用是將多個 Blob 或者 子 Tree 對象組織到一起,所有的內容都是通過 Tree 和 Blob 類型的對象進行存儲的。一個 Tree 對象包含了一個或者多個 Tree Entry(樹對象記錄),每個樹對象記錄都包含了一個指向 Blob 或者子 Tree SHA 值的指針,還有它們對應的文件名等信息,其實就可以理解為索引文件系統中的 inode 和 block 的關系,圖示一個 Tree 對象的話,如下圖:

?

圖片

?

這個 Tree 對象對應的目錄結構就是下面這樣的:

 

.
├──?LICENSE
├──?readme.md
└──?src
????├──?libssl.so
????└──?logo.png

通過這種方式,我們可以像組織 Linux 下目錄的方式一樣來結構化的存儲我們倉庫的內容,把 Tree 看作目錄結構,把 Blob 看作具體的文件內容。

?

那么該如何創建一個 Tree 對象呢?在 Git 中是根據暫存區的狀態來創建對應的 Tree 對象的,這里的暫存區其實就是我們日常在使用 Git 的過程中所理解的暫存區(Staged),一般我們使用 git add 命令將某些文件添加到暫存區待提交。在沒有任何提交的空倉庫里,這個暫存區的狀態就是你通過 git add 所添加的那些文件,如:

 

???Zoker?git:(master)???git?status
On?branch?master

No?commits?yet

Changes?to?be?committed:
??(use?"git?rm?--cached?<file>..."?to?unstage)
????new?file:???LICENSE
????new?file:???readme.md

Untracked?files:
??(use?"git?add?<file>..."?to?include?in?what?will?be?committed)
????src/

這里當前的暫存區狀態就是在根目錄有兩個文件,暫存區的狀態是保存在 .git/index 文件的,我們使用 file 命令來看看它是什么:

 

???Zoker?git:(master)???file?.git/index
.git/index:?Git?index,?version?2,?2?entries

可以發現在 index 文件中有兩個 entry,也就是根目錄的兩個文件 LICENSE 和 readme.md。對于已經有提交的倉庫,如果暫存區沒有任何內容,那么這個 index 表示的就是當前版本的目錄樹狀態,如果修改或者增刪了文件,并且加入了暫存區,那么 index 就會發生改變,將相關文件的指針指向該文件新的 Blob 對象的 SHA 值。

?

所以如果想要創建一個 Tree 對象,我們需要往暫存區放點東西,除了使用 git add,我們還可以使用底層命令 update-index 來創建一個暫存區。接下來我們根據上面已經創建好的 testfile 文件來創建一個樹對象,首先就是將文件 testfile 加入到暫存區:

 

???Zoker?git:(master)???git?update-index?--add?testfile?//?與?git?add?testfile?一樣
???Zoker?git:(master)???git?status
On?branch?master

No?commits?yet

Changes?to?be?committed:
??(use?"git?rm?--cached?<file>..."?to?unstage)
????new?file:???testfile

這個過程 Git 主要是先把testfile的內容以 Blob 的形式插入到 Git 倉庫,然后將返回的這個 Blob 的 SHA 值記錄到index中,告訴暫存區目前這個文件的內容是哪個。

 

???Zoker?git:(master)???tree?.git/objects
.git/objects
├──?9f
│???└──?4d96d5b00d98959ea9960f069585ce42b1349a
├──?info
└──?pack

3?directories,?1?file
???Zoker?git:(master)???git?cat-file?-p?9f4d96d5b00d98959ea9960f069585ce42b1349a
Hello?Git

Git 在執行update-index命令的時候,把指定文件的內容存儲為 Blob 對象,并且記錄在index文件狀態內。由于在之前我們已經通過 git hash-object 命令將這個文件的內容插入過了,并且我們可以發現因為內容不變,所以生成的這個 Blob 對象的 SHA 值也是一致的,如果像我們這樣已經做過插入的動作,下面的命令是等效的:

 

git?update-index?--add?--cacheinfo?9f4d96d5b00d98959ea9960f069585ce42b1349a?testfile

這個命令其實就是把之前已經生成的 Blob 對象放到暫存區,并且指定它的文件名字是 testfile。由于我們的暫存區已經有一個文件 testfile,所以我接下來我們可以使用 git write-tree 命令來基于當前暫存區的狀態來創建一個 Tree 對象了:

 

???Zoker?git:(master)???git?write-tree
aa406ee8804971cf8edfd8c89ff431b0462e250c
???Zoker?git:(master)???tree?.git/objects
.git/objects
├──?9f
│???└──?4d96d5b00d98959ea9960f069585ce42b1349a
├──?aa
│???└──?406ee8804971cf8edfd8c89ff431b0462e250c
├──?info
└──?pack

執行完命令后,Git 會基于當前暫存區的狀態生成一個 SHA 值為aa406ee8804971cf8edfd8c89ff431b0462e250c的 Tree 對象,并把這個 Tree 對象像 Blob 對象一樣存儲在.git/objects目錄下。

 

???Zoker?git:(master)???git?cat-file?-p?aa406ee8804971cf8edfd8c89ff431b0462e250c
100644?blob?9f4d96d5b00d98959ea9960f069585ce42b1349a????testfile

使用 cat-file 命令查看這個 Tree 對象,可以看到這個對象下只有一個文件,名為testfile。

?

圖片

?

我們繼續創建第二個 Tree 對象,我們需要第二個 Tree 對象下有修改后的testfile文件,有新增的 testfile2文件,并且需要把第一個 Tree 對象作為 第二個 Tree 對象的duplicate目錄。首先我們先把修改后的testfile和新增的testfile2文件加入到暫存區:

 

???Zoker?git:(master)???git?update-index?testfile
???Zoker?git:(master)???git?update-index?--add?testfile2
???Zoker?git:(master)???git?status
On?branch?master

No?commits?yet

Changes?to?be?committed:
??(use?"git?rm?--cached?<file>..."?to?unstage)
????new?file:???testfile
????new?file:???testfile2

緊接著我們需要把第一個 Tree 對象掛到 duplicate 目錄下,我們可以使用 read-tree 命令來實現:

 

???Zoker?git:(master)???git?read-tree?--prefix=duplicate?aa406ee8804971cf8edfd8c89ff431b0462e250c?
???Zoker?git:(master)???git?status
On?branch?master

No?commits?yet

Changes?to?be?committed:
??(use?"git?rm?--cached?<file>..."?to?unstage)
????new?file:???duplicate/testfile
????new?file:???testfile
????new?file:???testfile2

然后我們執行 write-tree 并通過 cat-file 查看第二個 Tree 對象:

 

???Zoker?git:(master)???git?write-tree
64d62cef754e6cc995ed8d34f0d0e233e1dfd5d1
???Zoker?git:(master)???git?cat-file?-p?64d62cef754e6cc995ed8d34f0d0e233e1dfd5d1
040000?tree?aa406ee8804971cf8edfd8c89ff431b0462e250c????duplicate
100644?blob?106287c47fd25ad9a0874670a0d5c6eacf1bfe4e????testfile
100644?blob?098ffe6f84559f4899edf119c25d276dc70607cf????testfile2

成功完成了,我們不僅修改了 testfile 的文件內容,還新增了一個 testfile2 文件,并且還把第一個 Tree 對象當作第二個 Tree 對象的 duplicate 目錄了,這個時候 Tree 對象看起來應該是這樣的:

?

圖片

?

至此,我們知道了如何手動創建一個 Tree 對象,但是后面如果我需要這兩個不同的 Tree 的快照該怎么辦?總不能都記住這三個 Tree 對象的 SHA 值吧?沒錯,記起來費老大勁了,關鍵是還不知道是誰在什么時間為了什么而創建的這個快照,而 Commit 對象(提交對象)就能夠幫我們解決這個問題。

?

Commit 對象

?

Commit 對象主要是為了記錄快照的一些附加信息,并且維護快照之間的線性關系。我們可以通過git commit-tree命令來創建一個提交,這個命令看字面意思就知道,它是用來將 Tree 對象提交為一個 Commit 對象的命令:

 

???Zoker?git:(master)???git?commit-tree?-h
usage:?git?commit-tree?[(-p?<parent>)...]?[-S[<keyid>]]?[(-m?<message>)...]?[(-F?<file>)...]?<tree>

????-p?<parent>???????????id?of?a?parent?commit?object
????-m?<message>??????????commit?message
????-F?<file>?????????????read?commit?log?message?from?file
????-S,?--gpg-sign[=<key-id>]
??????????????????????????GPG?sign?commit

關鍵的兩個參數是 -p 和 -m,-p 是指定這個提交的父提交,如果是初始的第一個提交,那這里可以忽略;-m 則是指定本次提交的信息,主要是用來描述提交的原因。我們來把第一個 Tree 對象作為我們的初始提交:

 

???Zoker?git:(master)???git?commit-tree?-m?"init?commit"?aa406ee8804971cf8edfd8c89ff431b0462e250c
17ae181bd6c3e703df7851c0f7ea01d9e33a675b

使用cat-file來查看這個提交:

 

tree?aa406ee8804971cf8edfd8c89ff431b0462e250c
author?Zoker?<kaixuanguiqu@gmail.com>?1613225370?+0800
committer?Zoker?<kaixuanguiqu@gmail.com>?1613225370?+0800

init?commit

Commit 所存儲的內容是一個 Tree 對象,并且記錄了提交者、提交時間以及提交信息。我們基于這個 Commit 將第二個 Tree 對象作為引用:

 

???Zoker?git:(master)???git?commit-tree?-p?17ae181bd?-m?"add?dir"?64d62cef754e6cc995ed8d34f0d0e233e1dfd5d1
de96a74725dd72c10693c4896cb74e8967859e58
???Zoker?git:(master)???git?cat-file?-p?de96a74725dd72c10693c4896cb74e8967859e58
tree?64d62cef754e6cc995ed8d34f0d0e233e1dfd5d1
parent?17ae181bd6c3e703df7851c0f7ea01d9e33a675b
author?Zoker?<kaixuanguiqu@gmail.com>?1613225850?+0800
committer?Zoker?<kaixuanguiqu@gmail.com>?1613225850?+0800

add?dir

我們可以使用 git log 來查看這兩個提交,這里添加 --stat 參數查看文件變更記錄:

 

commit?de96a74725dd72c10693c4896cb74e8967859e58
Author:?Zoker?<kaixuanguiqu@gmail.com>
Date:???Sun?Feb?13?22:17:30?2021?+0800

????add?dir

?duplicate/testfile?|?1?+
?testfile???????????|?2?+-
?testfile2??????????|?1?+
?3?files?changed,?3?insertions(+),?1?deletion(-)

commit?17ae181bd6c3e703df7851c0f7ea01d9e33a675b
Author:?Zoker?<kaixuanguiqu@gmail.com>
Date:???Sun?Feb?13?22:09:30?2021?+0800

????init?commit

?testfile?|?1?+
?1?file?changed,?1?insertion(+)

這個時候整個對象的結構如下圖:

?

圖片

?

練習:使用底層命令創建一個提交

?

僅使用我們上面提到的hash-object write-tree read-tree commit-tree等底層命令來創建一個提交,思考哪些過程是與git add git commit等價的。

?

對象存儲方式

?

我們通過前面的介紹,知道了 Git 是將數據以不同的對象類型歸納,并且根據內容計算出一個 SHA 值用來作為尋址,那么到底是如何計算的呢?以 Blob 對象為例,Git 主要是做了如下幾步:

?

  • 識別對象的類型,構造頭部信息,以類型 + 內容字節數 + 空字節作為頭部信息如 blob 151\u0000

  • 將頭部信息與內容拼接,并且計算 SHA-1 校驗和

  • 通過 zlib 壓縮內容

  • 通過 SHA 值將其內容放到對應的 objects 目錄

?

整個過程就做了這些事情,Tree 對象和 Commit 對象也差不多,只是頭部類型有所差異而已,這里不再贅述,《Pro Git 2》在 Git 內部原理章節中有介紹如何使用 Ruby 來實現同等的邏輯,感興趣的可以自行翻閱。

?

Git-內部原理:https://git-scm.com/book/zh/v2/Git-內部原理-Git-對象

?

Git 引用

圖片

?

我們在上面通過 git log --stat 17ae181b 能夠查看第一個版本的相關信息,并且可以通過這串 SHA 值拿到這個快照的內容,但是還是挺麻煩的,因為我們要記住一串毫無意義的字符串,這個時候 Git 的引用就派上用場了,在 Git 目錄結構章節我們已經介紹了refs目錄,我們知道在引用中存儲的就是 Commit 對象的鍵值,也就是這個對象的 SHA 值,既然如此,我們就給我們當前的版本起一個有意義的名字,一般我們會拿master作為默認分支引用:

 

???Zoker?git:(master)???echo?"17ae181bd6c3e703df7851c0f7ea01d9e33a675b"?>>?.git/refs/heads/master
???Zoker?git:(master)???tree?.git/refs
.git/refs
├──?heads
│???└──?master
└──?tags

這個時候,master 里面存儲了我們的第一個 Commit 的 SHA 值,我們可以使用 master 來代替 17ae181b 這串毫無意義的字符串了。

 

???Zoker?git:(master)???git?cat-file?-p?master
tree?aa406ee8804971cf8edfd8c89ff431b0462e250c
author?Zoker?<kaixuanguiqu@gmail.com>?1613916447?+0800
committer?Zoker?<kaixuanguiqu@gmail.com>?1613916447?+0800

init?commit

但是,這個并不是我們最新的版本,我們最新的版本是第二個提交 de96a74725dd72c10693c4896cb74e8967859e58,同樣的,我們可以把refs/heads/master的內容更改為這個提交的 SHA 值,但是這里我們使用一個底層命令來完成。

 

???Zoker?git:(master)???git?update-ref?refs/heads/master?de96a74725dd72c10693c4896cb74e8967859e58
???Zoker?git:(master)???cat?.git/refs/heads/master
de96a74725dd72c10693c4896cb74e8967859e58

這個時候,分支 master 就指向了我們最新的版本。

?

圖片

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/535424.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/535424.shtml
英文地址,請注明出處:http://en.pswp.cn/news/535424.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Git內部原理

Git有什么特點&#xff1f; fast&#xff0c;scalable&#xff0c;distributed revision control system&#xff08;快速&#xff0c;可擴展的分布式版本控制系統&#xff09; 幾乎所有操作都是本地執行 每一個clone都是整個生命周期的完整副本 the stupid content tracker&a…

git存儲原理

四種數據類型 實際上Git基于數據類型的不同&#xff0c;把對象分為四種&#xff1a;數據對象、樹對象、提交對象、標簽對象。Git文件系統的設計思路與linux文件系統相似&#xff0c;即將文件的內容與文件的屬性分開存儲&#xff0c;文件內容以“裝滿字節的袋子”存儲在文件系統…

詳解設計模式:中介者模式

中介者模式&#xff08;Mediator Pattern&#xff09;也被稱為調停者模式&#xff0c;是在 GoF 23 種設計模式中定義了的行為型模式。 中介者模式 是用來降低多個對象和類之間的通信復雜性。這種模式提供了一個中介類&#xff0c;該類通常處理不同類之間的通信&#xff0c;并支…

rebase參數以及注意事項

可以根據需要將pick參數&#xff0c;改變為下面代表不同作用的參數&#xff1b;這樣就可以對節點C和D進行不同的操作了。比如&#xff1a; pick&#xff1a;默認參數&#xff0c;表示不對提交節點進行任何操作&#xff0c;直接應用原提交節點。不創建新提交&#xff1b; rewor…

RPC 服務 與 HTTP 服務的區別

1、什么是RPC RPC&#xff08;Remote Procedure Call&#xff09;—遠程過程調用&#xff0c;它是一種通過網絡從遠程計算機程序上請求服務&#xff0c;而不需要了解底層網絡技術的協議。RPC協議假定某些傳輸協議的存在&#xff0c;如TCP或UDP&#xff0c;為通信程序之間攜帶信…

Docker 網絡命名空間

Docker 用戶可以通過與 CNM 的 Object 以及 API 的交互來管理對應容器的網絡&#xff0c;下面是一個典型的容器網絡生命周期&#xff1a; 1、Driver要向NetworkController注冊。內置的Driver在Libnetwork內注冊&#xff0c;遠程的Driver則通過Plugin mechanism注冊。每一個Driv…

緩存雪崩、擊穿、穿透解決方案

用戶的數據一般都是存儲于數據庫&#xff0c;數據庫的數據是落在磁盤上的&#xff0c;磁盤的讀寫速度可以說是計算機里最慢的硬件了。 當用戶的請求&#xff0c;都訪問數據庫的話&#xff0c;請求數量一上來&#xff0c;數據庫很容易就奔潰的了&#xff0c;所以為了避免用戶直…

Ansible中的playbook詳解

首先簡單說明一下playbook&#xff0c;playbook是什么呢&#xff1f; 根本上說playbook和shell腳本沒有任何的區別&#xff0c;playbook就像shell一樣&#xff0c;也是把一堆的命令組合起來&#xff0c;然后加入對應條件判斷等等&#xff0c;在shell腳本中是一條一條的命令&am…

【Docker】容器鏡像有哪些特性

首先解釋一下什么是Docker鏡像&#xff1f; Docker鏡像它其實是一個模板&#xff0c;擁有這個模板我們才能創建我們的Docker容器&#xff0c;鏡像里含有啟動 docker 容器所需的文件系統結構及其內容&#xff0c;因此是啟動一個 docker 容器的基礎。docker 鏡像的文件內容以及一…

nginx中的location指令

1、location 介紹 location是Nginx中的塊級指令(block directive)&#xff0c;location指令的功能是用來匹配不同的url請求&#xff0c;進而對請求做不同的處理和響應&#xff0c;這其中較難理解的是多個location的匹配順序&#xff0c;本文會作為重點來解釋和說明。 開始之前…

容器底層實現技術Namespace/Cgroup

Docker容器實現原理 Docker容器在實現上是通過namespace技術來進行進程隔離&#xff0c;通過cgroup技術實現容器進程可用資源的限制&#xff0c;當docker啟動一個容器時&#xff0c;實際是創建了多了namespace參數的進程。 Namespace Namespace&#xff1a;命名空間 作用&#…

身體原因 斷更一周

由于眾所周知的原因&#xff0c;博主最近具有發熱、全身乏力、酸痛、干咳等癥狀&#xff0c;已嚴重影響日常的工作學習和博客編寫&#xff0c;所以斷更我將一周&#xff0c;由下周三&#xff08;即2022年12月21日&#xff09;恢復更新 更多往期內容可以參考&#xff1a;全網最…

異方差與多重共線性對回歸問題的影響

異方差的檢驗 1.異方差的畫圖觀察 2.異方差的假設檢驗&#xff0c;假設檢驗有兩種&#xff0c;一般用懷特檢驗使用方法在ppt中&#xff0c;課程中也有實驗&#xff0c;是一段代碼。 異方差的解決辦法 多重共線性 多重共線性可能帶來的影響&#xff1a; 多重共線性的檢驗 多重…

如何修改Docker的鏡像源

改或新增/etc/docker/daemon.json 文件 vi/etc/docker/daemon.json 添加需要修改的國內鏡像源鏡像源 { "registry-mirrors":["http://hub-mirror.c.163.com"] } 重啟Docker服務 Systemctl restart docker.service 方法二 修改或新增 /etc/sysconfig…

nginx 的 rewrite 模塊

ngxhttprewrite_module 模塊用來使用正則表達式&#xff08;PCRE&#xff09;改變請求的 URI&#xff0c;返回重定向&#xff0c;并有條件地選擇配置。 指令執行順序 首先順序執行 server 塊中的 rewrite 模塊指令&#xff0c;得到 rewrite 后的請求 URI 然后循環執行如下指令…

所有的Python庫

庫名稱簡介 Chardet字符編碼探測器&#xff0c;可以自動檢測文本、網頁、xml的編碼。 colorama主要用來給文本添加各種顏色&#xff0c;并且非常簡單易用。 Prettytable主要用于在終端或瀏覽器端構建格式化的輸出。 difflib&#xff0c;[Python]標準庫&#xff0c;計算文本…

Oracle行轉列語法總結大全

一、decode語法 SELECT deptno, nvl(SUM(decode(job, MANAGER, sal)), 0) s_MANAGER, nvl(SUM(decode(job, ANALYST, sal)), 0) s_ANALYST, nvl(SUM(decode(job, CLERK, sal)), 0) s_CLERK, nvl(SUM(decode(job, PRESIDENT, sal)), 0) s_PRESIDENT, …

Shell 各種符號 之 含義

#!&#xff1a;符號能夠被內核識別成是一個腳本的開始&#xff0c;這一行必須位于腳本的首行 $0&#xff1a;當前腳本的名字 $#&#xff1a;輸入<調用>參數(腳本或函數的位置參數) 的個數&#xff0c;如 NumArg$#&#xff1b;echo"\$#: $#;\$NumArg: $NumArg"…

Nginx的11個執行流程

1 Nginx簡介 Web服務器市場份額 Nginx [engine x] 最初由 Lgor Sysoev 編寫。根據 Netcraft 的數據&#xff0c;到2020年9月&#xff0c;Nginx 服務或代理了25.76&#xff05;站點&#xff0c;市場份額占到了約34.03&#xff05;。 Nginx 被廣泛用作&#xff1a; HTTP服務器…

Nginx的執行階段詳解

在了解nginx的執行階段前&#xff0c;先看一個例子 對echo不熟悉的&#xff0c;可以先看文章Nginx調試必備了解下echo擴展 回到上面這個例子&#xff0c;在server塊中配置這樣的location&#xff0c;你覺得輸出是什么樣子&#xff1f; 按照正常的邏輯&#xff0c;輸出應該是32 …