0%

《Version Control With Git》Git库的基础数据结构

本文介绍Git库的基础数据结构,这是Git得以运转的基础。Git库的基础数据结构根据作用主要可分为“支持存放数据信息的”,“支持存放配置信息的”和“支持存放索引信息的”这3大类。详细描述见表1。

表1

基础数据结构作用 基础数据结构名称 基础数据结构存放路径
支持存放数据信息的 Object Store和Pack File .git/objects目录
支持存放配置信息的 配置文件 .git/config文件
支持存放索引信息的 索引文件 .git/index文件

一、Object Store

1.1、概念

Object Store内存储有Git库内所有数据文件和所有修改操作记录等数据,根据这些数据可获得Git库任意版本的快照。Object Store内所有数据文件可分为“Blob”,“Tree”,“Commit”和“Tag”这4种类型,文件名由40个十六进制字符构成,具体是对文件内容应用SHA1算法而获得。
Object Store内数据文件具体存放路径为“.git/objects目录”,取文件名前两个十六进制字符创建子目录,防止直接平铺文件数量过多。

1.1.1、Blob

Blob类型文件存储Git库内数据文件的具体内容,Git库内不同数据文件和数据文件的不同版本对应于不同的Blob类型文件(一个数据文件的两个版本即便只有一点修改,仍会以“两个独立的Blob类型文件”形式存在,而不是“一个独立的Blob类型文件,另外一个差异内容文件”形式)。
Blob类型文件不能引用其他类型文件,只能被Tree类型文件引用。

1.1.2、Tree

Tree类型文件内有0到多行记录,每行记录或指向一个Blob类型文件,或指向另外一个Tree类型文件。选取某个Tree类型文件为树的根节点,向下遍历,可得到文件系统内相应的一棵“目录-文件层次树”。
Tree类型文件能够引用Tree类型或者Blob类型文件,且能够被Tree类型或者Commit类型文件引用。

1.1.3、Commit

Commit类型文件保存修改操作记录,每次提交修改操作记录时都会创建一个新的Commit类型文件。Commit类型文件(除了初始Commit类型文件)会以上一次提交修改操作记录时创建的Commit类型文件为父Commit类型文件,同时会引用一个Tree类型文件。以该Tree类型文件为树的根节点进行遍历,得到的相应的“目录-文件层次树”即为Git库某个版本的完整快照。
Commit类型文件能够以Commit类型文件为父Commit类型文件,能够引用Tree类型文件,且能够被Tag类型文件引用。

1.1.4、Tag

Tag类型文件保存相关联的其他类型文件的描述信息,比如说“版本号”,这个其他类型文件一般为“Commit类型文件”。
Tag类型文件要么不存在,如果存在,则引用一个Commit类型文件。

1.2、实例

1.2.1、增加操作

执行如下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Commit类型文件包含提交时间,因此,Commit类型文件文件名很大可能跟图示不一致
# Tag类型文件引用Commit类型文件,由以上知,Tag类型文件文件名很大可能也跟图示不一致
rm -rf *
git init
git config --local user.email 'dslztx@not.com'
git config --local user.name 'dslztx'
echo "hello world" > hello.txt
git add hello.txt
git commit . -m 'first commit'
mkdir subdir1
echo "hello world again" > subdir1/hello.txt
git add subdir1
git commit . -m 'second commit'
mkdir subdir2
cp hello.txt subdir2/
git add subdir2
git commit . -m 'third commit'
# 以下命令需要手动补全
# git tag -m 'Version 1.0' V1.0 第1次Commit类型文件文件名
# git tag -m 'Version 2.0' V2.0 第2次Commit类型文件文件名
# git tag -m 'Version 3.0' V3.0 第3次Commit类型文件文件名

读取Object Store内数据文件的内容,可得到如图1所示引用关系图。

图1

1.2.2、删除操作

执行如下命令:

1
2
3
4
5
6
7
8
9
10
# Commit类型文件包含提交时间,因此,Commit类型文件文件名很大可能跟图示不一致
rm -rf *
git init
git config --local user.email 'dslztx@not.com'
git config --local user.name 'dslztx'
echo "hello world" > hello.txt
git add hello.txt
git commit . -m 'first commit'
git rm hello.txt
git commit . -m 'second commit'

读取Object Store内数据文件的内容,可得到如图2所示引用关系图。

图2

1.2.3、修改操作

执行如下命令:

