1、Tree对象介绍
接下来要探讨的 Git 对象类型是树对象(tree object
),它能解决文件名保存的问题。tree
对象可以存储文件名,也允许我们将多个文件组织到一起。
Git以一种类似于UNIX文件系统的方式存储内容,但做了一些简化。所有内容均以树(tree
)对象和数据(blob
)对象的形式存储,其中树对象对应了UNIX中的目录项,数据对象blob
则大致上对应了文件中的内容。
一个树对象可以包含一条或多条记录(tree
对象和blob
对象),每条记录含有一个指向blob
对象或者子tree
对象的SHA-1
指针,以及相应的模式、类型、文件名信息。
如下图:
1
| 100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
|
Tree对象存储方式如下图所示:

2、Tree对象说明
2.1. (1)初始化一个新的本地版本库
1 2
| ➜ gitObject git:(master) git init gitObject && cd gitObjec
|
(2)创建一个树对象(重点)
1)新建一个文件,然后把文件提交到本地版本库。
例如:新建文件test.txt
,文件内容version 1
。
1 2 3 4 5
| ➜ gitObject git:(master) echo "version 1" >> test.txt
➜ gitObject git:(master) ✗ cat test.txt
version 1
|
2)把test.txt
文件,提交到本地版本库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ➜ gitObject git:(master) ✗ git hash-object -w ./test.txt
83baae61804e65cc73a7201a7252750c76066a30
➜ gitObject git:(master) ✗ find .git/objects -type f
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
➜ gitObject git:(master) ✗ git cat-file -t 83ba
blob
➜ gitObject git:(master) ✗ git cat-file -p 83ba
version 1
|
以上就和我们讲blob
对象的操作一样。
此时test.txt
文件被管理在Git本地版本库中。
3)创建一个树对象。
通常Git是根据暂存区或者索引文件index来创建tree对象,因此要把文件存储到暂存区进并建立index
文件。
提示1:
index
文件在.git
目录中,最新初始化的Git本地仓库是没有index
文件,只有添加过一次数据到暂存区之后,才会在.git
目录中自动生成index
文件。
新初始化的.git
目录内容如下:是没有index
文件的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ➜ gitObject git:(master) ✗ ll .git/
total 24
-rw-r--r-- 1 taylor staff 23B 9 16 11:45 HEAD
-rw-r--r-- 1 taylor staff 137B 9 16 11:45 config
-rw-r--r-- 1 taylor staff 73B 9 16 11:45 description
drwxr-xr-x 15 taylor staff 480B 9 16 11:45 hooks
drwxr-xr-x 3 taylor staff 96B 9 16 11:45 info
drwxr-xr-x 5 taylor staff 160B 9 16 11:48 objects
drwxr-xr-x 4 taylor staff 128B 9 16 11:45 refs
|
提示2:
可以通过git ls-files
命令查看暂存区的文件信息。
参数信息如下,括号中简写:
--cached(-c)
: 查看暂存区中文件。git ls-files
命令默认执行此选项。--midified(-m)
:查看修改的文件。--delete(-d)
:查看删除过的文件。--other(-o)
:查看没有被Git跟踪的文件。--stage(-s)
:显示mode以及文件对应的Blob对象,进而我们可以获取暂存区中对应文件里面的内容。stage:阶段编号,普通(未合并)文件通常为0。
例如:git ls-files -c
或者git ls-files --cached
(其他命令同理)
我们常用git ls-files -s
命令查看暂存区的文件信息。
接下来,我们可以通过底层命令:update-index
、write-tree
、read-tree
等命令,轻松创建自己的tree
对象。
1 2 3 4 5 6 7 8 9
| ➜ gitObject git:(master) ✗ git ls-files -s
➜ gitObject git:(master) ✗ git update-index --add --cacheinfo 100644 \
83baae61804e65cc73a7201a7252750c76066a30 test.txt
➜ gitObject git:(master) ✗ git ls-files -s
100644 83baae61804e65cc73a7201a7252750c76066a30 0 test.txt
|
命令说明:
- 为创建一个树对象,首先需要通过暂存一些文件到暂存区。
通过底层命令 git update-index
将一个单独文件存入暂存区中。 --add
选项:因为此前该文件并不在暂存区中,一个文件首次添加到暂存区,需要使用--add
选项。--cacheinfo
选项:因为要添加的test.txt
文件位于Git 数据库中(上一步的操作),而不是位于当前工作目录,所以需要--cacheinfo
选项。- 最后需要指定
文件模式
、SHA-1
与文件名
。
文件模式说明:
100644
:表明这是一个普通文件。(blob对象的文件模式一般都为100644)100755
:表示一个可执行文件。120000
:表示一个符号链接。
继续,下面来观察生成的树对象::
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 34 35 36 37 38 39 40 41
| ➜ gitObject git:(master) ✗ ll .git/
total 32
-rw-r--r-- 1 taylor staff 23B 9 16 11:45 HEAD
-rw-r--r-- 1 taylor staff 137B 9 16 11:45 config
-rw-r--r-- 1 taylor staff 73B 9 16 11:45 description
drwxr-xr-x 15 taylor staff 480B 9 16 11:45 hooks
-rw-r--r-- 1 taylor staff 104B 9 16 11:52 index
drwxr-xr-x 3 taylor staff 96B 9 16 11:45 info
drwxr-xr-x 5 taylor staff 160B 9 16 11:48 objects
drwxr-xr-x 4 taylor staff 128B 9 16 11:45 refs
➜ gitObject git:(master) ✗ find .git/objects -type f
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
➜ gitObject git:(master) ✗ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
➜ gitObject git:(master) ✗ find .git/objects -type f
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
➜ gitObject git:(master) ✗ git cat-file -t d832
tree
➜ gitObject git:(master) ✗ git ls-files -s
100644 83baae61804e65cc73a7201a7252750c76066a30 0 test.txt
|
4)总结
以上就是在Git中,使用底层命令手动创建一个树对象的过程。
- 创建一个文件,把该文件通过
git hash-object
命令存储到本地版本库中。 - 通过
git update-index
命令,把文件存储到暂存区中。 - 通过
git write-tree
命令,把暂存区中的文件索引信息提交到本地版本库,生成了一个树对象。
(3)创建第二个文件(重点)
1)新增new.txt
文件,并修改test.txt
文件内容。
1 2 3 4 5 6 7 8 9 10 11 12 13
| ➜ gitObject git:(master) ✗ echo "new file" > new.txt
➜ gitObject git:(master) ✗ echo "version 2" >> test.txt
➜ gitObject git:(master) ✗ cat new.txt
new file
➜ gitObject git:(master) ✗ cat test.txt
version 1
version 2
|
2)将new.txt
文件和test.txt
文件的第二个版本添加到暂存区。
将test.txt
文件添加到暂存区。
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
| ➜ gitObject git:(master) ✗ git ls-files -s
100644 83baae61804e65cc73a7201a7252750c76066a30 0 test.txt
➜ gitObject git:(master) ✗ git hash-object -w ./test.txt
0c1e7391ca4e59584f8b773ecdbbb9467eba1547
➜ gitObject git:(master) ✗ git ls-files -s
100644 83baae61804e65cc73a7201a7252750c76066a30 0 test.txt
➜ gitObject git:(master) ✗ find .git/objects -type f
.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
➜ gitObject git:(master) ✗ git update-index --cacheinfo 100644 \
0c1e7391ca4e59584f8b773ecdbbb9467eba1547 test.txt
➜ gitObject git:(master) ✗ git ls-files -s
100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 0 test.txt
|
将new.txt
文件添加到暂存区。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ➜ gitObject git:(master) ✗ git update-index --add new.txt
➜ gitObject git:(master) ✗ find .git/objects -type f
.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
➜ gitObject git:(master) ✗ git ls-files -s
100644 fa49b077972391ad58037050f2a75f74e3671e92 0 new.txt
100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 0 test.txt
|
说明:git update-index --add 文件名
完成了之前的两步操作。
- 把
new.txt
文件内容存入了Git版本库。 - 把
new.txt
文件添加到了暂存区中。
3)把暂存区的内容提交的本地版本库。
此时工作目录和暂存区中的文件状态是一样的, 可以通过git write-tree
命令提交到本地版本库,生成树对像了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ➜ gitObject git:(master) ✗ git write-tree
163b45f0a0925b0655da232ea8a4188ccec615f5
➜ gitObject git:(master) ✗ find .git/objects -type f
.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects/16/3b45f0a0925b0655da232ea8a4188ccec615f5
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
➜ gitObject git:(master) ✗ git cat-file -t 163b
tree
|
此时Git版本库中的5个对象,即表示了项目的2个版本。(不明白这句话?继续往下看)
(4)将第一个树对象加入暂存区,使其成为新的树对
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ➜ gitObject git:(master) ✗ git ls-files -s
100644 fa49b077972391ad58037050f2a75f74e3671e92 0 new.txt
100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 0 test.txt
➜ gitObject git:(master) ✗ git read-tree --prefix=bak d832
➜ gitObject git:(master) ✗ git ls-files -s
100644 83baae61804e65cc73a7201a7252750c76066a30 0 bak/test.txt
100644 fa49b077972391ad58037050f2a75f74e3671e92 0 new.txt
100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 0 test.txt
|
说明:
read-tree
命令:可以把树对象读入暂存区。--prefix=bak
选项:将一个已有的树对象作为子树读入暂存区。- 我们可以回忆一下,tree d832是什么时候write出来的,write的时候暂存区里有什么?然后看看read到暂存区之后,多出来的第一行数据,是不是就是当时生成tree d832时暂存区的内容
接下来继续,再提交暂存区的内容,会继续生成一个新的tree
对象在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
| ➜ gitObject git:(master) ✗ git write-tree
01ab2a43b1eb150bcf00f375800727df240cf653
➜ gitObject git:(master) ✗ git cat-file -t 01ab
tree
➜ gitObject git:(master) ✗ git cat-file -p 01ab
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 test.txt
➜ gitObject git:(master) ✗ find .git/objects -type f
.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects/16/3b45f0a0925b0655da232ea8a4188ccec615f5
.git/objects/01/ab2a43b1eb150bcf00f375800727df240cf653
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
|
到这里我们的演示就完成了,请看下面的总结。
3、总结
3.1. (1)分析每个树对象的存储结构
我们可以先查看一下Git本地库中的对象,如下
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
|
我们接下来用三个图,描述一下三个树对象的结构关系。
第一个树对象结构如下图:


