Git由浅入深之存储原理

Git由浅入深之存储原理

本来计划本篇介绍Git分支的相关知识点与操作,但是准备的过程中发现涉及到很多内部存储原理,决定先介绍一下Git存储原理,明白了这些,有助于理解后续内容,对Git的使用也会有很大帮助。

Git存储目录结构

在初始化项目仓库时(git clone 或git init),Git会在根目录下创建一个.git目录,其下存放着Git操作和存储相关的内容,该目录结构大致如下:

Git存储目录结构

如图中所述:

  • HEAD文件指向当前分支;
  • index文件存储着暂存区的内容信息;
  • refs目录存储着所有分支指向各自提交对象的指针;
  • objects目录存储着Git数据库的所有内容;
  • config文件包含项目的配置信息;
  • info目录下的exclude文件包含项目全局忽略匹配模式,与.gitignore文件互补;
  • hooks目录则存放项目的客户端或服务端钩子脚本。

注:其中的ORIG_HEAD记录的是在进行极端(drastic)操作(如合并merge,回退reset等)时,此操作之前HEAD所指向的位置,便于我们在发生毁灭性失误时进行回退,如使用
git reset --hard ORIG_HEAD指令可以回退到危险操作之前的状态,但是对于正常的提交操作,该指针是不会变化的。在1.8.5版本以后,Git使用了链表记录HEAD的所有移动轨迹,
可以使用git reflog查看,使用git reset HEAD@{num}方式可以回退到指定版本,这也是之后介绍Git数据恢复将要介绍的一个指令,推荐使用这种方式替代ORIG_HEAD方式。
更多信息可参考此处

Git存储

Git是一个内容寻址文件系统(content-addressed filesystem),其存储内容都是通过内容地址维护,可以把它理解成一个键值对存储方式:即给定一个存储文件,该系统根据文件信息和内容,使用SHA-1算法计算,返回一个由40个十六进制字符组成的字符串,之后只需要通过该字符串即可访问该文件,这个字符串就是Git中通常所说的校验和。

内容寻址

在了解Git内部存储原理之前我们先了解下内容寻址:

When being contrasted with content-addressed storage, a typical local or networked
storage device is referred to as location-addressed. In a location-addressed storage device,
each element of data is stored onto the physical medium, and its location recorded for later use.
The storage device often keeps a list, or directory, of these locations.
When a future request is made for a particular item, the request includes only the
location (for example, path and file names) of the data. The storage device can then use this
information to locate the data on the physical medium, and retrieve it. When new information is
written into a location-addressed device, it is simply stored in some available free space,
without regard to its content.
In contrast, when information is stored into a CAS system, the system will record a content address,
which is an identifier uniquely and permanently linked to the information content itself.
A request to retrieve information from a CAS system must provide the content identifier,
from which the system can determine the physical location of the data and retrieve it.
Because the identifiers are based on content, any change to a data element will necessarily
change its content address.
谈到内容寻址,有必要了解一下的就是本地寻址,或者叫物理寻址。对于物理寻址系统,其所有数据存储在物理媒介的可用空间,与其内容无关,系统记录其物理地址(physical location)供随后使用,这些物理地址通常通过使用一个列表或者目录来维护,当再次请求特定数据时,需要使用其物理地址,如路径和文件名。
而对于一个内容寻址系统,系统记录的是一个内容地址(content-address),该内容地址是对应数据的一个唯一且持久的识别符,它是通过加密哈希算法(如,SHA-1或MD5)计算出来的一串值,当我们需要数据时,提供该内容地址,系统即可通过该地址获取数据的物理地址,返回数据;同时,对于数据的任何变更都将导致内容地址发生变化。

Git中所有的这些内容地址都存储在根目录下的.git/objects/文件夹下,他们按照树形结构分类存放,每个内容地址的前两位作为枝干分别建立子目录,该目录下存在所有同类型(开头相同)的内容地址。

Git对象

下面需要介绍Git存储相关的几个对象概念:提交对象,tree对象,blob对象。

git cat-file

Git提供cat-file指令支持将校验和解密开,如下校验和是”hello”经过SHA-1算法加密后得到的,还原后:

git cat-file

提交对象

如下,我们初始化一个本地仓库,并进入Git存储目录.git/objects,这里除了默认创建的pack和info子目录,并没有任何存储文件或目录,因为当前仓库是空的:

初始化项目

我们在仓库内新建一个文件,并在文件中输入”hello”内容:

初始化提交

我们看到此次提交的commitId是debaa7e47534af51ef3c10117200e65dd4c658ef,我们根据校验和的前两位,即de.git/objects/下寻找de目录,进入该目录:

查看commitid

该值就是之前的提交commitId值,该值也是此次提交对象的校验和,解密如下:

还原提交对象

在上图中,我们并没有使用完整的校验和,因为Git使用SHA-1算法计算校验和,前面8位字符完全重叠的情况几乎不存在,而且对比后发现解密后的内容和之前查看的提交历史内容吻合,我们再次变更内容,新增几个文件:

第二次提交

再次根据新提交的commitId686b5e1cbfa83ed0244558d006ee01543fde83f3,解密:

还原第二次提交对象

和初始提交对象相比多了一行parent debaa7e47534af51ef3c10117200e65dd4c658ef,该行对应存储的校验和代表的是此次提交的父提交对象,此处即之前的初始提交对象校验和。

通常提交对象包括三部分内容:

  • 提交信息:包括提交者姓名/邮箱和提交备注等相关信息;
  • tree对象的校验和:关于tree对象在下一节详细介绍;
  • 父提交对象的校验和(可能存在多个。如分支发生合并时,提交的父提交可能有多个)

tree对象

解密上例中的tree对象校验和:

还原tree对象

我们发现此tree对象包含三行,前两行是两个blob校验和,指向单个文件,第三行的校验和是对应一个tree对象,指向一个spa目录。

还原第一行的blob对象:

还原blob对象

可以发现blob校验和就代表了该文件的内容;再看二级tree对象校验和解密后:

还原二级tree对象

和一级tree对象类似,其包含了一个blob校验和,指向spa目录下的test.js,当然这个校验和代表的就是test.js文件的内容:

还原二级blob对象

现在,我们知道一个tree对象可以包含:

  • 若干tree对象校验和: 指向当前目录的一个子目录;
  • 若干blob校验和:指向一个文件

注:这里的blob说明Git是通过创建Blob对象(二进制方式)存储数据。

blob对象

BLOB (binary large object),二进制大对象,是一个可以存储二进制文件的容器,Git中所有数据都是通过该方式存储。

本篇对Git内部存储原理作了介绍,读完相信会有一定的认识,下一篇介绍Git分支的操作相关。

原创文章,转载请注明: 转载自 熊建刚的博客

本文链接地址: Git由浅入深之存储原理

熊 建刚

热爱前端,但不局限于前端,喜欢尝试各种新技术,爱好读书。

One thought on “Git由浅入深之存储原理

发表评论

电子邮件地址不会被公开。 必填项已用*标注