Git 原理分析:存储对象、树对象、提交对象

在上一篇我们分析了如何在开发中使用 Git, 但是 Git 的底层原理到是什么呢?

1.GIT 存储对象(HashMap)

Git 是一个内容寻址文件系统,其核心部分是一个简单的键值对数据库(key-value data store),你可以向数据库中插入任意内容,它会返回一个用于取回该值的 hash 键。

# 将 'Let us go' 存入键值库,然后返回相应对象的key值
echo 'Let us go' | git hash-object -w --stdin
4ab1f81f376332da3ca586a3ff63c25d6b8bf062

# 通过 git cat-file -p <key> 可以得到相应的value
git cat-file -p 4ab1f81f376332da3ca586a3ff63c25d6b8bf062
Let us go

在这里插入图片描述

Git基于该功能 把每个文件的版本中内容都保存在数据库中,当要进行版本回滚的时候就通过其中一个键将期取回并替换。下面我们来演示一下:

# 新建一个git工作空间
git init
# 查找所有的git对象
find .git/objects/ -type f

在这里插入图片描述

# 写入版本1
echo 'version1' > README.MF; git hash-object -w README.MF;

在这里插入图片描述

再向 README.MF 中写入两次

# 写入版本2
echo 'version2' > README.MF; git hash-object -w README.MF;
# 写入版本3
echo 'version3' > README.MF; git hash-object -w README.MF;

在这里插入图片描述

问题一:那么现在 README.MF 中的内容是是什么?version3,因为它是最后写入的。

在这里插入图片描述

问题二:如果我现在想版本回滚,怎么搞?读取之前版本的内容,然后再写入到 README.MF 就好了

# 注意,这里只用写部分 key 值就可以了
git cat-file -p 5bdcfc19f119 > README.MF

在这里插入图片描述

问题三:你说的这些跟我 git add 又有什么关系呢?

我们通过实例来演示一下,

# 新建一个文件
echo 'hello world' > test.txt
# 查看暂存区状态
git status

在这里插入图片描述

# 查找所有的git对象
find .git/objects/ -type f

在这里插入图片描述

# 将 test.txt 添加到暂存区
git add test.txt
# 再次查找所有的git对象
find .git/objects/ -type f

在这里插入图片描述

可以看见在.git\objects\ 目录下多了一个哈希对象。所以,当我们执行 git add test.txt 等同于执行了 git hash-object -w test.txt 把文件写到数据库中。

注意,上面如果我们 git add README.MF 并不会产生新的哈希对象,因为它的三种存在形式 v1,v2,v3 的哈希对象已经在里面了。

我们解决了存储的问题,但其只能存储内容,并没有存储文件名,如果要进行回滚怎么知道哪个内容对应哪个文件呢?接下要说的就是树对象,它解决了文件名存储的问题 。

2.GIT 树对象

树对像解决了文件名的问题,它的目的将多个文件名组织在一起,其内包含多个文件名称与其对应的Key和其它树对像的用引用,可以理解成操作系统当中的文件夹,一个文件夹包含多个文件和多个其它文件夹。

在这里插入图片描述

每一个分支当中都关联了一个树对像,他存储了当前分支下所有的文件名及对应的 key。

3.GIT 提交对象

一次提交即为当前版本的一个快照,该快照就是通过提交对像保存,其存储的内容为:一个顶级树对象、上一次提交的对像啥希、提交者用户名及邮箱、提交时间戳、提交评论。

下面我们就来演示一下树对象和提交对象:

# 提交上面的 test.txt
$ git commit -am 'test'
# 再次查找所有的git对象
find .git/objects/ -type f

在这里插入图片描述

问题一:可以看到相比提交之前,多了两个新的哈希对象,所以这俩对象是什么呢?提交对象和树对象

# 查看提交情况,我们可以得到提交对象的key值
git log

在这里插入图片描述

问题二:提交对象有什么?树对象的key值 + 作者信息 + 提交信息 + 提交注释

# 根据key值查看提交对象
git cat-file -p c1a04fc324e

在这里插入图片描述

问题三:那这个树对象里存的是什么呢?内容对象的 key 值 + 文件名

# 根据key值查看树对象
git cat-file -p c3b8bb102a

在这里插入图片描述

通过上面的分析推理,我们可以推测出从修改一个文件到提交的过程总共生成了三个对像:

  1. 一个内容对象(add后生成) ==> 存储了文件内容
  2. 一个提交对像(commit后生成) ==> 存储了树对像的 key
  3. 一个树对像(commit后生成) ==> 存储了文件名及内容对像的 key

问题四:多级目录的树对象是什么样呢?

# 新建一个工作空间
cd ..; mkdir hash02; cd hash02; git init;
# 创建一个目录
mkdir -p src/main/java/com
# 在目录底层创建一个文件
echo 'hello java' > src/main/java/com/hello.java

# 加入暂存区并提交
git add -A; git commit -am 'hello'
# 查找所有的git对象
find .git/objects/ -type f

在这里插入图片描述

我们可以看到,这次他创建了7个哈希对象,那为什么会有7个呢?

在这里插入图片描述

问题五:如果我对目录结构进行了变化,这些哈希对象如何变化呢?

# 现在我在main目录下又建了一个newFile.txt
git status

在这里插入图片描述

# 将新文件提交
git add -A; git commit -am 'new'
# 查看git对象
find .git/objects/ -type f

在这里插入图片描述

可以发现他变成成了 12 个git对象,多了5个为什么呢?

在这里插入图片描述

所以,Git 这么设计有什么优势呢?

  1. 有没有点像链表的感觉,所以 Git 添加分支和切换分支都很快。
  2. 如果我们要回滚到某个快照时,我们需要遍历树对象,通过不断比对 key 值,然后从树对象中找到相应存储对象的 key。因为比对的只是是 key 所以回滚的速度也很快。

4.GIT 引用

当我们执行 git branch {branchName} 时创建了一个分支,其本质就是在git 基于指定提交创建了一个引用文件,保存在 .git\refs\heads\ 下。

原来只有master分支,现在我们新建一个分支dev

git branch dev

在这里插入图片描述

问题一:那这分支两个文件中存储的是什么呢?

在这里插入图片描述

问题二:它们俩的值是一样的,所以 e67541123674f2043cab6751bb56af19b69b832f 是什么?最近一次提交对象的 key。

在这里插入图片描述

相关推荐
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:马嘣嘣 返回首页