第二个树对象结构如下图:


第三个树对象结构如下图:


也可以换Git对象类型表示:

(2)blob对象和tree对象(重点)
从上图我们可以分析出:
blob
对象代表文件一次次的版本。tree
对象代表项目的一次次的版本。
这就是我前面2-(3)
描述过的Git版本库中的5个对象,即表示了项目的2个版本。
(就先这样理解)
(3)总结(重点)
暂存区的概念和相关理解:
- 所谓的暂存区
Stage
只是一个简单的索引文件而已。指的是是 .git/index
文件。 - 暂存区这个索引文件里面包含的是文件的目录树,像一个虚拟的工作区,在这个虚拟工作区的目录树中,记录了文件名、文件的时间戳、文件长度、文件类型以及最重要的SHA-1值,文件的内容并没有存储在其中,所以说它像一个虚拟的工作区。
即:暂存区,也就是.git/index
文件中存放的是文件内容的索引(快照),也可以是tree
对象的索引。 - 索引指向的是
.git/objects/
目录下的文件(Git对象)。 - Git通过暂存区的文件索引信息来创建
tree
对象的。 tree
对象可以使文件内容和文件名进行关联。- 一个树对象可以包含一条或多条记录(
tree
对象和blob
对象)。 - 暂存区内容写到版本库中后,暂存区索引内容不清空。
- 暂存区中的文件内容索引,是按对应文件覆盖的,也就是修改一个文件内容,添加到缓存区,只会把对应的文件覆盖,其他文件不会被覆盖,即:暂存区不是整体覆盖的。
暂存区的作用:除非是绕过暂存区直接提交,否则Git想把修改提交上去,就必须将修改存入暂存区最后才能commit。每次提交的是暂存区所对应的文件快照。
提示:Git对象的hash键,我们截取前几位就行,练习时对象不那么对,就不用全部都写,能够表示唯一对象就行。
4、问题
现在有三个树对象(因为执行了三次write-tree
命令),分别代表了我们想要跟踪项目的三次快照。然而问题依旧:若想重用这些快照,你必须记住这三个树对象的SHA-1
哈希值。
并且,你也完全不知道是谁保存了这些快照,在什么时刻保存的,以及为什么保存这些快照。
而以上这些,提交对象commit object
为你保存了这些基本信息。
5、本文用到的命令总结
Git底层命令:
git update-index --add
:把文件索引(快照)存入暂存区中。git write-tree
:将当前暂存区的索引内容同步到一个树对象中。git ls-files -s
:查看暂存区的文件信息。git read-tree --prefix=bak
:将一个已存在的树对象添加到暂存区。git cat-file -t 键
:查看Git对象的类型。git cat-file -p 键
:查看Git对象的内容。
6、演示示例代码
写文件到本地版本库
1 2 3 4 5 6 7 8 9 10 11
| cd ~ && rm -rf ~/gitObject
git init gitObject && cd gitObject
echo "version 1" >> test.txt
cat test.txt
find .git/objects -type f
git hash-object -w ./test.txt
|
将本地版本库文件放入暂存区
1 2 3 4 5 6 7 8 9
| git cat-file -t 83ba
git cat-file -p 83ba
ll .git/
git ls-files -s
git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt
|
根据暂存区创建树对象
1 2 3 4 5 6 7 8 9 10 11 12 13
| git ls-files -s
ll .git/
find .git/objects -type f
git write-tree
find .git/objects -type f
git cat-file -t d832
git ls-files -s
|
新增文件放入版本库
1 2 3 4 5 6 7 8 9 10 11
| echo "new file" > new.txt
echo "version 2" >> test.txt
cat new.txt
cat test.txt
git ls-files -s
git hash-object -w ./test.txt
|
更新暂存区已有文件
1 2 3 4 5
| git ls-files -s
find .git/objects -type f
git update-index --cacheinfo 100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 test.txt
|
新文件加入暂存区
1 2 3 4 5 6 7
| git ls-files -s
git update-index --add new.txt
find .git/objects -type f
git ls-files -s
|
创建第二棵树对象
1 2 3 4 5 6 7
| git write-tree
find .git/objects -type f
git cat-file -t 163b
git ls-files -s
|
创建第三棵树对象
1 2 3 4 5 6 7 8 9 10 11
| git read-tree --prefix=bak d832
git ls-files -s
git write-tree
git cat-file -t 01ab
git cat-file -p 01ab
find .git/objects -type f
|
参考
https://www.cnblogs.com/liuyuelinfighting/p/16189429.html