Git 仓库探秘
当我们使用git commit命令将我们的文件修改添加到本地仓库时,这些文件到底保存在什么地方?是如何保存的?接下来我们就一探究竟。
为了更好的演示效果,我们新建一个仓库test:
root@ubuntu:/home# mkdir test
root@ubuntu:/home# cd test
root@ubuntu:/home/test# git init
Initialized empty Git repository in /home/test/.git/
在/home/test/目录下,就会生成一个.git隐藏目录,这个就是我们当前项目的版本库,我们所有的修改都保存在这个目录内。
./.git
./.git/objects
./.git/objects/pack
./.git/objects/info
./.git/branches
./.git/HEAD
./.git/description Git项目的描述信息
./.git/info
./.git/info/exclude 指定Git要忽略的文件
./.git/hooks 默认的hook脚本,由特定事件触发
./.git/refs Git引用:指向(远程分支)
./.git/refs/heads
./.git/refs/tags
./.git/config
每一个子目录的功能如下:
- branches:项目分支信息
- hooks: 默认的hooks脚本,由特定事件触发
- info: 内含exclude文件,指定Git要忽略的文件
- logs: 历史记录,删除的commit对象等
- objects: Git数据对象:commit、tree、blob、tag
- refs: Git引用:指向(远程)分支、标签的指针
- config: Git项目配置信息
- HEAD: 指向当前分支的末端
- index: staging area暂存区
- COMMIT_EDITMS:最后一次提交的注释
- description: Git项目描述信息
我们对文件的每次修改和提交,都保存到了objects目录下。现在我们给test仓库添加一个文件,并提交到仓库中:
root@ubuntu:/home/test# touch test.c
root@ubuntu:/home/test# git commit -m "Init test repo and add test.c to repo"
[master (root-commit) 62d2153] Init test repo and add test.c to repo
1 file changed, 6 insertions(+)
create mode 100644 test.c
提交成功后,在版本库的.git/test/.git/objects目录下,你会看到一些以40位16进制数字命令的文件:
root@ubuntu:/home/test/.git/objects# ls
62 63 8c info pack
root@ubuntu:/home/test/.git/objects# tree
.
├── 62
│ └── d21534ae082c61dc9e40196d5ff2265b4ca845
├── 63
│ └── 9c21339379b5521eac86c8f9547f41fe67679b
├── 8c
│ └── 2b6c9658cbcc70e302a7e75753fc05a79e8b2a
├── info
└── pack
5 directories, 3 files
root@ubuntu:/home/test/.git/objects#
为了保存用户的每次提交,Git在底层引入了对象的概念,每一个文件、提交都是一个Git数据对象,保存在objects目录下,这些对象的名字是一个40位16进制的数字,这个数字是根据对文件得内容经过哈希算法运算生成的。前2位数字作为目录,后面38位作为文件名,整个40位数字表示一个Git数据对象。
常见的数据对象有:
- blob对象:用来存储文件数据,通常是一个文件
- tree对象:类似一个目录,用来管理tree和blob
- commit对象:指向一个tree,用来标记项目某个特定时间点状态
- tag对象:用来标记某一个提交(commit)
我们可以使用git cat-file命令来查看三个数据对象的类型:
root@ubuntu:/home/test/.git/objects# git cat-file -t 62d21534ae082c61d
commit
root@ubuntu:/home/test/.git/objects# git cat-file -t 639c21339379b55
blob
root@ubuntu:/home/test/.git/objects# git cat-file -t 8c2b6c9658cbcc70
tree
每个数据对象的40位数字名字很长,每次输入都很麻烦,为了方便,一般我们只写前6位就可以了,一般不会跟其他的名字冲突。
通过命令我们可以看到,三个数据对象的类型分别为commit、tree、blob。其中commit类型的这个数据对象保存的是我们刚刚做的一次提交的信息,我们可以通过git cat-file -p查看这个对象的内容
root@ubuntu:/home/test/.git/objects# git cat-file -p 62d21534ae082c61d
tree 8c2b6c9658cbcc70e302a7e75753fc05a79e8b2a
author “litao.wang” <3284757626@qq.com> 1600911824 -0700
committer “litao.wang” <3284757626@qq.com> 1600911824 -0700
Init test repo and add test.c to repo
一个commit对象一般用来指向一个tree对象。我们在修改文件时,可能会修改不同目录下的不同文件,这些文件构成一个有层次目录关系,通过tree对象的指针可以将这些对象关联起来。当我们使用git commit命令提交时,Git会将存储在暂存区的这些index全部提交。
其中的tree数据对象中保存的是目录信息、各个内容之间的层次目录关系。在一个tree对象中,一般会包括对象的类型、mode、SHA1值、文件名字、一串指向blob或其它tree对象的指针。
root@ubuntu:/home/test/.git/objects# git cat-file -p 8c2b6c9658cbcc70
100644 blob 639c21339379b5521eac86c8f9547f41fe67679b test.c
root@ubuntu:/home/test/.git/objects#
blob对象用来存储某一个文件的具体内容,比如我们这次新提交的test.c,就保存在名为639c21339379b5521eac86c8f9547f41fe67679b的这个blob对象中,我们同样可以使用git cat-file命令来查看它的内容:
root@ubuntu:/home/test/.git/objects# git cat-file -p 639c21339379b5521eac86c8f9547f41fe67679b
#include <stdio.h>
int main (void)
{
return 0;
}
我们本次的提交在版本库中的数据指向关系为:
commit----->tree ----->blob
62d215----->8c2b6c----->639c21
test.c