【git】存储原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010900754/article/details/79633742

git与传统的版本控制工具的区别在于 分布式。

每一个开发者都可以有自己的版本库,可以对自己的版本库提交,修改。对本地库的修改可以在断网的情况下进行。同时,项目组还有一个中心库,就git是分布式的,项目组也必须保证有一份最最“权威”的代码在中心库,这份代码就是稳定版本,是最终可以上线的版本。虽然git是分布式的,但是最终还会有“集中化”的味道。不过相比于传统的svn等集中式工具,git还是有优势的,主要体现在,开发者对于主库的依赖明显减少。每一个人都可以把主库拉下来然后进行离线操作,最终基于分支开发,合到主库里,灵活性大大增加。

那么每一个git的本地库是如何实现的?

这就涉及到了git中的对象概念,在git实现中共有tag,tree,commit和blob四种对象。这里只看后三个。blob是最基本的存储单元,对应git中要管理的每一个文件,二进制形式。tree对应的是目录,里面是文件或者其他目录,当然只需要存储文件名或者目录名即可。git不仅仅会管理单一文件,所以tree可以包含一组文件或者文件夹。commit是每一次提交,包含了本次提交的所有文件的根目录所对应的tree的引用。commit是一个链表,所以还有前一个提交的引用。

以上所有的对象都存储在项目根目录下的.git文件夹下,这也是存储维护git信息的目录。

假设一个项目包含aa目录下的b.txt:

进入.git目录下,可以看到:


查看HEAD内容:


我们知道HEAD是当前分支的指针,我们可以创建很多分支,但是工作区中只能对一个分支操作,这个分支就是由HEAD指定,HEAD记录了当前是哪一个分支。这里的ref/heads/master就是当前分支。相关内容在refs文件夹下。


可以看到,refs/heads目录下存储了全部的分支,这里只有master一个分支,查看master的内容,是一串字符。接着再看一下log:


发现第一次提交的id和这个文件名一样,所以refs/heads下的每一个分支名的文件存储的是当前分支的最后一次提交的id。

其实这一串字符也有含义,前面说了commit其实是一个文件,存储下.git下的objects目录下。而这串字符正是commit文件的hash值。这种命名方式贯穿了git的全部文件的命名。

 可以使用cat-file工具查看这个commit的id:


此次commit包含了一个tree的id和一个父commit的id。

继续查看tree:


可以看到该tree包含了另一个tree对象,也就是aa目录对应的tree对象。

继续:


可以看到aa目录的tree下包含了b.txt这个文件的blob。


最终看到了b.txt的内容,也就是blob存储的内容。

接着进入.git/objects目录。


git存储这些文件时会按照前几位划分为若干文件夹,最后的blob文件名是文件夹+文件名的组合,每一个文件夹不会太大。此时总共有4个文件,1个commit,1个blob和2个tree。

此时如果在aa目录同级加一个a.txt,内容与b.txt相同:


可以看到,a.txt和b.txt内容相同名字不同,结果git并没有新建一个blob,而是复用了b.txt的blob,所以git存储文件只看内容而不看文件名。结果也多了一个tree,包含了a.txt和aa目录。

如果修改a.txt为内容呢?


最终,多了一个blob,而且包含aa和a.txt的tree也多了一个,但是aa对应的tree不变,因为该目录下的文件不变。

git使用hash值作为文件名,主要是为了对比差异,只要文件变了,那么该文件的hash值就会变,与老版本的文件名就不同,就可以据此判定有差异,这个信息会随着文件夹向上传递,也就催生了不同的tree包含这些不同的blob。

所以搞清楚了git底层的这三种对象是理解git存储的关键。

git存储文件只看内容;根据内容生成hash值;基于底层的blob名构建不同的tree;commit指向tree。


展开阅读全文

没有更多推荐了,返回首页