git底层原理(二)
git对象模型
在git系统中有四种类型的对象,所有的Git操作都是基于这四种类型的对象:
"blob":这种对象用来保存文件的内容。
"tree":可以理解成一个对象关系树,它管理一些"tree"和“blob”对象。
"commit":指向一个"tree",它用来标记项目某一个特定时间点的状态。它包括以下关于时间点的元数据,如时间戳、最近一次提交的作者、指向上次提交等。
"tag":给某个提交增添一个标记。
SHA1哈希值
在Git系统中,每个Git对象都有一个特殊的ID来代表这个对象,这个特殊的ID就是我们所说的SHA1哈希值。SHA1哈希值是通过SHA1算法计算出来的哈希值,对于内容不同的对象,会有不同的SHA1哈希值。
实例
我们以这样的目录结构为例来研究,
--repository //根目录文件夹
--a.txt //txt文件,初始时,内容为a_git
--bb //文件夹
--b.txt //txt文件,在bb文件夹下面,内容为b_git
首先cd repository目录,初始化使用git init命令,执行了这个命令后repository目录下会生成一个.git文件夹,使用find . -type f命令查看目录结构:
然后执行git add -A和git commit -m "commit1"两条命令,可以看到.git/objects/文件夹下增加了五个对象
可是我们的示例工程只有两个文件和一个文件夹,为什么objects里却有五个文件那,这里就要说说commit类型了,commit类型内部可以引用一个tree类型文件和一个commit类型文件。
关于文件的名字,objects内部的文件全部是以sha-1命名,如果是blob类型则以文件内容做sha-1计算得出40字符的校验和,然后为了不让这个objects内部文件过多,所以使用40字符的前两位来建立一个文件夹,在以后38位为文件名,包括commit 和tree 全部使用sha-1校验和来命名。
接下来说一下这五个文件是以什么结构关联的:
每个commit 引用一个tree,根据工程目录结构这个tree 再引用一个blob(a.txt)和一个tree(bb) ,这个tree(bb)则引用一个blob(b.txt)如图:
通过这个图就可以看出来commit,tree,blob的关系了,也基本能明白为什么objects下有五个文件了, 现在我改动一下a.txt然后在提交一个版本,在a.txt添加"123456"内容然后执行add和commit,添加一个版本,接下来再看objects内部的情况:
比刚刚多了三个文件,究竟是哪三个文件?因为我们改动了a.txt文件,所以这里在保留上一个版本的a.txt的基础上新增加一个a.txt文件,git只对有改动的文件进行备份保留。另外两个文件分别是commit 2和一个根tree,现在这8个文件的关系如图:
commit文件内部可以引用一个commit,这样commit之间就可以建立关系了,因为只有a.txt文件做了改动所以只有a.txt文件新建了一个,然后被commit2引用,bb和b.txt文件未做改动则commit2依然引用之前的文件。同理如果我们再改动b.txt文件,我们可以设想一下这些文件的关联关系。如图:
branch分支
git branch 可以获取当前的分支列表,这个分支列表会保存在./git/refs/heads/这个路径下,这里包含master和一些其他分支文件,以master文件为例查看master文件内容如下:
其实这是一个commit类型 的文件名,这样每个分支都可以拥有一个自己的commit引用,从上面的图可以看出只要拿到commit的文件名就可以找到所有跟他关联的文件,还有个问题是./git/refs/heads/这个文件下是所有分支信息,总要有一个当前分支,其实这个当前分支是被记录在./git/HEAD文件内部:
因为当前是master分支所以HEAD文件里记录的是master,每次我们使用git checkout 来切换分支的时候就是在修改HEAD这个文件的内容。那么我们基本就可以理解:我们首先选择一个分支为当前分支,每个分支里记录着当前分支最顶端的commit对象,这个commit对象又可以找到所有跟它关联tree和blob,同时commit对象又和它的历史commit关联。我们可以任意切换当前分支,同时又可以修改当前分支指向的commit对象,比如我们执行reset可以选择回退到任意一个commit。这些就可以顺理成章切换任意分支并且找到任意版本的文件了。