首先要弄明白一點(diǎn),從根本上來講 Git 是一個(gè)內(nèi)容尋址(content-addressable)文件系統(tǒng)斤程,并在此之上提供了一個(gè)版本控制系統(tǒng)的用戶界面。
1.低層命令(plumbing)和高層命令(porcelain)
高層命令:對(duì)用戶友好的一些命令包括我們常見的:push pull checkout branch等30多個(gè)
參見:https://git-scm.com/book/zh/v2/Appendix-C%3A-Git-%E5%91%BD%E4%BB%A4-%E8%AE%BE%E7%BD%AE%E4%B8%8E%E9%85%8D%E7%BD%AE 附錄3
低層命令: cat-file ls-remote等命令
2.分析git目錄
首先初始化一個(gè)git文件 然后打開.git文件
hooks
logs
config
objects
ORIG_HEAD
description
info
refs
HEAD
FETCH_HEAD
packed-refs
gc.log
COMMIT_EDITMSG
index
可以看到有這么多文件或文件夾
其中
description 文件僅供 GitWeb 程序使用菩混,我們無需關(guān)心
config 文件包含項(xiàng)目特有的配置選項(xiàng)忿墅。
info 目錄包含一個(gè)全局性排除(global exclude)文件,用以放置那些不希望被記錄在 .gitignore 文件中的忽略模式(ignored patterns)
hooks 目錄包含客戶端或服務(wù)端的鉤子腳本(hook scripts)
HEAD 文件
(尚待創(chuàng)建的)index 文件
objects 目錄
refs 目錄
這4個(gè)目錄是最重要的是git的核心部分
- objects 存放所有的數(shù)據(jù)內(nèi)容
- refs 目錄存儲(chǔ)指向數(shù)據(jù)(分支)的提交對(duì)象的指針沮峡;
- HEAD 文件指示目前被檢出的分支疚脐;
- index 文件保存暫存區(qū)信息。
3.git對(duì)象
Git 是一個(gè)內(nèi)容尋址文件系統(tǒng).這意味著邢疙,Git 的核心部分是一個(gè)簡(jiǎn)單的鍵值對(duì)數(shù)據(jù)庫(key-value data store)棍弄。 你可以向該數(shù)據(jù)庫插入任意類型的內(nèi)容望薄,它會(huì)返回一個(gè)鍵值,通過該鍵值可以在任意時(shí)刻再次檢索(retrieve)該內(nèi)容照卦。
我們打開一個(gè)使用過git的.git/objects目錄
可以看到很多文件夾
每個(gè)文件夾中都有一個(gè)文件
比較特殊的有2個(gè)文件:info, pack 這兩個(gè)文件后續(xù)會(huì)解釋
首先我們查看git是如何存儲(chǔ)文件的
使用git hash-object -w --stdin
w表示存儲(chǔ) 不添加則僅生成
stdin如果不添加則需要在命令后接入要處理的內(nèi)容
命令輸出一個(gè)長(zhǎng)度為 40 個(gè)字符的校驗(yàn)和式矫。
這是一個(gè) SHA-1 哈希值——一個(gè)將待存儲(chǔ)的數(shù)據(jù)外加一個(gè)頭部信息(header)一起做 SHA-1 校驗(yàn)運(yùn)算而得的校驗(yàn)和。
校驗(yàn)和的前兩個(gè)字符用于命名子目錄役耕,余下的 38 個(gè)字符則用作文件名采转。
現(xiàn)在我們知道可以用hash-object存儲(chǔ)
然后通過cat-file
命令我們可以解析git 取回?cái)?shù)據(jù)
cat-file是一個(gè)非常重要的命令
-p 可以將git內(nèi)容解析 為我們展示友好的輸出
-t 可以讓 Git 告訴我們其內(nèi)部存儲(chǔ)的任何對(duì)象類型,只要給定該對(duì)象的 SHA-1 值
git 樹對(duì)象
它能解決文件名保存的問題瞬痘,也允許我們將多個(gè)文件組織到一起故慈。 Git 以一種類似于 UNIX 文件系統(tǒng)的方式存儲(chǔ)內(nèi)容,但作了些許簡(jiǎn)化框全。 所有內(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)拆撼,每條記錄含有一個(gè)指向數(shù)據(jù)對(duì)象或者子樹對(duì)象的 SHA-1 指針,以及相應(yīng)的模式喘沿、類型闸度、文件名信息。
例如我們分析下.git目錄
git cat-file -p master^{tree} // 將當(dāng)前的master以樹對(duì)象輸出
100644 blob eccc066d0fee656b1cd5b0a0918acc12a04e54ed README.md
040000 tree d8286c0bdd97ecf334347e556082e410ee6d8a16 data
樹對(duì)象指向了一個(gè)readme和另一個(gè)樹對(duì)象
graph TD
master-->|tree|data
master-->|blob|README
我們繼續(xù)看
git cat-file -p d8286c0bdd97ecf334347e556082e410ee6d8a16
100644 blob 682b2728d73775036b0624f8d2d2422e14cd71de secret
這樣就拿到了data里面存儲(chǔ)的git對(duì)象
graph TD
master-->|tree|data
master-->|blob|README
data-->|blob|secret
*這里我們看到了一些100644等數(shù)字蚜印,這個(gè)是文件類型*
下面是git數(shù)據(jù)對(duì)象的類型
100644:表明這是一個(gè)普通文件
100755:表示一個(gè)可執(zhí)行文件
120000:表示一個(gè)符號(hào)鏈接
以及我們見到過的
040000: tree類型
下面我們生成一個(gè)樹對(duì)象
通常git根據(jù)某一時(shí)刻的暫存區(qū)生成樹對(duì)象
首先生成暫存區(qū)并把文件加入
git update-index --add --cacheinfo 100644 <SHA1> text1
通過這個(gè)命令我們吧一個(gè)名為text1的普通文件加入了暫存區(qū)
--add是因?yàn)槲覀冎皼]有創(chuàng)建過暫存區(qū)
--cacheinfo 命令因?yàn)閷⒁砑拥奈募挥?Git 數(shù)據(jù)庫中莺禁,而不是位于當(dāng)前目錄下
現(xiàn)在可以通過write-tree
命令將暫存區(qū)文件生成一個(gè)樹對(duì)象
提交對(duì)象
如果有多個(gè)樹對(duì)象,分別代表了我們想要跟蹤的不同項(xiàng)目快照窄赋。若想重用這些快照哟冬,你必須記住所有三個(gè) SHA-1 哈希值。 并且忆绰,你也完全不知道是誰保存了這些快照浩峡,在什么時(shí)刻保存的,以及為什么保存這些快照错敢。 而以上這些红符,正是提交對(duì)象(commit object)能為你保存的基本信息。
通過調(diào)用 commit-tree
命令創(chuàng)建一個(gè)提交對(duì)象伐债,為此需要指定一個(gè)樹對(duì)象的 SHA-1 值,以及該提交的父提交對(duì)象
$ echo 'first commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d
查看文件
$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700
first commit
提交對(duì)象的格式很簡(jiǎn)單:它先指定一個(gè)頂層樹對(duì)象致开,代表當(dāng)前項(xiàng)目快照峰锁;然后是作者/提交者信息(依據(jù)你的 user.name 和 user.email 配置來設(shè)定,外加一個(gè)時(shí)間戳)双戳;留空一行虹蒋,最后是提交注釋。
我們生成了第一個(gè)提交 fdf4fc3344e67ab068f836878b6c4951e3b15f3d
接著使用commit-tree 對(duì)象提交新的對(duì)象并將第一個(gè)提交作為父對(duì)象
echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
例如0155eb 是新的暫存區(qū)引用 fdf4fc3是父提交
這個(gè)時(shí)候我們使用git log的話就可以看到真正的提交記錄了
這個(gè)就是每次我們使用git add 和 git commit時(shí)git為我們做的事情
Git 所做的實(shí)質(zhì)工作——將被改寫的文件保存為數(shù)據(jù)對(duì)象,更新暫存區(qū)魄衅,記錄樹對(duì)象峭竣,最后創(chuàng)建一個(gè)指明了頂層樹對(duì)象和父提交的提交對(duì)象。 這三種主要的 Git 對(duì)象——數(shù)據(jù)對(duì)象晃虫、樹對(duì)象皆撩、提交對(duì)象——最初均以單獨(dú)文件的形式保存在 .git/objects 目錄下
git 如何存儲(chǔ)對(duì)象
前面提到git生成SHA-1后會(huì)將前2位作為文件夾名后38位為文件名存儲(chǔ)。
git通過zlib壓縮文件并存儲(chǔ)
4.git 引用
我們可以借助git log等命令來瀏覽完整的提交歷史哲银,但為了能遍歷那段歷史從而找到所有相關(guān)對(duì)象扛吞,你仍須記住 某個(gè)SHA-1 是最后一個(gè)提交。 我們需要一個(gè)文件來保存 SHA-1 值荆责,并給文件起一個(gè)簡(jiǎn)單的名字滥比,然后用這個(gè)名字指針來替代原始的 SHA-1 值。
git中這樣的文件稱為引用(references)存放在refs目錄下
我們可以通過update-ref
來創(chuàng)建引用
git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9
這個(gè)命令就是創(chuàng)建一個(gè)master的引用對(duì)象 指向1a這個(gè)提交對(duì)象
同時(shí)我們也可以創(chuàng)建別的引用對(duì)象
git update-ref refs/heads/xunlu 1a410efbd13591db07496601ebc7a059dd55cfe9
這個(gè)命令就相當(dāng)于在1a這個(gè)提交對(duì)象下git branch xunlu
那么git 如何知道當(dāng)前的分支呢
答案是HEAD文件
HEAD文件是一個(gè)符號(hào)引用指向目前所在的分支做院, 所謂符號(hào)引用盲泛,意味著它并不像普通引用那樣包含一個(gè) SHA-1 值——它是一個(gè)指向其他引用的指針
cat .git/HEAD
ref: refs/heads/master
當(dāng)我們執(zhí)行g(shù)it commit時(shí),git會(huì)創(chuàng)建一個(gè)提交對(duì)象键耕,并用 HEAD 文件中那個(gè)引用所指向的 SHA-1 值設(shè)置其父提交字段寺滚。
你可以通過symbolic-ref
命令來查看或者修改HEAD文件
git symbolic-ref HEAD refs/heads/test
將HEAD指向test分支
還有標(biāo)簽引用和遠(yuǎn)程引用
標(biāo)簽引用就是創(chuàng)建了一個(gè)永遠(yuǎn)指向一個(gè)固定的提交對(duì)象的引用,相當(dāng)于起了別名
遠(yuǎn)程引用:如果你添加了一個(gè)遠(yuǎn)程版本庫并對(duì)其執(zhí)行過推送操作郁竟,Git 會(huì)記錄下最近一次推送操作時(shí)每一個(gè)分支所對(duì)應(yīng)的值玛迄,并保存在 refs/remotes 目錄下
5.包文件
git 使用zlib壓縮文件
當(dāng)你對(duì)同一個(gè)很大的文件修改哪怕一行以后你會(huì)發(fā)現(xiàn) git 會(huì)使用全新的文件存儲(chǔ)這個(gè)修改了的文件 放在了object中,這就造成了極大的浪費(fèi) 這兩個(gè)文件幾乎相同棚亩。
如果存放一個(gè)文件放他們相同的部分豈不是更好蓖议。
Git 最初向磁盤中存儲(chǔ)對(duì)象時(shí)所使用的格式被稱為“松散(loose)”對(duì)象格式。 但是讥蟆,Git 會(huì)時(shí)不時(shí)地將多個(gè)這些對(duì)象打包成一個(gè)稱為“包文件(packfile)”的二進(jìn)制文件勒虾,以節(jié)省空間和提高效率。 當(dāng)版本庫中有太多的松散對(duì)象瘸彤,或者你手動(dòng)執(zhí)行 git gc 命令修然,或者你向遠(yuǎn)程服務(wù)器執(zhí)行推送時(shí),Git 都會(huì)這樣做质况。
通過 git gc
命令 git生成了pack/ 文件和索引文件
可以使用 git verify-pac
命令查看已打包的內(nèi)容