进来在闲暇的时间里在看一些关系P2P网络的拓扑发现的内容,重点关注了Markle Tree的知识点,在一篇文章里( https://www.sdnlab.com/20095....),发现了了一句话 “Merkle DAG的一个常见例子就是Git存储库”,因而查找了一些关于git存储库的原理,先整理以下。仅供本身和你们参考。
当时个人疑问:html
- git怎么存储数据的,如何能根据存储的数据能够很精确的回退到制定的版本?
- git存储和docker的存储机制相似吗?是否是都是分层的存储?
- git若是不是分层,每次提交都存储起来,那么数据量大了会怎么办?
解惑
咱们的解惑路线是,重新建一个本地git仓库开始,一步一步增长数据和提交,观察内容的具体变化。
首先使用
git init
新建一个本地仓库,而后打开仓库中的.git
文件夹git
- HEAD表示当前提交的指针位置;
- index是索引文件;
- refs文件夹中的文件是不一样分支指向的commitID;
- logs文件夹中记录的是每次refs的历史记录;
- objects文件夹中的内容就是用来存放git本地仓库对象
既然找到了存储git数据的位置,那么git数据结构是什么样的呢?golang
- git 是以键值的方式存储的,也就是说任何类型的数据均可以存储。所以也能够在任什么时候候经过键取出对应的值;
git中底层生成了4中数据的对象:算法
- tree对象:能够看做一个目录,管理一些“tree”对象或是“blob”对象。它有一串指向“blob”对象或是其它“tree”对象的指针,通常用来表示内容之间的目录层次关系(就像文件和子目录)。
- blob对象: 一个“blob”一般用来存储文件的内容。一个“blob”对象就是一块二进制数据,blob对象的键是根据SHA1算法生成的,因此若两个文件在一个目录树或是一个版本仓库中有一样的数据内容,那么它们将会共享同一个“blob”对象,和其所对应的文件所在路径、文件名是否改被更改都彻底没有关系。
- commit对象:“commit”对象指向一个“tree对象”,而且带有相关的描述信息,标记项目某一个特定时间点的状态。它包括一些关于时间点的元数据,如时间戳、最近一次提交的做者、指向上次提交的指针等等。
- tag对象: 一个“tag”对象包括一个对象名(SHA1签名)、对象类型、标签名、标签建立人的名字(“tagger”), 还有一条可能包含有签名(signature)的消息。
当新增一些内容的时候,进行
git commit
命令会出现什么变化?docker
- 当进行一次提交的时候,objects、logs和refs文件夹都会发生变化,咱们主要关注objects文件夹。
- 每次commit都会对数据进行一次保存,会生成commit对象、tree对象和blob对象;
- objects文件夹里面的数据存放的具体规则,对于这三种对象,都会用SHA-1对内容和头信息生成Hash值,去hash值的前两位为objects目录下面的文件夹的名字,取剩余38个字符为文件名,例:8b0c4fe1567a463214c09334b54977e0114c90fe,取8b在objects建立一个文件夹,取0c4fe1567a463214c09334b54977e0114c90fe为文件名在8b文件夹下建立一个文件。
知识点学习了以后,咱们怎么去验证呢?网络
- 使用
git cat-file
对咱们提交的内容进行验证。- 我进行了两次commit,一次是彻底新建的一个README.md文件,里面是有一行数据(### You Know);第二次commit,新建一个test.py文件和在README.md中新添加了一些数据;
git cat-file -t
查看对象的类型,git cat-file -p
优雅的方式打印对象的内容。
- 使用
git log --pretty=oneline
查看个人两次提交![]()
- 使用
git cat-file -t 172b54c8cd3eedca2fc301374286c2cb807d674f
查看第一次提交的类型![]()
- 使用
git cat-file -p 172b54c8cd3eedca2fc301374286c2cb807d674fe
查看第一次提交的内容![]()
- 使用
git cat-file -p 8b0c4fe1567a463214c09334b54977e0114c90fe
查看第一次提交的tree对象,能够看到tree对象中存放的是一个blob对象,就是咱们第一次提交新建的文件README.md![]()
- 使用
git cat-file -p 67aeba604cea61ec63d19db0706b19d846c65ba4
查看第一次提交的blob对象的内容为### You Know![]()
- 使用
git cat-file -p 03543a4c19023da01b5114d7f7a614d95a1bf084
查看第二次提交的内容![]()
- 使用
git cat-file -p 03543a4c19023da01b5114d7f7a614d95a1bf084
查看第二次提交的tree对象内容,包括修改的内容和新增的内容![]()
- 使用
git cat-file -p 03543a4c19023da01b5114d7f7a614d95a1bf084
查看第二次提交的README.md blob对象内容,能够看到是整个文件的所有内容,而不是仅仅包含修改的数据。![]()
总结问题的答案数据结构
- git的数据存储数据结构是键值类型,分为4个对象,而且每次提交都是整个文件的存储,而不是分层的增长存储,因此这样会致使存储的数据量很大,那git用的方法是使用zlib对数据进行压缩,因此咱们打开存储的文件是这样的数据,那咱们都是用cat-file命令来查看的,怎么才能这些内容是通过zlib压缩过的呢?
![]()
- 我用GO语言写了一个简单的程序,来验证这些数据是通过zlib压缩以后的,运行这个程序的时候带上你要查看git对象的文件路径,就能够看到被还原的内容了;
![]()
package main import ( "bytes" "compress/zlib" "fmt" "io" "io/ioutil" "os" ) //进行zlib压缩 func DoZlibCompress(src []byte) []byte { var in bytes.Buffer w := zlib.NewWriter(&in) w.Write(src) w.Close() return in.Bytes() } //进行zlib解压缩 func DoZlibUnCompress(compressSrc []byte) []byte { b := bytes.NewReader(compressSrc) var out bytes.Buffer r, _ := zlib.NewReader(b) io.Copy(&out, r) return out.Bytes() } func main() { args := os.Args if args == nil || len(args) < 2{ fmt.Println("Should input zlib file path.") return } b, err := ioutil.ReadFile(args[1]) if err != nil { fmt.Print(err) } fmt.Println(string(DoZlibUnCompress(b))) }
- 文章是参考了不少前辈博客基础上写来的, 也有本身的实践,因此颇有必要记录下来。
- 有问题就想办法去理解和解决,并经过动手实践验证。