树对象(tree object),它能解决文件名保存的问题,也允许我们将多个文件组织到一起。
Git 以一种类似于 UNIX 文件系统的方式存储内容,但作了些许简化。
所有内容均以树对象和数据对象的形式存储,其中树对象对应了 UNIX 中的目录项,数据对象则大致上对应了 inodes 或文件内容。 一个树对象包含了一条或多条树对象记录(tree entry),每条记录含有一个指向数据对象或者子树对象的 SHA-1 指针,以及相应的模式、类型、文件名信息。
通常,Git 根据某一时刻暂存区(即 index 区域,下同)所表示的状态创建并记录一个对应的树对象, 如此重复便可依次记录(某个时间段内)一系列的树对象。 因此,为创建一个树对象,首先需要通过暂存一些文件来创建一个暂存区。
以通过底层命令 git update-index 为一个单独文件——我们的 test.txt 文件的首个版本——创建一个暂存区。
利用该命令,可以把 test.txt 文件的首个版本人为地加入一个新的暂存区。 必须为上述命令指定 —add 选项,因为此前该文件并不在暂存区中(我们甚至都还没来得及创建一个暂存区呢); 同样必需的还有 —cacheinfo 选项,因为将要添加的文件位于 Git 数据库中,而不是位于当前目录下。 同时,需要指定文件模式、SHA-1 与文件名:
$ git update-index --add --cacheinfo 100644 a5bce3fd2565d8f458555a0c6f42d0504a848bd5 test.txt
:::info 创建一个暂存区并将数据库中test.txt的版本1加入该暂存区,同时需要指明文件模式与文件名 ::: 本例中,我们指定的文件模式为 100644,表明这是一个普通文件。 其他选择包括:100755,表示一个可执行文件;120000,表示一个符号链接。 这里的文件模式参考了常见的 UNIX 文件模式,但远没那么灵活——上述三种模式即是 Git 文件(即数据对象)的所有合法模式(当然,还有其他一些模式,但用于目录项和子模块)。
通过 git write-tree 命令将暂存区内容写入一个树对象。 此处无需指定 -w 选项——如果某个树对象此前并不存在的话,当调用此命令时, 它会根据当前暂存区状态自动创建一个新的树对象:
$ git write-tree
a376c417225045e853f785ef73ecd0b10291e1b9
$ git cat-file -p a376c417225045e853f785ef73ecd0b10291e1b9
100644 blob a5bce3fd2565d8f458555a0c6f42d0504a848bd5 test.txt
$ git cat-file -t a376c417225045e853f785ef73ecd0b10291e1b9
tree
接着我们来创建一个新的树对象,它包括 test.txt 文件的第二个版本,以及一个新的文件:
$ git update-index --add --cacheinfo 100644 180cf8328022becee9aaa2577a8f84ea2b9f3827 test.txt
$ git update-index --add new.txt
暂存区现在包含了 test.txt 文件的新版本,和一个新文件:new.txt。 记录下这个目录树(将当前暂存区的状态记录为一个树对象),然后观察它的结构:
$ git write-tree
22958f74c92ee3d5281002141288eca069c75b86
$ git cat-file -p 22958f74c92ee3d5281002141288eca069c75b86
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 180cf8328022becee9aaa2577a8f84ea2b9f3827 test.txt
只是为了好玩:可以将第一个树对象加入第二个树对象,使其成为新的树对象的一个子目录。 通过调用 git read-tree 命令,可以把树对象读入暂存区。 本例中,可以通过对该命令指定 —prefix 选项,将一个已有的树对象作为子树读入暂存区:
$ git read-tree --prefix=bak a376c417225045e853f785ef73ecd0b10291e1b9
$ git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: bak/test.txt
new file: new.txt
new file: test.txt
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: bak/test.txt
$ git write-tree
3387740527358bcad3ab519369794c8cc2c15673
$ git cat-file -p 3387740527358bcad3ab519369794c8cc2c15673
040000 tree a376c417225045e853f785ef73ecd0b10291e1b9 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 180cf8328022becee9aaa2577a8f84ea2b9f3827 test.txt