Git之对象数据库

数据库,顾名思义,即存储数据的仓库。

我们的Git是一个关系型对象数据库,怎么理解呢,它主要管理对象及对象之间的关系。而对象间的关系,组成了一个图结构。

图结构

我们先来看一下Git里对象的关系是怎样的。如下图。

git-objects.png

这是我用git-show-objects工具生成的某个Git仓库的对象关系。

矩形框(包括折角矩形框)即我们要研究的对象,而椭圆形是我们的指针。下面我们来分别看一下Git里的对象和指针。

对象

Git中的对象主要分两大类:不可变对象可变对象

不可变意味着,如果对象的某个域要发生变化,我们会创建一个新的对象,而不是修改原来的对象。

这就有几个很有意思的事情:

  1. 频繁创建对象会占用大量磁盘,但它会带来更好的灵活性和可跟踪性
  2. 不可变的对象更安全,可以共享使用,这在一定程度上又节省了磁盘

对于可变对象,由于其域可变,故我们是决不可能用它来保存历史的,但是保存临时的或一次性的数据则非常理想。

不可变对象

Git的图结构主要是由不可变对象组成的,Blob、Tree和Commit是我们接触最多的三类不可变对象。

Blob

Blob对象用来保存我们项目文件的内容,对应上图中的折角矩形。

有一点比较关键,Blob对象只保存文件内容。这就意味着,如果我们有两个不同的文件,而其内容竟然一致,好了,我们只会生成一个Blob对象。

Tree

Tree对象用来保存我们项目目录的内容,对应上图中的蓝文字矩形。

跟Blob对象一样,如果我们有hello1/dir和hello2/dir两个目录,而两个目录的内容竟然递归相同,那我们的两个dir只需要一个Tree对象就可以了。

Tree对象内保存着一个列表,它的每一项,都是一个文件名+Blob对象目录名+另一个Tree对象(子目录)。

Commit

Commit对象用来保存我们的提交动作,对应上图中的橘红色矩形。

Commit对象里保存的信息比Blob和Tree都要多,因为它已经是我们经常访问到的对象了。

Commit对象里有我们想知道的作者提交时间 信息,它还保存着此次提交的父提交。当然,父提交有可能是两个,三个或百八十个,只要你喜欢。

可变对象

Git中的可变对象最典型的就是Index对象,它是一个全局对象,或称其为单例对象。

Index对象维护着当前Git对应的文件及状态,是我们操作Git的入口。它维护一个列表,每一项包含了文件的mode对应的Blob文件对应路径和一些Git自己使用的标记。

指针

上面提到保存着父提交保存着对应的Blob,那这些是以何种类型保存到对象的域的呢?

答案是哈希值

我们知道,在计算机中,一个对象是有一个内存地址对应的,而Git的对象,用的是对象的哈希值。即,Git计算某个对象的哈希值,使用此值作为文件的路径,并将对象的内容序列化后存储到该路径。

实际上,Git将其要维护的对象统一存放在项目根目录的.git/objects/位置,并以对象哈希值的前两个字符作目录名,剩余字符作文件名,生成对象目录。

我们之前说,Index对象是我们操作Git的入口。指针,即是我们访问对象的入口。

Git中的指针直接或间接指向Commit对象,分为常量指针和变量指针。它们对应上图中的椭圆形标记。

变量指针

随着我们对Git的操作,变量指针的指向有可能发生变化。

Head、Remote

变量指针就像我们的书签,标记着我们当前看到了哪一页。

最典型的变量指针是Head(Remote),也即是我们常说的分支。如上图中的heads/master,它对应我们本地的master分支。

HEAD是一个特殊的指针,跟Index相似,它有且只有一个,顺着它指的方向,我们总可以找到一个Commit。

这里要注意,HEAD正常情况下是指向Head的,但上图中它直接指向了某个Commit,这就是Git里面的游离态(detached)。

常量指针

常量指针是一旦创建,就很少发生变化的指针。注意,是很少,并非没有。

Tag

Tag是常量指针的典型,它主要充当我们的标记笔,考试画重点必备。

例如,我们可以用它来标记一个稳定的发行版本对应的Commit,以供伙伴们检出或下载。

常用操作

git init

初始化仓库,主要是初始化.git/目录,创建Index对象、HEAD指针,以准备接下来的工作。

init.png

git add

创建Blob对象,并将对应文件添加到Index对象。

add.png

git commit

递归创建Tree对象,创建Commit对象,并保存根目录对应的Tree对象。更新HEAD指向的指针的指向。

commit.png

问题

此时,一个简单的提交就完成了。当然,这连一个开始都够不上。

如果这三个命令你都理解了,不仿实操一下以下几个问题:

  1. fast-forware是如何操作的?
  2. 上面提到过detached,它有什么问题,应该如何避免?
  3. 根据Git的实现原理,如何设计系统可以更好的利用磁盘空间?

其实第三个问题恰是我们设计简洁系统的一个原则。

参考

根本停不下来?可以看一下maryrosecook写的《git from the inside out》,如果看英文费劲也可看一下我的粗陋翻译《彻底理解git》。

自己创建一个测试仓库,使用git-show-objects工具,边做边观察一下发生了什么。