Git 工作原理(一)
今天讲一下 Git 工作原理,本文主要目的不是讲解如何使用各种 git 命令,而是关于一些常用 git 命令之后仓库中会出现什么变化,以及 git 是如何进行仓库的版本控制的。
[TOC]
1. Git 的文件管理
1.1 Git 文件存储
git 是怎么管理文件的呢?首先我们需要理解 git 对文件的保存逻辑,这很重要:
git 根据文件内容来管理文件,而不是文件名!
比如某个仓库中,完全相同的两个文件我们保存了 2 份,只不过文件名不同,那么在 git 看来这两个是同一个文件,他会根据文件内容经过SHA1算法计算出一个对应的哈希值,然后根据哈希值来索引文件。
注:这么做的好处是显而易见的,相同的两个文件不再需要多存储一份。
这是怎么体现出来的呢?其实 git
会把我们仓库中的原始文件压缩成二进制文件,然后都保存在
.git/objects/
文件夹下,每个文件的命名就是计算出来的哈希值。我们可以用如下命令来查看
git 给我们保存的所有文件。
1 |
|
现在我们新建一个仓库试试吧
1 |
|
好,我们来看看 .git/objects/
文件夹。咦?怎么是空的?什么也没有。哦我们只是修改了工作区,还没添加修改呢,来添加一下
1 |
|
好,现在来看看 .git/objects/
吧
1 |
|
1.2 Git 文件类型
应用上述命令后,三列分别表示文件的哈希值、文件类型、长度。注意这里出现了
git 的文件类型,主要有 4
种:blob
、tree
、commit
、tag
。
我们可以用以下命令查看文件类型
1 |
|
可以用以下命令查看内容
1 |
|
前面我们只看到了 blob
文件,什么情况下会出现
tree
和 commit
呢?注意我们现在只是新建 txt 并
add 了,还没有提交,那我们 commit 一下吧
1 |
|
然后再查看一下 .git/objects/
文件夹
1 |
|
咦,他们出现了!!!让我们分别看看他们是什么东西~
1.2.1 blob
文件
如果是 blob
文件我们可以直接看到文件的内容
1 |
|
1.2.2 tree
文件
如果是 tree
文件我们可以看到目录信息
1 |
|
四列内容分别为文件权限、文件类型、哈希值、文件名。再查看最后一个
tree
文件的内容
1 |
|
这不就是我们工作空间中的文件结构嘛!
注意我们工作空间中的文件对应的文件名和权限等信息是保存在
tree
类型的文件中,而不是blob
文件中哦!
1.2.3 commit
文件
让我们再看看这个 commit
1 |
|
其实 commit
就是指向了当前仓库的根目录所对应的
tree
文件嘛。
1.2.4 tag
文件
这个我们以后再说。
1.2.5 index
文件
细心的话可以发现我们 add 以后 .git/
文件夹下的
index
文件也会被修改,我们可以用以下命令查看他的内容
1 |
|
其实它的内容跟 tree
是类似的,不过多了一列,这个我们后面再说
小结
敲黑板!各种文件的作用是
blob
:工作区的任何文件都会被 git 压缩后以blob
形式保存一个副本在.git/objects/
文件夹下,文件名就是计算的哈希值;tree
:这个其实就是目录文件,描述当前文件夹结构;注意文件名和权限等信息是保存在tree
文件中而不是blob
文件,后者只保存文件内容;commit
:记录提交的信息,指向仓库当前根目录的tree
文件;tag
:记录标签信息。
2. Git 的版本控制
2.1 git add
会发生什么
假如我们现在修改了其中一个文件呢?比如
1 |
|
再看一下 .git/objects/
文件夹
1 |
|
增加了什么?
1 |
|
我们来看看这个文件,他就是一个 blob
文件
1 |
|
看来就是把我们修改后的文件又存储了一个 blob
文件,注意修改前的文件并没有删除哦,也就是修改前的 a.txt
对应的 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c blob 4
还在,方便我们以后版本回退嘛。
那么我们再看看 index
文件
1 |
|
咦,a.txt
文件的指针已经修改了!!!指向了最新的
blob
文件。好了我们知道了
index
文件在执行git add
之后就会被修改,总是保存最新的仓库根目录信息。事实上,这也是我们下次commit
所要提交的信息!!!事实上,
index
就是我们常说的 git 的暂存区!!!
2.2 git commit
会发生什么
如果再提交一下修改呢?
1 |
|
好了,新增加的有
1 |
|
我们可以很容易推断,新的 tree
文件就描述了更新后的根目录信息,可以看出 a.txt
文件的指针(哈希值)变了,但是由于 dir
文件夹中的内容没有任何修改,所以他的指针不变
1 |
|
我们再看看新的 commit
文件,可以发现还多了一个
parent
选项,也就是指向了上一次提交对应的的
commit
文件。
1 |
|
小结
重点来了,敲黑板!
git add
- 对每个修改后的文件压缩,并保存一个新的
blob
文件在.git/objects/
文件夹下,用哈希值命名文件;- 修改
.git/index
保存最新的根目录信息;git commit
- 生成新的
tree
文件保存在.git/objects/
目录下,记录新的仓库文件结构信息;- 生成新的
commit
文件保存在.git/objects/
目录下,指向当前最新的根目录的tree
文件;同时该文件中还存在以一项parent
指向上一次的commit
文件;实际上,只需要1 个
commit
,若干个tree
和若干个blob
文件就可以完整描述仓库的当前提交版本。因此 git 只需要用一个单向链表记录commit
文件之间的指向关系,就可以描述版本变化。