树对象
接下来要探讨的 Git 对象类型是树对象(tree object),它能解决文件名保存的问题,也允许我们将多个文件组织到一起。 Git 以一种类似于 UNIX 文件系统的方式存储内容,但作了些许简化。 所有内容均以树对象和数据对象(git 对象)的形式存储,其中树对象对应了 UNIX 中的目录项,数据对象(git 对象)则大致上对应文件内容。一个树对象包含了一条或多条记录(每条记录含有一个指向 git 对象或者子树对象的 SHA-1 指针,以及相应的模式、类型、文件名信息)。一个树对象也可以包含另一个树对象。
让我们开始创建树对象
通常,Git 根据某一时刻暂存区(即 index 区域,下同)所表示的状态创建并记录一个对应的树对象, 如此重复便可依次记录(某个时间段内)一系列的树对象。 因此,为创建一个树对象,首先需要通过暂存一些文件来创建一个暂存区。
-
创建暂存区
首先我们准备一个
test.txt文件并将其存入对象数据库中利用
update-index命令 为test.txt文件的首个版本创建一个暂存区。必须为上述命令指定--add选项,因为此前该文件并不在暂存区中(我们甚至都还没来得及创建一个暂存区呢); 同样必需的还有--cacheinfo选项,因为将要添加的文件位于 Git 数据库中,而不是位于当前目录下。 同时,需要指定文件模式、SHA-1 与文件名:
本例中,我们指定的文件模式为 100644,表明这是一个普通文件。 其他选择包括:100755,表示一个可执行文件;120000,表示一个符号链接。
- 查看暂存区
利用 git ls-files -s 命令查看当前的暂存区内容。
- 创建树对象
现在,可以通过 git write-tree 命令将暂存区内容写入一个树对象,并将此树对象存入对象数据库中。 此处无需指定 -w 选项——如果某个树对象此前并不存在的话,当调用此命令时, 它会根据当前暂存区状态自动创建一个新的树对象:
可以看到,返回了与之前不同的树对象的SHA-1值
不妨用之前见过的 git cat-file 命令验证一下它确实是一个树对象,然后再查看一下这个树对象所包含的内容:
再次查看对象数据库,发现其中多了一个树对象的文件(d8)
现在我们将 test.txt 迭代到第二个版本(之前内容是 version 1),并存入对象数据库中:
查看对象数据库,现在其中应该存有 test.txt 文件的第一个版本(83)和第二个版本(1f)以及包含 test.txt 文件的第一个版本的树对象(d8):
接着我们来创建一个新的树对象,它包括 test.txt 文件的第二个版本,以及一个新的文件 new.txt :
先创建一个内容为
new file 的文件 new.txt,然后利用 git update-index 命令将 new.txt 文件塞入暂存区,因为此处是直接引用文件名,所以不用加 --cacheinfo 选项。
此时我们利用 git ls-files -s 命令查看暂存区,可以看到其中除了 test.txt 的第一个版本之外,多出了咱们刚加入的 new.txt。这里要注意,虽然我们在 new.txt 文件创建之后并没有通过 git hash-object 命令将其存入对象数据库中,但我们通过 find .git/objects -type f 命令对数据库进行查看的时候发现,其中已经出现了咱们 new.txt 的相关内容。
可见,对 new.txt 文件使用 git update-index 命令的时候其实已经隐式进行了 git hash-object -w new.txt 这行命令。
随后再将 test.txt 文件的第二个版本塞入暂存区中。
此时的暂存区中存放的就是
new.txt 的第一个版本和 test.txt 的第二个版本了。记录下这个目录树(将当前暂存区的状态记录为一个树对象),然后观察它的结构:
我们注意到,新的树对象包含两条文件记录,同时
test.txt 的 SHA-1 值(1f7a7a)是先前值的 “第二版”
只是为了好玩:你可以将第一个树对象加入第二个树对象,使其成为新的树对象的一个子目录。 通过调用 git read-tree 命令,可以把树对象读入暂存区。 本例中,可以通过对该命令指定 --prefix 选项,将一个已有的树对象作为子树读入暂存区:
如果基于这个新的树对象创建一个工作目录,你会发现工作目录的根目录包含两个文件以及一个名为 bak 的子目录,该子目录包含 test.txt 文件的第一个版本。 可以认为 Git 内部存储着的用于表示上述结构的数据是这样的:
解析树对象
Git 根据某一时刻暂存区(即 index 区域)所表示的状态创建并记录一个对应的树对象,如此重复便可依次记录(某个时间段内)一系列的树对象。其实树对象是对暂存区内操作的抽象,这颗树对象相对于就是快照。当我们的工作区有任何更改同步到暂存区时。便会调用 write-tree 命令通过 write-tree 命令向暂存区内容写入一个树对象。它会根据当前暂存区状态自动创建一个新的树对象。即每一次同步都产生一颗树对象。且该命令会返回一个 hash 指向树对象。在 Git 中每一个文件(数据)都对应一个 hash(类型 blob)每一个树对象都对应一个 hash(类型 tree)
- 总结
我们可以认为树对象就是我们项目的快照
问题
现在有三个树对象(执行了三次 write-tree),分别代表了我们想要跟踪的不同项目快照。然而问题依旧:若想重用这些快照,你必须记住所有三个SHA-1哈希值。并且,你也完全不知道是谁保存了这些快照,在什么时刻保存的,以及为什么保存这些快照。而以上这些,正是提交对象(commit object)能为你保存的基本信息
【YOU DON'T KNOW GIT】系列
- 开始探索Git内部原理
- Git内部原理-Git对象
- Git内部原理-树对象(本文)
- Git内部原理-提交对象
参考资料
1.Pro Git