本篇博客我们来讨论一下git的对象库。
开始之前呢,我们通过git init命令初始化一个对象库。然后,新增一个文件a.txt,并写入一个一句话“hello,git”。
git add a.txt
git commit -m 'add a.txt'
接下来,在根目录新建一个文件夹叫[folder1],在folder1下新建一个文件b.txt,并写入一句话”hello,b.txt”。然后,再修改a.txt,加入一句话”hello,a.txt”。
通过一下命令将a.txt和b.txt加入暂存区并提交
git add a.txt folder1/b.txt
git commit -m 'add b.txt and modify a.txt'
下面就开始今天的讨论了。我们先思考一下这个问题:既然git作为一个版本管理工具,可以记录下我们每次提交的文件内容,并且允许我们回到任意一次提交来看看当时每个文件的样子,那么,git是如何存储每次提交的文件呢?
通过git log --pretty=raw
查看一下当前的log,会得到一下输出
$ git log --pretty=raw
commit 31a9707cfa227a20ff62c87701af054435653633
tree 60b5b8c3452dbc636f1eb8bcbed82aab9ee619cb
parent 1f16e7a88f22daba43220093f0c3f25bdb73299d
author zdk <zdk@menhoo.com> 1497516908 +0800
committer zdk <zdk@menhoo.com> 1497516908 +0800
add b.txt and modify a.txt
commit 1f16e7a88f22daba43220093f0c3f25bdb73299d
tree f8201282dc9d2215f29421ea38b9702082b218a8
author zdk <zdk@menhoo.com> 1497516613 +0800
committer zdk <zdk@menhoo.com> 1497516613 +0800
add a.txt
这些40位的哈希值,到底是什么东西呢?git提供了cat-file
命令来查看git对象,参数-t会输出git对象的类型,-p参数会输出git对象的内容。
下面先看一下第二次提交的tree这个哈希值对应的内容
$ git cat-file -t 60b5b8c3452dbc636f1eb8bcbed82aab9ee619cb
tree
$ git cat-file -p 60b5b8c3452dbc636f1eb8bcbed82aab9ee619cb
100644 blob 97c3279503234b905d2f51b0dc148bcc064df6d8 a.txt
040000 tree 67d684c6a7cfdf08beaf30411af3ae1144b0713d folder1
git告诉我们,60b5b8c3452dbc636f1eb8bcbed82aab9ee619cb这个id的类型是tree,内容包含了两个文件,以及这两个文件的id。
我们接着来再看一下a.txt中的内容
$ git cat-file -p 97c3279503234b905d2f51b0dc148bcc064df6d8
hello,git
hello,a.txt
哦?一不小心看到了这次提交的内容。
由于这两次提交时我都修改了a.txt,那么,我第一次提交时的a.txt的内容应该也可以看到。
通过log可以看出,第一次提交时的文件树的id是f8201282dc9d2215f29421ea38b9702082b218a8,那我们就来扒一扒这个id下的内容
$ git cat-file -p f8201282dc9d2215f29421ea38b9702082b218a8
100644 blob f28ffa36cdf69904e516babfdb3005e108dddfb7 a.txt
找到了第一次提交时的文件树中的a.txt的id
$ git cat-file -p f28ffa36cdf69904e516babfdb3005e108dddfb7
hello,git
这就找到了第一次提交时a.txt的内容。
tree,或者叫文件树,有以下几个特点:
其实可以把tree理解为一个文件夹,这个文件夹中包含了其他的文件和子文件夹,然后子文件夹有可以包含其他文件和子子文件夹,只是,这里面提到的所有文件,都是一个引用。如果我们再修改一下a.txt并提交,git会再生成一个新的文件树并关联到这个提交,这个树中的a.txt将会指向一个新的id,而b.txt文件由于没有修改,所有id不变,也就是还指向原来的文件位置。这样做可以优化存储空间。
$ git ls-tree
usage: git ls-tree [<options>] <tree-ish> [<path>...]
-d only show trees
-r recurse into subtrees
-t show trees when recursing
-z terminate entries with NUL byte
-l, --long include object size
--name-only list only filenames
--name-status list only filenames
--full-name use full path names
--full-tree list entire tree; not just current directory (implies --full-name)
--abbrev[=<n>] use <n> digits to display SHA-1s
输入git ls-tree
命令,git会告诉我们这个命令的用法。先说这个<tree-ish>
,它可以是一个提交id,也可以是一个树的id。当然,也可以是HEAD。指定了<tree-ish>
后,git会列出那一次提交时对应的那个树的内容。默认情况下,是不递归遍历所有文件的。
$ git ls-tree HEAD
100644 blob 97c3279503234b905d2f51b0dc148bcc064df6d8 a.txt
040000 tree 67d684c6a7cfdf08beaf30411af3ae1144b0713d folder1
通过-r
参数,可以递归遍历文件树。
$ git ls-tree -r HEAD
100644 blob 97c3279503234b905d2f51b0dc148bcc064df6d8 a.txt
100644 blob cbcaaeab925e1aec25b56f69cc6af6f5108f8f5f folder1/b.txt
还可以继续使用-t
参数,来输出更为完整的信息,包括子树
$ git ls-tree -r -t HEAD
100644 blob 97c3279503234b905d2f51b0dc148bcc064df6d8 a.txt
040000 tree 67d684c6a7cfdf08beaf30411af3ae1144b0713d folder1
100644 blob cbcaaeab925e1aec25b56f69cc6af6f5108f8f5f folder1/b.txt
通过ls-tree
把你想看的某个文件树的内容都列出来了,再配合使用上面介绍的cat-file
命令,你就可以看到每一次提交时的每一个文件的样子了。
作为分布式的版本管理工具,git把所有文件都保存在了.git/objects文件夹下。这个文件夹中包含了很多已两个字符命名的子文件夹,到底是什么含义呢?
我们就以第二次提交时文件树中的a.txt文件的id97c3279503234b905d2f51b0dc148bcc064df6d8
为例,这个是四十位的哈希值,前两位表示所在的文件夹,后面的是文件名,所以这个文件就被保存在.git/objects/97/c3279503234b905d2f51b0dc148bcc064df6d8
。
以上就是我对git对象库的理解,分享给你,如果你有收获,就把这篇博客分享给你的朋友吧。