Git本質(zhì)上是一個(gè)內(nèi)容尋址(content-addressable)的文件系統(tǒng),根據(jù)文件內(nèi)容的SHA-1哈希值來(lái)定位文件。Git核心部分是一個(gè)簡(jiǎn)單的鍵值對(duì)數(shù)據(jù)庫(kù)(key-value data store)。向Git數(shù)據(jù)庫(kù)插入任意類型的內(nèi)容,會(huì)返回一個(gè)鍵值,通過(guò)返回的鍵值可以在任意時(shí)刻再次檢索(retrieve)插入的內(nèi)容。通過(guò)底層命令hash-object可以將任意數(shù)據(jù)保存到.git目錄并返回相應(yīng)的鍵值。
Git包含一套面向版本控制系統(tǒng)的工具集,包括高級(jí)命令和底層命令。高級(jí)命令主要由用戶使用,底層命令可以窺探Git內(nèi)部的工作機(jī)制,但多數(shù)底層命令并不面向最終用戶,更適合作為新命令和自定義腳本的組成部分。
使用git init創(chuàng)建倉(cāng)庫(kù)時(shí),Git會(huì)創(chuàng)建一個(gè).git目錄,其目錄結(jié)構(gòu)如下:
A、description文件僅供GitWeb程序使用。
B、config文件包含項(xiàng)目特有的配置選項(xiàng)。
C、info目錄包含一個(gè)全局性排除(global exclude)文件,用于放置不希望被記錄在.gitignore文件中的忽略模式(ignored patterns)。
D、hooks目錄包含客戶端或服務(wù)端的鉤子腳本(hook scripts)。
E、objects目錄存儲(chǔ)所有數(shù)據(jù)內(nèi)容,內(nèi)有info、pack子目錄。
F、refs目錄存儲(chǔ)指向數(shù)據(jù)(分支)的提交對(duì)象的指針。
G、HEAD文件指示目前被檢出的分支。
H、index(尚待創(chuàng)建)文件保存暫存區(qū)信息。
objects、refs、HEAD、index是Git倉(cāng)庫(kù)的四個(gè)核心部分。
成都創(chuàng)新互聯(lián)公司是一家專注于成都網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站與策劃設(shè)計(jì),曹妃甸網(wǎng)站建設(shè)哪家好?成都創(chuàng)新互聯(lián)公司做網(wǎng)站,專注于網(wǎng)站建設(shè)十余年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:曹妃甸等地區(qū)。曹妃甸做網(wǎng)站價(jià)格咨詢:18980820575
Git對(duì)象分為四種:數(shù)據(jù)對(duì)象(blob)、樹對(duì)象(tree)、提交對(duì)象(commit)、標(biāo)簽對(duì)象(tag)。Git文件系統(tǒng)的設(shè)計(jì)思路與linux文件系統(tǒng)相似,即將文件的內(nèi)容與文件的屬性分開存儲(chǔ),文件內(nèi)容存儲(chǔ)在文件系統(tǒng)中,文件名、所有者、權(quán)限等文件屬性信息則另外開辟區(qū)域進(jìn)行存儲(chǔ)。
Git利用SHA-1加密算法對(duì)其管理的每一個(gè)文件生成一個(gè)唯一的16進(jìn)制的40個(gè)字符長(zhǎng)度的SHA-1哈希值來(lái)唯一標(biāo)識(shí)對(duì)象。如果文件不變化,SHA-1哈希值不會(huì)改變;如果文件改變,會(huì)生成新的SHA-1哈希值。40位字符SHA-1哈希值的前兩個(gè)字符作為目錄名,后38個(gè)字符作為文件名,標(biāo)識(shí)生成的Git對(duì)象。
Git對(duì)象的SHA-1哈希值計(jì)算公式如下:
header = " " + content.length + "\0"
hash = sha1(header + content)
Git在計(jì)算對(duì)象hash時(shí),首先會(huì)在對(duì)象頭部添加一個(gè)header。header由3部分組成:第一部分表示對(duì)象的類型,可以取值blob、tree、commit以分別表示數(shù)據(jù)對(duì)象、樹對(duì)象、提交對(duì)象;第二部分是數(shù)據(jù)的字節(jié)長(zhǎng)度;第三部分是一個(gè)空字節(jié),用來(lái)將header和content分隔開。將header添加到content頭部后,使用sha1算法計(jì)算出一個(gè)40位的hash值。
在手動(dòng)計(jì)算Git對(duì)象的hash時(shí)需要注意:
A、header中第二部分關(guān)于數(shù)據(jù)長(zhǎng)度的計(jì)算,一定是字節(jié)的長(zhǎng)度而不是字符串的長(zhǎng)度;
B、header + content的操作并不是字符串級(jí)別的拼接,而是二進(jìn)制級(jí)別的拼接。
各種Git對(duì)象的hash方法相同,不同的在于:
A、頭部類型不同,數(shù)據(jù)對(duì)象是blob,樹對(duì)象是tree,提交對(duì)象是commit;
B、數(shù)據(jù)內(nèi)容不同,數(shù)據(jù)對(duì)象的內(nèi)容可以是任意內(nèi)容,而樹對(duì)象和提交對(duì)象的內(nèi)容有固定的格式。
git cat-file可以用來(lái)實(shí)現(xiàn)所有Git對(duì)象的讀取,包括數(shù)據(jù)對(duì)象、樹對(duì)象、提交對(duì)象的查看。
git cat-file -p [hash-key] 可以查看已經(jīng)存在的object對(duì)象內(nèi)容
git cat-file -t [hash-key] 可以查看已經(jīng)存在的object對(duì)象類型
數(shù)據(jù)對(duì)象通常用于存儲(chǔ)文件的內(nèi)容,但不包括文件名、權(quán)限等信息。數(shù)據(jù)對(duì)象和其對(duì)應(yīng)文件的所在路徑、文件名是否改被更改都完全沒(méi)有關(guān)系。
Git會(huì)根據(jù)文件內(nèi)容計(jì)算出一個(gè)SHA-1 hash值,以hash值作為索引將文件存儲(chǔ)在Git文件系統(tǒng)中。由于相同的文件內(nèi)容的hash值是一樣的,因此Git將相同內(nèi)容的文件只會(huì)存儲(chǔ)一次。git hash-object命令可以用來(lái)計(jì)算文件內(nèi)容的hash值,并將生成的數(shù)據(jù)對(duì)象存儲(chǔ)到Git文件系統(tǒng)中。echo -en "hello,git" | git hash-object --stdin
f28ffa36cdf69904e516babfdb3005e108dddfb7
在echo后面使用-n選項(xiàng),用來(lái)阻止自動(dòng)在字符串末尾添加換行符,否則會(huì)導(dǎo)致實(shí)際傳給git hash-object是hello,git
數(shù)據(jù)對(duì)象查看:
git show + 對(duì)象名(SHA1哈希值)
數(shù)據(jù)對(duì)象的內(nèi)容格式如下:
blob
使用git hash-object計(jì)算文本的SHA1哈希值echo -en "hello,git" | git hash-object --stdin
f28ffa36cdf69904e516babfdb3005e108dddfb7
使用openssl計(jì)算文本的SHA1哈希值:echo -en "blob 9\0hello,git" | openssl sha1
(stdin)= f28ffa36cdf69904e516babfdb3005e108dddfb7
如果文本中有中文時(shí),必須注意數(shù)據(jù)長(zhǎng)度的計(jì)算是字節(jié)數(shù)而不是字符數(shù)??梢允褂妹畈榭次谋镜淖止?jié)數(shù):echo -n "中文" | wc -c
git init Test //初始化一個(gè)版本庫(kù)
cd Test //進(jìn)入Test
find .git/objects //查找.git/objects目錄下的內(nèi)容
Git對(duì)objects目錄進(jìn)行初始化,并創(chuàng)建pack和info目錄,但均為空。echo 'test content' | git hash-object -w --stdin //向Git數(shù)據(jù)庫(kù)存入文本
d670460b4b4aece5915caf5c68d12f560a9fe3e4 //返回的鍵值
-w選項(xiàng)指示hash-object命令存儲(chǔ)數(shù)據(jù)對(duì)象;若不指定此選項(xiàng),則上述命令僅返回對(duì)應(yīng)的鍵值。
--stdin選項(xiàng)則指示上述命令從標(biāo)準(zhǔn)輸入讀取內(nèi)容,若不指定此選項(xiàng),則須在命令尾部給出待存儲(chǔ)文件的路徑。
命令輸出一個(gè)長(zhǎng)度為40個(gè)字符的校驗(yàn)和,是一個(gè)SHA-1哈希值,一個(gè)將待存儲(chǔ)的數(shù)據(jù)外加一個(gè)頭部信息(header)一起做SHA-1校驗(yàn)運(yùn)算而得的校驗(yàn)和。find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
可以在objects目錄下看到一個(gè)文件。 Git存儲(chǔ)內(nèi)容的方式是一個(gè)文件對(duì)應(yīng)一條內(nèi)容,用內(nèi)容加上特定頭部信息一起的SHA-1校驗(yàn)和為文件命名。校驗(yàn)和的前兩個(gè)字符用于命名子目錄,余下的38個(gè)字符則用作文件名。
可以通過(guò)cat-file命令從Git數(shù)據(jù)庫(kù)取回?cái)?shù)據(jù)。指定-p選項(xiàng)可指示cat-file命令自動(dòng)判斷內(nèi)容的類型,并顯示格式友好的內(nèi)容:git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
通過(guò)對(duì)一個(gè)文件進(jìn)行簡(jiǎn)單的版本控制揭示Git版本控制的原理。首先,創(chuàng)建一個(gè)新文件并將其內(nèi)容存入Git數(shù)據(jù)庫(kù)。
echo "version 1" > test //寫入test文件內(nèi)容
git hash-object -w test //存儲(chǔ)test文件到Git數(shù)據(jù)庫(kù)
83baae61804e65cc73a7201a7252750c76066a30
echo 'version 2' > test //寫入test文件新的內(nèi)容
git hash-object -w test //再次將修改后的test文件存儲(chǔ)到Git數(shù)據(jù)庫(kù)
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
Git數(shù)據(jù)庫(kù)記錄了test文件的兩個(gè)不同版本。find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
恢復(fù)test文件到第一個(gè)版本:
git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test
cat test //讀取test文件內(nèi)容
version 1
恢復(fù)test文件到第二個(gè)版本:
git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test
cat test //讀取test文件內(nèi)容
version 2
上述對(duì)文件的版本控制中,記住文件的每一個(gè)版本所對(duì)應(yīng)的SHA-1值并不現(xiàn)實(shí),并且文件名并沒(méi)有被保存。
利用cat-file -t命令,可以查看Git內(nèi)部存儲(chǔ)的任何對(duì)象類型,只要給定對(duì)象的SHA-1值。git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob
通過(guò)每一個(gè)數(shù)據(jù)對(duì)象的hash值,可以訪問(wèn)Git文件系統(tǒng)中的任意數(shù)據(jù)對(duì)象,但記住數(shù)據(jù)對(duì)象的SHA-1哈希值顯然是不現(xiàn)實(shí)的。數(shù)據(jù)對(duì)象只是解決了文件內(nèi)容存儲(chǔ)的問(wèn)題,而文件名的存儲(chǔ)則需要通過(guò)樹對(duì)象實(shí)現(xiàn)。
樹對(duì)象包含指向數(shù)據(jù)對(duì)象或是其它樹對(duì)象的多個(gè)指針,用來(lái)表示內(nèi)容之間的目錄層次關(guān)系。
Git所有內(nèi)容均以樹對(duì)象和數(shù)據(jù)對(duì)象的形式存儲(chǔ),其中樹對(duì)象對(duì)應(yīng)UNIX中的目錄項(xiàng),數(shù)據(jù)對(duì)象對(duì)應(yīng)inodes或文件內(nèi)容。 一個(gè)樹對(duì)象包含一條或多條樹對(duì)象記錄(tree entry),每條樹對(duì)象記錄含有一個(gè)指向數(shù)據(jù)對(duì)象或者子樹對(duì)象的SHA-1指針以及相應(yīng)的模式、類型、文件名信息。
某項(xiàng)目當(dāng)前對(duì)應(yīng)的最新樹對(duì)象可以使用如下命令查看:git cat-file -p master^{tree}
master^{tree}語(yǔ)法表示master分支上最新的提交所指向的樹對(duì)象。 目錄(所對(duì)應(yīng)的樹對(duì)象記錄)并不是一個(gè)數(shù)據(jù)對(duì)象,而是一個(gè)指針,其指向的是另一個(gè)樹對(duì)象。
樹對(duì)象查看:
git show + 對(duì)象名/git ls-tree + 對(duì)象名
git ls-files --stage命令可以查看暫存區(qū)的內(nèi)容。
樹對(duì)象的內(nèi)容格式如下:
tree - ...
item sha部分是二進(jìn)制形式的sha1碼,而不是十六進(jìn)制形式的sha1碼。git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30? test.txt
首先使用xxd把83baae61804e65cc73a7201a7252750c76066a30轉(zhuǎn)換成為二進(jìn)制形式,并將結(jié)果保存為sha1.txt以方便后面做追加操作。
echo -en "83baae61804e65cc73a7201a7252750c76066a30" | xxd -r -p > sha1.txt
構(gòu)造content部分,并保存至文件content.txt
echo -en "100644 test.txt\0" | cat - sha1.txt > content.txt
計(jì)算content的長(zhǎng)度cat content.txt | wc -c
生成SHA-1echo -en "tree 36\0" | cat - content.txt | openssl sha1
(stdin)= d8329fc1cc938780ffdd9f94e0d364e0ea74f579
Git根據(jù)某一時(shí)刻暫存區(qū)(即index文件)所表示的狀態(tài)創(chuàng)建并記錄一個(gè)對(duì)應(yīng)的樹對(duì)象,如此重復(fù)便可依次記錄(某個(gè)時(shí)間段內(nèi))一系列的樹對(duì)象。 因此,為創(chuàng)建一個(gè)樹對(duì)象,首先需要通過(guò)暫存一些文件來(lái)創(chuàng)建一個(gè)暫存區(qū)。通過(guò)update-index為一個(gè)單獨(dú)文件(test.txt文件)的首個(gè)版本創(chuàng)建一個(gè)暫存區(qū)。 利用update-index命令,可以把test文件的首個(gè)版本加入一個(gè)新的暫存區(qū)。
git update-index --add --cacheinfo 100644 \
83baae61804e65cc73a7201a7252750c76066a30 test
--add表示新增文件名,如果第一次添加某一文件名,必須使用此選項(xiàng);--cacheinfo mode object path是要添加的數(shù)據(jù)對(duì)象的模式、hash值和路徑,path意味著為數(shù)據(jù)對(duì)象不僅可以指定單純的文件名,也可以使用路徑。另外要注意的是,使用git update-index添加完文件后,一定要使用git write-tree寫入到Git文件系統(tǒng)中,否則只會(huì)存在于暫存區(qū)。
指定的文件模式為100644,表明是一個(gè)普通文件。 其它選擇包括:100755,表示一個(gè)可執(zhí)行文件;120000表示一個(gè)符號(hào)鏈接。
現(xiàn)在可以通過(guò)write-tree命令將暫存區(qū)內(nèi)容寫入一個(gè)樹對(duì)象。無(wú)需指定-w 選項(xiàng),如果某個(gè)樹對(duì)象不存在,調(diào)用write-tree命令時(shí)會(huì)根據(jù)當(dāng)前暫存區(qū)狀態(tài)自動(dòng)創(chuàng)建一個(gè)新的樹對(duì)象。git write-tree
5bf35b145b6281c080d58b6d19a5113a47f782ed
git cat-file -p 5bf35b145b6281c080d58b6d19a5113a47f782ed
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test
git cat-file -t 5bf35b145b6281c080d58b6d19a5113a47f782ed
tree
Git樹對(duì)象是在commit的過(guò)程中生成的,其生成會(huì)根據(jù).git目錄下的index文件的內(nèi)容來(lái)創(chuàng)建。git add的操作就是將文件的信息保存到index文件中,在commit時(shí),根據(jù)index的內(nèi)容來(lái)生成樹對(duì)象。
使用git update-index可以為數(shù)據(jù)對(duì)象指定名稱和模式,然后使用git write-tree將樹對(duì)象寫入到Git文件系統(tǒng)中。
創(chuàng)建一個(gè)新的樹對(duì)象,包括test.txt文件的第二個(gè)版本以及一個(gè)新的文件。
echo 'new file' > new.txt
git update-index --cacheinfo 100644 \
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
git update-index --add new.txt
暫存區(qū)現(xiàn)在包含test.txt文件的新版本和一個(gè)新文件new.txt,使用當(dāng)前暫存區(qū)生成新的樹對(duì)象。git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
新的樹對(duì)象包含兩條文件記錄,同時(shí)test.txt的SHA-1值是第二版test.txt。 將第一個(gè)樹對(duì)象加入第二個(gè)樹對(duì)象,使其成為新的樹對(duì)象的一個(gè)子目錄。 通過(guò)調(diào)用read-tree命令可以把樹對(duì)象讀入暫存區(qū)。通過(guò)對(duì) read-tree指定--prefix選項(xiàng)將一個(gè)已有的樹對(duì)象作為子樹讀入暫存區(qū)。
git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
如果基于新的樹對(duì)象創(chuàng)建一個(gè)工作目錄,工作目錄的根目錄包含兩個(gè)文件以及一個(gè)名為bak的子目錄,bak子目錄包含test.txt文件的第一個(gè)版本。
樹對(duì)象解決了文件名的問(wèn)題,而且由于分階段提交樹對(duì)象,樹對(duì)象可以看做是開發(fā)階段源代碼目錄樹的一次次快照,因此可以用樹對(duì)象作為源代碼版本管理。但需要記住每個(gè)樹對(duì)象的hash值,才能找到各階段的源代碼文件目錄樹。在源代碼版本控制中,還需要知道誰(shuí)提交了代碼、什么時(shí)候提交的、提交的說(shuō)明信息等,提交對(duì)象就是為了解決上述問(wèn)題的。
提交對(duì)象指向一個(gè)樹對(duì)象,并且?guī)в邢嚓P(guān)的描述信息,標(biāo)記項(xiàng)目某一個(gè)特定時(shí)間點(diǎn)的狀態(tài)。提交對(duì)象包含一些關(guān)于時(shí)間點(diǎn)的元數(shù)據(jù),如時(shí)間戳、最近一次提交的作者、指向上次提交的指針等等。
提交對(duì)象查看如下:
git show / git log + -s + --pretty=raw +對(duì)象名
提交對(duì)象是用來(lái)保存提交的作者、時(shí)間、說(shuō)明這些信息的,可以使用git commit-tree來(lái)將提交對(duì)象寫入到Git文件系統(tǒng)中。echo 'first commit' | git commit-tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
162f9174ac6bb4c5d41bfc00fcb5147e2d62b839
commit-tree除了要指定提交的樹對(duì)象,也要提供提交說(shuō)明,但提交的作者和時(shí)間則根據(jù)環(huán)境變量自動(dòng)生成,并不需要指定。由于提交的作者和時(shí)間不同,提交對(duì)象的SHA-1哈希值也不相同。
提交對(duì)象的查看可以使用git cat-file。git cat-file -p 162f9174ac6bb4c5d41bfc00fcb5147e2d62b839
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author scorpio <642960662@qq.com> 1536497938 +0800
committer scorpio <642960662@qq.com> 1536497938 +0800
first commit
非首次提交需要指定使用-p指定父提交對(duì)象,使代碼版本才能成為一條時(shí)間線。echo 'second commit' | git commit-tree 0155eb -p 162f9174ac6bb4c5d41bfc00fcb5147e2d62b839
f6bbc9d4e8de1b35ad66c2115aa8519587c26100
git cat-file查看一下新的提交對(duì)象,看到相比于第一次提交,多了parent部分。
第三次提交:echo 'third commit' | git commit-tree 3c4e9c -p f6bbc9d4e8de1b35ad66c2115aa8519587c26100
第三次提交查看:git log --stat 26a72965aa9c1bdab9fe5972012bd903f501f006 --pretty=oneline
26a72965aa9c1bdab9fe5972012bd903f501f006 third commit
bak/test.txt | 1 +
1 file changed, 1 insertion(+)
f6bbc9d4e8de1b35ad66c2115aa8519587c26100 second commit
new.txt | 1 +
test.txt | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
162f9174ac6bb4c5d41bfc00fcb5147e2d62b839 first commit
test.txt | 1 +
1 file changed, 1 insertion(+)
最終提交對(duì)象的結(jié)構(gòu)圖:
合并的提交(merge commits)可能會(huì)有不只一個(gè)父對(duì)象。如果一個(gè)提交對(duì)象沒(méi)有父對(duì)象,稱為根提交(root commit),代表項(xiàng)目最初的一個(gè)版本(revision)。每個(gè)項(xiàng)目必須有至少有一個(gè)根提交(root commit)。
提交對(duì)象的內(nèi)容格式如下:
commit tree
parent
[parent if several parents from merges]
author
committer
第一次提交的提交對(duì)象的內(nèi)容如下:git cat-file -p 162f9174ac6bb4c5d41bfc00fcb5147e2d62b839
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author scorpio <642960662@qq.com> 1536497938 +0800
committer scorpio <642960662@qq.com> 1536497938 +0800
first commit
使用openssl計(jì)算SHA-1
echo -n "commit 165\0
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author scorpio <642960662@qq.com> 1536497938 +0800
committer scorpio <642960662@qq.com> 1536497938 +0800
first commit" | openssl sha1
Git中的數(shù)據(jù)對(duì)象解決了數(shù)據(jù)存儲(chǔ)的問(wèn)題,樹對(duì)象解決了文件名存儲(chǔ)問(wèn)題,提交對(duì)象解決了提交信息的存儲(chǔ)問(wèn)題。
Git對(duì)象(數(shù)據(jù)對(duì)象、樹對(duì)象和提交對(duì)象)都存儲(chǔ)在.git/objects目錄下。
Git對(duì)象的40位SHA-1哈希值分為兩部分:前兩位作為目錄名稱,后38位作為對(duì)象文件名。
Git對(duì)象的存儲(chǔ)路徑規(guī)則為:.git/objects/hash[0, 1]/hash[2, 40]
Git對(duì)象存儲(chǔ)的算法步驟:
A、計(jì)算content長(zhǎng)度,構(gòu)造header;
B、將header添加到content前面,構(gòu)造Git對(duì)象;
C、使用sha1算法計(jì)算Git對(duì)象的40位hash碼;
D、使用zlib的deflate算法壓縮Git對(duì)象;
E、將壓縮后的Git對(duì)象存儲(chǔ)到.git/objects/hash[0, 2]/hash[2, 40]路徑下;
使用Nodejs來(lái)實(shí)現(xiàn)git hash-object -w的功能,即計(jì)算Git對(duì)象的hash值并存儲(chǔ)到Git文件系統(tǒng)中:
const fs = require('fs')
const crypto = require('crypto')
const zlib = require('zlib')
function gitHashObject(content, type) {
??// 構(gòu)造header
??const header = `${type} ${Buffer.from(content).length}\0`
??// 構(gòu)造Git對(duì)象
??const store = Buffer.concat([Buffer.from(header), Buffer.from(content)])
??// 計(jì)算hash
??const sha1 = crypto.createHash('sha1')
??sha1.update(store)
??const hash = sha1.digest('hex')
??// 壓縮Git對(duì)象
??const zlib_store = zlib.deflateSync(store)
??// 存儲(chǔ)Git對(duì)象
??fs.mkdirSync(`.git/objects/${hash.substring(0, 2)}`)
??fs.writeFileSync(`.git/objects/${hash.substring(0, 2)}/${hash.substring(2, 40)}`, zlib_store)
??console.log(hash)
}
// 調(diào)用入口
gitHashObject(process.argv[2], process.argv[3])
測(cè)試:node index.js 'hello, world' blob
8c01d89ae06311834ee4b1fab2f0414d35f01102
git cat-file -p 8c01d89ae06311834ee4b1fab2f0414d35f01102
hello, world
Git操作中經(jīng)常需要瀏覽完整的提交歷史,但為了能遍歷提交歷史從而找到所有相關(guān)對(duì)象,必須記住最后一個(gè)提交對(duì)象的SHA1哈希值。因此,需要一個(gè)文件來(lái)保存SHA-1值,并給文件起一個(gè)簡(jiǎn)單名字,然后用名字來(lái)替代原始的 SHA-1值??梢栽?git/refs目錄下找到含有SHA-1值的文件。find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
如果需要?jiǎng)?chuàng)建一個(gè)新引用來(lái)幫助記錄最新提交所在的位置,從技術(shù)上只需將最新提交對(duì)象的SHA1哈希值寫入引用文件內(nèi):
echo "524fd8729bbee740392739d22f64784ec81a9804" > .git/refs/heads/test
然后就可以在Git命令中使用剛創(chuàng)建的新引用來(lái)代替SHA-1值。git log --pretty=oneline test
通常,不提倡直接編輯引用文件。 如果想更新某個(gè)引用,Git提供了一個(gè)更加安全的命令update-ref來(lái)編輯引用。
Git分支的本質(zhì)上是一個(gè)指向某一系列提交之首的指針或引用。 若想在某個(gè)提交對(duì)象上創(chuàng)建一個(gè)分支,可以進(jìn)行如下操作:git update-ref refs/heads/newbranchname commit_id
當(dāng)執(zhí)行g(shù)it branch (branchname)時(shí),Git通過(guò)HEAD文件獲取最新提交對(duì)象的SHA-值。HEAD文件是一個(gè)符號(hào)引用(symbolic reference),不像普通引用包含一個(gè)SHA-1值,而是一個(gè)指向其它引用的指針,指向當(dāng)前所在的分支。 可以查看HEAD文件的內(nèi)容:cat .git/HEAD
ref: refs/heads/master
如果執(zhí)行g(shù)it checkout test,Git會(huì)對(duì)HEAD文件進(jìn)行更新。cat .git/HEAD
ref: refs/heads/test
當(dāng)執(zhí)行g(shù)it commit時(shí),會(huì)創(chuàng)建一個(gè)提交對(duì)象,并用HEAD文件中引用所指向的SHA-1值設(shè)置其父提交字段。
標(biāo)簽對(duì)象(tag object)類似于一個(gè)提交對(duì)象,包含一個(gè)標(biāo)簽創(chuàng)建者信息、一個(gè)日期、一段注釋信息以及一個(gè)指針。主要的區(qū)別在于,標(biāo)簽對(duì)象通常指向一個(gè)提交對(duì)象,而不是一個(gè)樹對(duì)象。 標(biāo)簽對(duì)象永遠(yuǎn)指向同一個(gè)提交對(duì)象,并給指向的提交對(duì)象加上一個(gè)更友好的名字。
如果添加了一個(gè)遠(yuǎn)程版本庫(kù)并對(duì)其執(zhí)行過(guò)推送操作,Git會(huì)記錄下最近一次推送操作時(shí)每一個(gè)分支所對(duì)應(yīng)的值,并保存在refs/remotes目錄下??梢蕴砑右粋€(gè)叫做origin的遠(yuǎn)程版本庫(kù),然后把master分支推送到遠(yuǎn)程倉(cāng)庫(kù)。
如果查看refs/remotes/origin/master文件,可以發(fā)現(xiàn)origin遠(yuǎn)程版本庫(kù)的 master分支所對(duì)應(yīng)的SHA-1值就是最近一次與服務(wù)器通信時(shí)本地master分支所對(duì)應(yīng)的SHA-1值。
遠(yuǎn)程引用和分支(位于refs/heads目錄下的引用)之間最主要的區(qū)別在于,遠(yuǎn)程引用是只讀的。雖然可以git checkout 到某個(gè)遠(yuǎn)程引用,但Git并不會(huì)將 HEAD引用指向該遠(yuǎn)程引用。因此,永遠(yuǎn)不能通過(guò)commit命令來(lái)更新遠(yuǎn)程引用。 Git將遠(yuǎn)程引用作為記錄遠(yuǎn)程服務(wù)器上各分支最后已知位置狀態(tài)的書簽來(lái)管理。
Git最初向磁盤中存儲(chǔ)對(duì)象時(shí)所使用的格式被稱為“松散(loose)”對(duì)象格式。 但Git會(huì)時(shí)不時(shí)地將多個(gè)松散對(duì)象打包成一個(gè)稱為“包文件(packfile)”的二進(jìn)制文件,以節(jié)省空間和提高效率。當(dāng)版本庫(kù)中有太多的松散對(duì)象,或者手動(dòng)執(zhí)行g(shù)it gc命令,或者向遠(yuǎn)程服務(wù)器執(zhí)行推送時(shí),Git都會(huì)對(duì)對(duì)象打包。 要看到打包過(guò)程,可以手動(dòng)執(zhí)行g(shù)it gc命令讓Git對(duì)對(duì)象進(jìn)行打包。git gc
Counting objects: 47126, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (16945/16945), done.
Writing objects: 100% (47126/47126), done.
Total 47126 (delta 29923), reused 46986 (delta 29783)