1
2
3
4
5
6
7
8
9
10
11
12
# Commit类型文件包含提交时间,因此,Commit类型文件文件名很大可能跟图示不一致
rm -rf *
git init
git config --local user.email 'dslztx@not.com'
git config --local user.name 'dslztx'
echo "hello world" > hello.txt
git add hello.txt
git commit . -m 'first commit'
git mv hello.txt hello.txt.alter
git commit . -m 'second commit'
echo "hello world content alter" > hello.txt.alter
git commit . -m 'third commit'

读取Object Store内数据文件的内容,可得到如图3所示引用关系图。

图3

二、Pack File

为了节省空间,Git会将Object Store内数据文件的内容使用zlib库进行压缩(也因此,不能直接查看这些数据文件的内容)。而为了更加压缩存储空间,Git又提供了Pack File机制,该机制会将Object Store内数据文件重新整合起来,以特定格式进行组织,达到再次压缩的目的。
Pack File机制产出文件的具体存放路径为“.git/objects/pack目录”和“.git/objects/info目录”。
触发Pack File机制的途径有:Object Store内含有过多数据文件,push本地Git库到远端Git库,执行git gc命令。
接下来进行举例说明,在上面的描述中,同一个文件的不同版本即便只有一点修改,也会以“两个独立的Blob类型文件”的形式保存,有些时候,这会显得浪费空间。在这种情形下,触发Pack File机制,可以大大减少空间的浪费,以下是具体的实验步骤。
1、执行以下命令,将原“repo.rb”和新“repo.rb”文件提交到Git库:

1
2
3
4
5
6
git init
curl https://raw.githubusercontent.com/mojombo/grit/master/lib/grit/repo.rb > repo.rb
git add repo.rb
git commit . -m 'first commit'
echo "hello world" >> repo.rb
git commit . -m 'second commit'

此时Object Store内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.git/objects
.git/objects/95
.git/objects/95/18b8504da44df20d70a5d8589a02535ca1e2a5
.git/objects/03
.git/objects/03/3b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5
.git/objects/10
.git/objects/10/e19ae731d1179fe21d22f223fb70d60c6a04e6
.git/objects/c0
.git/objects/c0/addf97e62267442515f4c7b31f754e6a777789
.git/objects/38
.git/objects/38/feecbdf638935287fd920e8f2d694aa8c28d9f
.git/objects/pack
.git/objects/info
.git/objects/58
.git/objects/58/7438a7ab34b34ca57e2da92ecf7b79c8bb4fb0

经过查找发现原“repo.rb”和新“repo.rb”文件对应的Blob类型文件文件名分别为“033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5”和“587438a7ab34b34ca57e2da92ecf7b79c8bb4fb0”,分别执行git cat-file -s 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5git cat-file -s 587438a7ab34b34ca57e2da92ecf7b79c8bb4fb0命令,可知“033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5”和“587438a7ab34b34ca57e2da92ecf7b79c8bb4fb0”文件大小分别为“22044”和“22056”字节
2、执行/usr/bin/du --block-size=1 -s .git/objects命令,可知此时Object Store内数据文件总大小为“69632”字节
3、执行git gc命令触发Pack File机制,此时Object Store内容如下:

1
2
3
4
5
6
.git/objects
.git/objects/pack
.git/objects/pack/pack-72421cdfd34c79982edc4882bd4febc308e5ae11.idx
.git/objects/pack/pack-72421cdfd34c79982edc4882bd4febc308e5ae11.pack
.git/objects/info
.git/objects/info/packs

4、执行git verify-pack -v .git/objects/pack/pack-72421cdfd34c79982edc4882bd4febc308e5ae11.idx命令,可知“587438a7ab34b34ca57e2da92ecf7b79c8bb4fb0”文件大小为“22056”字节,“033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5”文件引用“587438a7ab34b34ca57e2da92ecf7b79c8bb4fb0”,而其自身大小为“9”字节
5、执行/usr/bin/du --block-size=1 -s .git/objects命令,可知此时Object Store内数据文件总大小为“28672”字节

三、配置文件

Git库的本地配置文件为“.git/config文件”,但是,参数配置不单只有该途径。参数配置按照优先级从低到高顺序为:“/etc/gitconfig文件”,“~/.gitconfig文件”,“.git/config文件”,“环境变量”和“命令行参数配置”。

四、索引文件

对Git库的操作(增加,删除,修改等)暂时被保存在索引文件中,只在git commit命令执行后,才被真正提交到Git库。


参考文献: [1]https://git-scm.com/book/be/v2/Git-Internals-Packfiles
您的支持将鼓励我继续分享!