1. Commit对象介绍

现在来介绍最后一种Git对象commit对象,也叫提交对象。

提交对象可以理解为是对树对象的一层封装,提交信息包括基于当前暂存区中索引文件生成的tree对象,还有包含了提交时间,提交者信息,作者信息,以及提交备注等内容,更重要的是里面还包含了父提交的ID(第一次提交没有父提交ID),由此就可以形成Git提交的有向无环图。(是链式的关系,把所有commit对象关联起来)

即:commit对象通常指向一个 tree 对象,并且封装了文件的提交时间,提交者信息,作者信息,提交备注,以及父提交引用等数据。

下面是commit对象的存储结构:

2. Commit对象说明

我们通过练习来说明commit对象,接着用前面Tree对象的本地版本库。

(1)创建一个commit对象

我们可以通过调用commit-tree命令创建一个提交对象,为此需要指定一个树对象的SHA-1值,以及该提交的父提交对象。

说明:使用commit-tree命令来创建提交对象,一般都需要和父提交进行关联,如果是第一次将暂存区的文件索引数据提交到本地版本库,那么该提交操作就不需要指定父提交对象。

1)我们可以先查看一下此时Git本地库中的对象,如下
演示代码请看上一节Git原理--3、Git对象和存储原理-Tree对象

1
2
3
4
5
6
.git/objects/01/ab2a43b1eb150bcf00f375800727df240cf653 
.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects/16/3b45f0a0925b0655da232ea8a4188ccec615f5
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92

2)我们通过第一个树对象,创建一个commit对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
➜  gitObject git:(master) ✗ echo 'first commit' | git commit-tree d8329f

8683a3799e49f9fd1553391d56a816fa11953fec

➜  gitObject git:(master) ✗ git cat-file -p 8683

tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579

author luoweile <luoweile2008@126.com> 1663760954 +0800

committer luoweile <luoweile2008@126.com> 1663760954 +0800



first commit

说明:

  • tree:表示该commit对象所指向的tree对象的索引
  • author:表示该文件的作者。
  • committer:表示该文件的提交者。
  • first commit:这段文本是提交备注。(备注与前面留空一行)
  • 因为是第一次进行commit提交操作,所以没有父提交信息。
  • 1618190880 +0800:表示时间,一个时间戳。

即:commit对象的格式很简单:指明了该时间点项目快照的顶层树对象、作者/提交者信息(从 Git 设置的 user.nameuser.email中获得),以及当前时间戳、留空一行,最后是提交注释。

提示:git commit-tree命令不但生成了提交对象,而且会将对应的快照(树对象)提交到本地库中。

因为加了时间戳,所以每次执行,commit对象的SHA-1是不一样的

(2)创建第二个commit对象

根据第二个tree对象和第一个commit对象,来创建第二个commit对象。

通过-p选项指定父提交对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
➜  gitObject ➜  gitObject git:(master) ✗ echo 'second commit' | git commit-tree 163b45f0a09 -p 8683

4564e68bd6d9fd6ce818ddbc8189aebb5ff07352

➜  gitObject git:(master) ✗ git cat-file -p 4564

tree 163b45f0a0925b0655da232ea8a4188ccec615f5

parent 8683a3799e49f9fd1553391d56a816fa11953fec

author luoweile <luoweile2008@126.com> 1663761236 +0800

committer luoweile <luoweile2008@126.com> 1663761236 +0800



second commit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
➜  gitObject git:(master) ✗ echo 'third commit' | git commit-tree 01ab -p 4564 

7fa009de98135bfdbceadd8c68f9caa056215d01

➜  gitObject git:(master) ✗ git cat-file -p 7fa0

tree 01ab2a43b1eb150bcf00f375800727df240cf653

parent 4564e68bd6d9fd6ce818ddbc8189aebb5ff07352

author luoweile <luoweile2008@126.com> 1663761401 +0800

committer luoweile <luoweile2008@126.com> 1663761401 +0800



third commit

提交对象的格式很简单:

它先指定一个顶层树对象,代表当前项目快照;

然后是可能存在的父提交;

之后是作者/提交者信息(依据你的 user.nameuser.email 配置来设定,外加一个时间戳);

留空一行,最后是提交注释。

3. 本地库中对象之间的关系

我们可以查看一下此时Git本地库中的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
➜  gitObject git:(master) ✗ find .git/objects -type f

.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547

.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579

.git/objects/16/3b45f0a0925b0655da232ea8a4188ccec615f5

.git/objects/45/64e68bd6d9fd6ce818ddbc8189aebb5ff07352

.git/objects/86/83a3799e49f9fd1553391d56a816fa11953fec

.git/objects/01/ab2a43b1eb150bcf00f375800727df240cf653

.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92

.git/objects/83/baae61804e65cc73a7201a7252750c76066a30

.git/objects/7f/a009de98135bfdbceadd8c68f9caa056215d01

可以从上面看到,此时的本地版本库中共有9个对象,三个blob对象,三个tree对象,三个commit对象。

他们之间的关系如下图:

4. 总结

  1. 提交是我们经常使用的Git动作,每次提交操作都指向一个树对象,同时会产生一个commit对象。
    即:一个commit对象包含了一个tree对象,这个tree对象记录了在那个时间点,项目包含了什么文件夹和什么文件。
  2. 一个提交对象可以有一个或者多个父提交。
  3. 每次commit操作都会基于当前索引文件index新建tree对象。那么当前索引文件,是在上次提交的基础上更新来的,所以每次提交产生的commit对象,与其他的commit对象,都有前后关系或者称为父子关系。
  4. 对于我们来说,不需要直接访问blob对象和tree对象,我们直接访问commit对象就可以了。
    即:commit对象对应的tree对象下面,又包含了小的tree对象和blob对象,子的tree对象一层层展开,最后叶子节点就是一个个blob对象,也就是一个个文件。

到这里,我们就能够清楚的了解,什么叫一个Git版本。tree对象才是一次项目版本的快照,提交对象是对tree对象的一次封装。

即:

  • 项目的快照就是一个树对象。
  • 项目的版本就是一个提交对象。

而且Git的每一个版本,存储的不是增量,而存储的是当前项目的快照。同时objects目录中相当于存放了项目的所有历史记录,回滚就相当的方便了,找到对应的commit对象的hash就可以了。

5. 练习

请问下图中包含多少个tree对象和blob对象?

一共包含两个tree对象,一个blob对象,一个commit对象。

说明:

  • 一个commit对象一定对应一个tree对象(这个tree对象应该是一个完整项目仓库的快照)
  • doc目录下有一个blob对象,也就是readme文件。

6. 命令总结

Git底层命令:

  • git commit-tree:生成一个commit对象。
  • git cat-file -t 键:查看Git对象的类型。
  • git cat-file -p 键:查看Git对象的内容。

7. 参考

https://www.cnblogs.com/liuyuelinfighting/p/16198051.html