git内部原理

概述

Git从根本上说是一个基于内容寻址(content-addressable)的文件系统,并在此之上提供一个VCS的用户界面。

最初git只是为VCS提供的一套工具集,而不是一个完成的VCS,所以git存在一系列命令能完成一些底层操作,这些命令被设计为能以Unix-style连接(chained)在一起,或者可以被脚本调用。这些命令被称为底层(plumbing)命令。相对应的那些更友好(user-friendly)的命令被称为高层(porcelain)命令。

通常情况下,不会在命令行中直接使用这些底层命令,它们更多被用于构建新的命令或用于自定义的脚本。

.git目录

在执行完git init命令后,会生成一个.git目录。该目录里面包含了几乎所有的git存储和操作的对象。如果你想备份或克隆一个你的repo,只需要将copy该目录即可。

一个已经有提交记录的.git目录内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[root@localhost .git]# tree -F .
.
├── branches/
├── config # 包含所有特有(project-specific)的配置项
├── description # 该文件仅用于GitWeb程序,无需关注
├── HEAD # imp
├── hooks/ # 保存服务端和客户端的git钩子脚本
├── index # imp
├── info/ # 该目录下的exclude文件用于保存不希望配置在.gitignore文件中的忽略模式
│   └── exclude
├── logs/
│   ├── HEAD
│   └── refs/
│   ├── heads/
│   │   └── master
│   └── remotes/
│   └── origin/
│   └── HEAD
├── objects/ # imp, 存储所有的数据内容,包括所有文件的历史版本和commit信息
│   ├── info/
│   └── pack/
│   ├── pack-de504965c4952729b475b8075c814b171ef83bf8.idx
│   └── pack-de504965c4952729b475b8075c814b171ef83bf8.pack
├── packed-refs
└── refs/ # imp
├── heads/
│   └── master
├── remotes/
│   └── origin/
│   └── HEAD
└── tags/

16 directories, 24 files

文件HEAD、index和目录objects、refs是git系统的核心组成部分。

git初始化时的.git目录如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@localhost .git]# tree -F .
.
├── branches/
├── config
├── description
├── HEAD
├── hooks/
├── info/
│   └── exclude
├── objects/
│   ├── info/
│   └── pack/
└── refs/
├── heads/
└── tags/

git对象

数据对象(blob object)

通过 git hash-object 将文件内容写入数据库,生成的就是数据对象,可以通过 git cat-file -t 查看对象类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 创建一个新文件,将文件内容写入数据库
[root@localhost gitNewTest]# echo 'version 1' > text.txt
[root@localhost gitNewTest]# cat text.txt
version 1
# 将文件内容写入数据库,生成一个数据对象
[root@localhost gitNewTest]# git hash-object -w text.txt
83baae61804e65cc73a7201a7252750c76066a30
[root@localhost gitNewTest]# find .git/objects -type f
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
# 修改文件内容,将修改后的文件再次写入数据库,又会生成一个数据对象
[root@localhost gitNewTest]# echo 'version 2' > text.txt
[root@localhost gitNewTest]# git hash-object -w text.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
[root@localhost gitNewTest]# find .git/objects -type f
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
[root@localhost gitNewTest]# cat text.txt
version 2
# 将第一个版本的内容从数据库中读出再次写回文件
[root@localhost gitNewTest]# git cat-file -p 83baae6 > text.txt
[root@localhost gitNewTest]# cat text.txt
version 1
# 查看对象内容均为数据对象
[root@localhost gitNewTest]# git cat-file -t 83baae6
blob
[root@localhost gitNewTest]# git cat-file -t 1f7a7a4
blob

使用上述方式,只能保存文件内容,不能保存文件名称。可以使用下面的树对象实现文件名保存。

树对象(tree object)

git以一种类似Unix文件系统的方式存储内容。所有内容均以树对象和数据对象的形式存储,树对象对应了Unix中的目录项,数据对象大致对应了inodes或文件内容。

一个树对象包含了一条或多条树对象记录(tree entry),每条记录对应一个指向数据对象或子树对象的SHA-1指针,以及对应的模式、类型、文件名信息。

提交对象(commit object)

标签对象(tag object)

参考资料