0%

Git的理解-数据模型

自从git诞生于2005年以来,它就已经在开源环境中大受欢迎,并且我们在工作的时候经常使用它。它是一个很棒的VCS工具,有许多优点,但学习它并不容易。我认为,在我们习惯使用git过程中喜欢它最好的方式就是通过了解它的内部工作方式。就像Edward Thomson所说的一句话:

Git命令只是对数据存储的泄漏抽象

这就我们平常无论记住多少git命令或技巧,但如果不了解git的工作原理,就会时常刚到对git的奇怪方式的困惑。
所以,在对git的理解中,本文将要讲述的第一件事情就是git的核心和灵魂–数据模型

首先,我们可以在项目目录初始化一个空的git仓库。

1
git init

git会自动创建一个.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
.
|-- HEAD
|-- config
|-- description
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- fsmonitor-watchman.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- pre-merge-commit.sample
| |-- pre-push.sample
| |-- pre-rebase.sample
| |-- pre-receive.sample
| |-- prepare-commit-msg.sample
| `-- update.sample
|-- info
| `-- exclude
|-- objects
| |-- info
| `-- pack
|-- refs
| |-- heads
| `-- tags
|-- tree.md
`-- tree1.md

8 directories, 18 files

现在我把重点放在.git/object目录上,然后我们对其进行一些修改。
让我们创建一个index.c文件。

1
touch index.c

给它添加一些内容。

1
2
3
4
5
6
#inlcude <stdio.h>

void main(void)
{
printf("hello world");
}

接着创建一个README.md文件。

1
touch README.md

并提供一些内容。

1
# 说明

现在让我们暂存并提交它们:

1
2
git add .
git commit -m 'init commit'

这时再看一下.git目录,可以看到该.git/objects目录现在包含了一些子目录和文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
.
|-- 5a
| `-- 949ee02ae8c7404c07d2889484b38385e94a45
|-- 66
| `-- 60b9c259d741a73e6a2b8131e8a81cfcfd6e1e
|-- b5
| `-- 58421e928b3a84d9cd8a751b392f37fb9c8cb9
|-- f9
| `-- e56a7eef0c67ccde173a22db35de22cff031e1
|-- info
|-- pack

6 directories, 4 files

(注意:在你的电脑可能会有不同的目录和文件)

现在我们回到.git/objects,请注意每个目录名称都是两个字符。git为每个对象生成一个40个字符的检验和(SHA-1)哈希值,该检验和的前两个字符用作目录名,其它38个字符用作文件(对象)名。

git在提交某些文件创建第一种对象是blob对象,我们的实例是其中的两个,每个提交文件的一个:
Snipaste_2020-05-24_11-49-53.png

它包含我们文件的快照(提交时文件的内容),并具有检验和标头。
git创建的另一种对象是tree对象,在我们的例子中,只有一个,它包含项目中所有文件的列表,以及指向分配给它们blob对象的指针(这是git将文件与其blob对象关联的方式):
Snipaste_2020-05-24_13-14-44.png

最后,git创建一个提交对象,该对象具有一个指向它的tree对象的指针(以及一些其它信息):
Snipaste_2020-05-24_13-52-23.png

现在我们回头看.git/objects目录,思路就比较清楚了。

1
2
3
4
5
6
7
8
9
10
11
12
13
.
|-- 5a
| `-- 949ee02ae8c7404c07d2889484b38385e94a45
|-- 66
| `-- 60b9c259d741a73e6a2b8131e8a81cfcfd6e1e
|-- b5
| `-- 58421e928b3a84d9cd8a751b392f37fb9c8cb9
|-- f9
| `-- e56a7eef0c67ccde173a22db35de22cff031e1
|-- info
|-- pack

6 directories, 4 files

使用git log命令我们可以看到我们提交的历史:

1
2
3
4
5
6
git log
commit 5a949ee02ae8c7404c07d2889484b38385e94a45 (HEAD -> master)
Author: hongzhijun <azjhong1997@gmail.com>
Date: Sun May 24 11:08:35 2020 +0800

init commit

这时我们可以在.git/objects位置找到提交对象:

1
2
3
4
.
|-- objects
| `-- 5a
| `-- 949ee02ae8c7404c07d2889484b38385e94a45

我们不能简单地使用cat命令查看上面的内容,因为这不是纯文本文件,但是我们可以使用cat-file命令查看:

1
git cat-file commit 5a94

可以得到我们提交对象的内容:

1
2
3
4
5
6
git cat-file commit 5a94
tree 6660b9c259d741a73e6a2b8131e8a81cfcfd6e1e
author hongzhijun <azjhong1997@gmail.com> 1590289715 +0800
committer hongzhijun <azjhong1997@gmail.com> 1590289715 +0800

init commit

然后我们可以使用git ls-tree命令查看指向提交的tree对象的指针:

1
git ls-tree 6660

确实包含我们的文件列表,以及指向其blob对象的指针:

1
2
3
git ls-tree 6660
100644 blob b558421e928b3a84d9cd8a751b392f37fb9c8cb9 README.md
100644 blob f9e56a7eef0c67ccde173a22db35de22cff031e1 index.c

我们可以用cat-file命令来查看代表(例如)index.c的blob对象:

1
git cat-file blob f9e5

我们看到了我们index.c文件包含的内容:

1
2
3
4
5
6
7
$ git cat-file blob f9e5
#include <stdio.h>

void main(void)
{
printf("hello world");
}

以上就是我们创建和提交一些文件发生的情况。

现在,我们做另一次提交,这次我们对我们的index.c文件做一些修改(添加一些代码修改)并提交这些修改:
Snipaste_2020-05-24_13-54-23.png

正如我们所看到的,git已经用index.c的一个新的快照创建一个新的blob对象。因为README.md文件没有修改,git就没有为它创建新的blob对象,git会重现现有的那一个。

现在,当git创建一个tree对象时,分配给index.c文件的blob指针将会更新,分配给README.md文件的blob指针会与先前提交的tree中保持一样。
Snipaste_2020-05-24_14-07-31.png

最后,git会创建一个commit对象,该对象具有指向tree对象的指针。
Snipaste_2020-05-24_24-14-56.png
以及指向其父提交对象的指针(除第一个提交外,每个提交都至少具有一个父提交)。

现在我们知道git如何处理文件的添加和编辑,剩下的唯一的事情就是它如何删除文件:
Snipaste_2020-05-24_14-28-11.png
这很简单,git会从tree对象中删除文件条目(带指针到blob对象的文件名)。在这个例子中,我们删除了提交中的index.php,所以在提交的tree对象中不再有index.php条目 (换句话说,我们的提交的tree对象中不再有一个指针指向代表index.php的blob对象)。

我们在这个数据模型上还有一个补充–tree对象可以嵌套(它们可以指向其他tree对象)。你可以这样想:每个blob对象代表一个文件,每个tree对象代表一个目录,所以如果我们有嵌套的目录,就会有嵌套的tree对象。

让我们看一个例子:
Snipaste_2020-05-24_14-31-27.png
这里,我们的项目将有一个README.md文件和一个app目录下的两个文件(app.php和app_dev.php)。

Git使用blob对象来重现我们在任何给定时间点(提交)的文件内容,而tree对象来重现我们项目的文件夹结构。

以上是对git数据模型的一些理解和总结。

^_^