去年我读 Git Pro 这本书时,很难理解 Git 存储涉及到的四种对象模型 Blob、Tree、Commit、Tag,于是就直接跳过了。
今年用的 Git 逐渐多了起来,且有了一丁点 Linux 基础,回头再看豁然开朗。它的存储方式真是既精妙、又简单。
新文件纳入到 Git 后会被五马分尸,它的内容被扔到在一个 blob 对象中,它的对象名是基于内容运算生成的一个 40 个字符的 SHA1 值。
blob 没有文件名,只有内容。
一个 tree 对象就是一大坨指针,指向:
可以把 Tree 对象想象为 Linux 文件系统中的目录,记录了子目录的信息、文件信息。
一个 commit 对象由以下几部分组成
还有一个 tag 对象,平时几乎不用,就先不写了。
原文链接
#12 楼 @stardiviner 不是这个意思,git 的哲学之一是每次提交会对所有文件做一次快照,这样即使在无网络的时候也可以回退到任何版本。但是我们每次提交的时候,并不是所有文件都发生变化了,对于没有变化的文件,只需要保留指向上一次变化的指针即可。
因为公司里要做 Git 的技术交流分享,所以这段时间仔细研究了 Git 背后的原理,原本我打算写一篇详细介绍 Git 对象模型机制的文章的,忽然发现 @xiaoronglv 已经写了,而且写的相当不错,我也就懒得再写了,哈哈。
我把原先准备的一些素材发一下吧,理论的东西小荣的文章已经讲了不少,我就上一个鲜活的项目例子吧。
一个纯研究演示 Git 对象模型的项目,项目在 https://github.com/xiewenwei/dig-git,总共有 5 次 commit(包括一次 merge),都是很简单的内容,我们可以仔细观察一下每一 commit 之后 Git 对象模型图,以此分析 Git 存储的原理。
如图所示,生成了 3 个对象,一个 commit 对象,一个 tree 对象,一个 blob 对象。图上蓝底是 commit 对象,灰底的是 tree 对象,白底的是 blob 对象,每个对象节点的标题是对象的 key (SHA 摘要) 缩略表示。 对于 commit 对象,tree 内容表示这个 commit 对应根目录的 tree 对象,parent 表示父 commit 节点,通常 commit 只有一个父节点,也可能没有(首次提交时 parent 为空),也可能有多个(合并节点),commit 对象还保存了 commit message 等信息。 对于 tree 对象,里面的内容包含了文件名,文件对应的 blob 对象的 key,或者是目录名和目录对应 tree 对象的 key。 对于 blob 对象,表示一个实际文件对象的内容,但不包括文件名,文件名是在 tree 对象里存的。
这个图怎么得到的呢?主要是两个命令:
git log
命令获取最新 commit 的 key通过 git cat-file -p <object key>
获取 key 对应 object 的内容,根据 object 里的内容,继续探索,就可以访问到所有关联 object.
因为 a.txt 文件已经修改,生成了一个新的 blob 对象,tree 对象和 commit 对象。如图所示,commit 对象之间是有关联的,新提交的 commit 对象的 parent 是上一次提交的 commit 对象。
如图所示,目录是有一个 tree 对象表示的,里面的内容指明了目录包含的文件或子目录。
0c5ca 对应的 commit 对象就是生成的分支 test1 中的。分支在 Git 中是一个非常轻量化的操作,建立分支甚至都不增加新的对象。
def18 就是合并后的 commit 对象。合并生成了一个新的 commit,这个 commit 的 parent 有两个,指向合并的两个原分支对应的 commit 上。
抱歉没有写得很详细,恐怕需要自己参照例子试试一看看,搞明白这些图,也就能搞明白整个 Git 对象模型机制了。