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
《Linux三剑客》视频教程,从零开始快速掌握Linux开发常用的工具:Git、Makefile、vim、autotools、debug,免费赠送C语言视频教程,C语言项目实战:学生成绩管理系统。详情请点击淘宝链接:Linux三剑客