四种数据类型
实际上Git基于数据类型的不同,把对象分为四种:数据对象、树对象、提交对象、标签对象。Git文件系统的设计思路与linux文件系统相似,即将文件的内容与文件的属性分开存储,文件内容以“装满字节的袋子”存储在文件系统中,文件名、所有者、权限等文件属性信息则另外开辟区域进行存储。在Git中,数据对象相当于文件内容,树对象相当于文件目录树,提交对象则是对文件系统的快照,标签对象则是对提交信息的引用。
下面我们分别对每种对象进行说明(建议大家操作的时候观察一下.git/objects目录的变化)
数据对象
数据对象是文件的内容,不包括文件名、权限等信息。Git会根据文件内容计算出一个hash值,以hash值作为文件索引存储在Git文件系统中。由于相同的文件内容的hash值是一样的,因此Git将同样内容的文件只会存储一次。git hash-object可以用来计算文件内容的hash值,并将生成的数据对象存储到Git文件系统中:
$ echo 'version 1' | git hash-object -w --stdin
83baae61804e65cc73a7201a7252750c76066a30
$ echo 'version 2' | git hash-object -w --stdin
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
$ echo 'new file' | git hash-object -w --stdin
fa49b077972391ad58037050f2a75f74e3671e92
上面示例中,-w表示将数据对象写入到Git文件系统中,如果不加这个选项,那么只计算文件的hash值而不写入;–stdin表示从标准输入中获取文件内容,当然也可以指定一个文件路径代替此选项。上面讲数据对象写入到Git文件系统中,那如何读取数据对象呢?git cat-file
可以用来实现所有Git对象的读取。
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30
version 1
$ git cat-file -t 83baae61804e65cc73a7201a7252750c76066a30
blob
上面示例中,-p表示查看Git对象的内容,-t表示查看Git对象的类型。
我们能够对Git文件系统中的数据对象进行读写。但是,我们需要记住每一个数据对象的hash值,才能访问到Git文件系统中的任意数据对象,这显然是不现实的。数据对象只是解决了文件内容存储的问题,而文件名的存储则需要通过树对象来解决。
树对象
树对象是文件目录树,记录了文件获取目录的名称、类型、模式信息。使用git update-index可以为数据对象指定名称和模式,然后使用git write-tree将树对象写入到Git文件系统中:
$ git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
–add表示新增文件名,如果第一次添加某一文件名,必须使用此选项;–cacheinfo <mode> <object> <path>是要添加的数据对象的模式、hash值和路径,<path>意味着为数据对象不仅可以指定单纯的文件名,也可以使用路径。另外要注意的是,使用git update-index添加完文件后,一定要使用git write-tree写入到Git文件系统中,否则只会存在于index区域。此时的效果类似于执行了git add
命令。
树对象仍然可以使用git cat-file查看:
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree
树对象解决了文件名的问题,而且,由于我们是分阶段提交树对象的,树对象可以看做是开发阶段源代码目录树的一次次快照,因此我们可以是用树对象作为源代码版本管理。但是,这里仍然有问题需要解决,即我们需要记住每个树对象的hash值,才能找到个阶段的源代码文件目录树。在源代码版本控制中,我们还需要知道谁提交了代码、什么时候提交的、提交的说明信息等,接下来的提交对象就是为了解决这个问题的。
提交对象
提交对象是用来保存提交的作者、时间、说明这些信息的,可以使用git commit-tree来将提交对象写入到Git文件系统中:
$ echo 'first commit' | git commit-tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
db1d6f137952f2b24e3c85724ebd7528587a067a
上面commit-tree除了要指定提交的树对象,也要提供提交说明,至于提交的作者和时间,则是根据环境变量自动生成,并不需要指定。这里需要提醒一点的是,读者在测试时,得到的提交对象hash值一般和这里不一样,这是因为提交的作者和时间是因人而异的。此时相当于执行了git commit
提交对象的查看,也是使用git cat-file:
$ git cat-file -p d555a9dfc304e74a9557a5d92dc0807c20765104
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author defwang <wangdefu@100tal.com> 1607259906 +0800
committer defwang <wangdefu@100tal.com> 1607259906 +0800
first commit
$ git cat-file -t bb4de00acf2b259e767d8321e0fa3c865dae780e
commit
上面是属于首次提交,那么接下来的提交还需要指定使用-p指定父提交对象,这样代码版本才能成为一条时间线:
$ git update-index --add --cacheinfo 100644 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test2.txt
$ git write-tree
e6cebf205657fb2046b5f38877bbfa8c3ae2d05c
$ echo 'second commit' | git commit-tree e6cebf205657fb2046b5f38877bbfa8c3ae2d05c -p d555a9dfc304e74a9557a5d92dc0807c20765104
bb4de00acf2b259e767d8321e0fa3c865dae780e
使用git cat-file查看一下新的提交对象,可以看到相比于第一次提交,多了parent部分:
$ git cat-file -p bb4de00acf2b259e767d8321e0fa3c865dae780e
tree e6cebf205657fb2046b5f38877bbfa8c3ae2d05c
parent d555a9dfc304e74a9557a5d92dc0807c20765104
author defwang <wangdefu@100tal.com> 1607260467 +0800
committer defwang <wangdefu@100tal.com> 1607260467 +0800
second commit
使用git log可以查看整个提交历史:
$ git log --stat bb4de00acf2b259e767d8321e0fa3c865dae780e
commit bb4de00acf2b259e767d8321e0fa3c865dae780e
Author: defwang <wangdefu@100tal.com>
Date: Sun Dec 6 21:14:27 2020 +0800
second commit
test2.txt | 1 +
1 file changed, 1 insertion(+)
commit d555a9dfc304e74a9557a5d92dc0807c20765104
Author: defwang <wangdefu@100tal.com>
Date: Sun Dec 6 21:05:06 2020 +0800
first commit
test.txt | 1 +
1 file changed, 1 insertion(+)
有时候我们为了方便记忆某一次提交,会对该提交打一个tag进行标记,这就产生了标签对象
标签对象
$ git tag -a v1.0.0 bb4de00acf2b259e767d8321e0fa3c865dae780e -m "test tag"
当执行上面命令之后,.git目录下的refs目录中就会多一个文件
refs/tags
└── v1.0.0
文件内容为为40位的hash码, 同样我们使用git cat-file
读取该hash值
$ git cat-file -p 4599faa3ffb8042b85fce6f80cad2633b4ee01ff
object bb4de00acf2b259e767d8321e0fa3c865dae780e
type commit
tag v1.0.0
tagger defwang <wangdefu@100tal.com> 1607262079 +0800
test tag
git cat-file -t 4599faa3ffb8042b85fce6f80cad2633b4ee01ff
tag
可见该对象的类型为tag
至此,我们已经知道了git的四种数据类型,blob、tree、commit、tag
Git中的数据对象解决了数据存储的问题,树对象解决了文件名存储问题,提交对象解决了提交信息的存储问题,标签对象则是对提交对象进行更形象的命名,从Git设计中可以看出,Linus对一个源代码版本控制系统做了很好的抽象和解耦,每种对象解决的问题都很明确,相比于使用一种数据结构,无疑更灵活和更易维护。