Git 内部原理介绍
前言
本文主要是介绍Git内部原理,包含底层命令与文件系统两部分,旨在让我们从了解Git底层原理的运作,从而更好的使用。本文适用于有一定Git基础,并且对Git实现原理比较好奇的同学。
Git 文件系统简介
首先,先说个概念:
Git文件系统是内容寻址文件系统。
内容寻址文件系统是什么?
简单来说就是一个键值对数据库,你可以向数据库中插入任意类型的内容,它会返回一个键值,你可以通过这个键值再次检索插入的内容。
有没有点感觉了,没有也没关系,我们先来分析研究Git文件系统的内容,找到我们想要的答案。
如何得到Git文件系统?
首先,新建个文件夹,如mkdir mygit
,进入文件夹后使用git init
命令,初始化Git后得到.git
文件夹,这个就是我们的文件系统实体,具体操作如下图:
可以看到.git文件夹下已经有很多内容,相关介绍如下图所示:
可以看到每个文件或者目录对应的介绍,这里面我们重点关注的主要有HEAD、objects以及refs等三个内容,在介绍他们前,我们先了解底层命令,用它来打开我们走进Git内部的大门。
Git底层命令
通过git help
可以查看高级命令及相关介绍,而如果我们想看底层命令,可以通过命令git help -g
,下面是一些常用底层命令:
了解底层命令可以让我们更直观的理解git的工作原理,比如我们平时操作的git add & git commit
其实可以用底层命令实现,如下:
上图为一个新建的git仓库,通过使用内部命令hash-object、update-index、write-tree、commit-tree
等实现了我们的添加和提交的功能,这几部的作用依次是:
hash-object 得到test1文件对象的键值
updata-index将test1添加到暂存区(实现了add操作)
write-tree命令将暂存区内容写入到一个树对象中(这里提到了对象,下面来介绍对象)
最后通过commit-tree命令将树对象提交得到一个提交对象(实现了commit操作)
用底层命令需要四部才能完成我们的工作(其实还有第五步,后面讲文件系统在介绍),而我们平时用的高级命令add、commit
只需要两步,这么一比确实高级了很多,但通过这四步,我们可以理解下之前我说的Git就是个键值对数据库这个概念是不是理解了很多,Git所有的文件都可以由生成的键值取出,如下图:
这个键值ce013625030ba8dba906f756967f9e9ca394464a
就是我们上面由hash-object
生成的。
Git对象
对象是Git文件系统的基础,可分为树对象、数据对象、提交对象、标签对象等四种,如下图所示:
数据对象就是linux系统中的inode节点(详情看上篇inode介绍),只存储文件中的内容,数据对象的名字就是它的”inode节点”,也就是索引节点,而文件名等信息则存放在树对象中,对比linux系统就是目录文件,提交对象当然就是我们的commit_id了,这也是我们平时接触最多的,下图为对象中的内容:
上图主要使用底层命令cat-file
获取对象信息,首先用-p
选项打印对象中文件的内容,然后用-t
打印对象类型。可以看到首先打印的是blob类型,也就是数据对象的内容,然后是树对象内容,最后是提交对象的内容,这里可以看到tree就是我们当时提交时使用的树对象的键值。下面为具体格式介绍:
这里为什么没有tag对象,因为tag对象内容就是我们使用git tag *
时标签的commit id,如下图:
Git文件系统原理
介绍完了git底层命令和git对象,我们可以回头再来看Git文件系统原理了。
首先填前面的第一个坑,.git目录下我们关注的三个内容:HEAD、objects/以及refs/。
HEAD
HEAD存放的是我们当前的分支信息
当我们新建一个分支后,切换当前分支可以发现HEAD内容已经被改变
当然我们可以直接修改HEAD文件来更改分支,不过这样是不推荐的,你可以骚一点,用底层命令symbolic-ref
来进行修改,如下:
objects/
objects/目录下存放着我们的数据对象、树对象以及提交对象等等
可以看到,我们之前操作的对象,都在这里存着,存放的格式为前两位作为目录名,后续内容作为文件名,这个文件是由zlib压缩过的,我们一般通过cat-file
命令进行查看。
refs/
refs/目录放置的是引用文件,先收下我对引用的理解:存储指向数据(提交)对象的指针。
对,我理解的引用文件就是指针,里面存放的内容就是键值,可以直接找到对应的对象文件,拿到想要的内容,引用的分类和介绍如下图:
HEAD我认为也是一种引用,不过它比较特殊,是符号引用,因为它存放的内容为就是文件路径,如master的refs/heads/master
,像极了软链接的样子。
这里的refs/head/master的内容就是我们当前分支的键值了。
remote/
主要是远程引用相关的,这里不多介绍。
包文件
git存放数据对象的时候会把整个文件都存到数据对象中,即使有zlib压缩,你可能也会疑惑,我项目这么大,这么多提交,.git目录不是早就爆掉了么,你能想到的设计的人肯定也想到的,所以就有了包文件,他存在的意义就是将对象进行打包,节省空间。
上图介绍的很清楚了(请自动把引用两个字替换成packet,笔误),不过还有一点注意的是相同的文件修改后的数据对象打包后,前一个对象保存的是差异,而文件的全部内容存放在包中此文件最新的数据对象中,因为git认为最新的是人们访问几率最大的,这个设计是为了节省计算的时间,提高效率。
Git组织结构
讲了这么多,那么git组织结构是什么样的呢?看下图:
文章很长了,不详细介绍了,看不懂就去翻上面,动动脑子去理解才能吸收。
看到这里的都是好孩子,给你个奖励,我自己的一套git别名,用起来会方便很多,如下:
版权声明: 本文为 InfoQ 作者【戈坞昂】的原创文章。
原文链接:【http://xie.infoq.cn/article/f49b2b043ee808209ce19e887】。文章转载请联系作者。
评论