commit、branch、储存区这都是如何达到的,怎么搞的

lxf2023-04-05 12:59:01

git 大家天天都在用,但是你知道这是如何完成的吗?

git add、git commit 一天到晚敲,但是你知道它最底层干了什么么?

commit、branch、储存区这都是如何达到的,怎么搞的版本号转换呢?

全部这些问题,只需弄懂 3 个 object 就全能回答了。

不相信我们来看一下:

最先,实行 git init 复位 git 库房。

git 中的所有内容都是保存在 .git 这一掩藏文件目录的,我们首先把它搞出来:

commit、branch、储存区这都是如何达到的,怎么搞的

默认设置掩藏,但只要你将这个 exclude 配备删除,就显现出来了:

commit、branch、储存区这都是如何达到的,怎么搞的

进行之后能够看见这个东西:

commit、branch、储存区这都是如何达到的,怎么搞的

关键就是这儿的 objects。

它到底是什么呢?

大家添加一个 object 就明白了:

有这样一个 text.txt 文件:

commit、branch、储存区这都是如何达到的,怎么搞的

实行这一 hash-object 的命令:

git hash-object -w text.txt

他会回到一个 hash:

commit、branch、储存区这都是如何达到的,怎么搞的

那么你会到 objects 目录下发觉多了一个文件目录,目标目录是 hash 前两位,剩余是指文件夹名称:

commit、branch、储存区这都是如何达到的,怎么搞的

它存着什么信息呢?

能通过 cat-file 来说:

git cat-file -p 7c4a013e52c76442ab80ee5572399a30373600a2

-p 是 print 的意味。

能够看见文件信息便是 text.txt 内容:

commit、branch、储存区这都是如何达到的,怎么搞的

哦,原先 git 存储的文件信息便是放在这里的。

改一下文件信息,刚存一下:

git hash-object -w text.txt

你可以看到多了一个新的文件目录,同是 hash 做目标目录和文件夹名称:

commit、branch、储存区这都是如何达到的,怎么搞的

就这样一点东西,大家就可以做到版本控制了!

怎样做?

载入不一样 hash 内容写入文件不就好了?

比如最近内容包括 bbb,我觉得修复上一个版本具体内容是否只需 cat-file 上一个 hash 再写入文件就可以了?

git cat-file -p 7c4a013e52c76442ab80ee5572399a303 > text.txt

commit、branch、储存区这都是如何达到的,怎么搞的

这就是一个版本管理工具了!

自然,目前还没有存文件夹名称的信息,也有文件目录信息内容,这些数据存有哪里呢?

这个时候就需要其他类别的 object 了。

刚刚大家看得存储文件视频的 object 称为 blob。

能通过 cat-file 加一个 -t 看出:

-t 是 type 的意味。

git cat-file -t 7c4a013e52c76442ab80ee5572399a303

commit、branch、储存区这都是如何达到的,怎么搞的

也有存放文件目录和文件夹名称的 object,称为 tree。

tree 和 blob 是咋关系的?

找一个真实库房看一下就明白了:

例如我还在 react 新项目下实施了 cat-file,之前用来查询过 blob 目标具体内容,此次查询是指 main 支系顶部的 tree 目标。

git cat-file -p main^{tree}

能够看见有许多 blob 对象和 tree 目标:

commit、branch、储存区这都是如何达到的,怎么搞的

非常容易看出,文件目录是 tree 目标,文件信息是 blob 目标:

commit、branch、储存区这都是如何达到的,怎么搞的

那文件夹名称呢?

文件夹名称并不是早就在 tree 目标里包括了么?

大家接着用 cat-file 看看 packages 这一 tree 对象具体内容:

git cat-file -p 2889ab8f0ef04484849c40d3eebe330ec25bbe1c

commit、branch、储存区这都是如何达到的,怎么搞的

非常容易就能看出来 git 是如何存放一个文件目录的啦:

commit、branch、储存区这都是如何达到的,怎么搞的

在 tree 目标里存放每一个根目录和文件信息名称和 hash。

在 blob 目标里存储文件具体内容。

tree 目标里根据 hash 指向了相对应的 blob 目标。

那是不是就串联起来了!

这便是 git 存储文件的形式。

那么这个 hash 是怎么算出来的呢?

很简单,应该是“对象类型 具体内容长短\0具体内容” 的字符串数组 sha1 以后数值变为 16 进制字符串数组。

例如 aaa 的 hash 就这样算出来:

const crypto = require('crypto');

function hash(content) {
    const sha1 = crypto.createHash('sha1');
    sha1.update(content);
    return sha1.digest('hex');
}

console.log(hash('blob 3\0aaa'))

commit、branch、储存区这都是如何达到的,怎么搞的

是否一毛一样!

每一个 object 都是这样算 hash 的。

再次而言 tree 目标:

其实大家放进储存区内容就等于是一个新的文件目录,都是通过 tree 阿里云oss的。

升级储存区用 update-index 这一指令:

git update-index --add --cacheinfo 100644 7c4a013e52c76442ab80ee5572399a30373600a2 text.txt

--add --cacheinfo 便是往储存区加上具体内容。

特定文件夹名称和 hash,接下来我们把 aaa 那一个文档放进去了。

前面的 100644 是文档方式

100644 是一般文档,100755 是可执行程序,120000 是符号链接文档。

加上以后就能看到 .git/index 这一文档了,储存区的内容是放到这:

commit、branch、储存区这都是如何达到的,怎么搞的

这个时候你实行 git status 就能看到储存区早已有这种文档了:

commit、branch、储存区这都是如何达到的,怎么搞的

所以,git add 的底层便是实施了 git update-index。

随后储存区内容载入版本库得话只需实行下 write-tree 就行了:

git write-tree

随后就会发现它返回一个 hash,而且 objects 目录下多了一个 object:

commit、branch、储存区这都是如何达到的,怎么搞的

这一目标是什么种类呢?

根据 cat-file -t 看看就明白了:

git cat-file -t 9ef7e5a61a3b70ff7149805fc86a4c26e953bb3f

能够看见,是一个 tree 目标:

commit、branch、储存区这都是如何达到的,怎么搞的

所以,储存区内容就是做为 tree 目标存放的。

再 cat-file -p 看看它具体内容:

git cat-file -t 9ef7e5a61a3b70ff7149805fc86a4c26e953bb3f

能够看见是这样子的:

commit、branch、储存区这都是如何达到的,怎么搞的

这便是 git commit 的基本原理了。

如今假定有一个要求,使你寻找某一版本号某个文件信息具体内容,修复回家。

是否就比较容易了?

一定要找到相匹配版本那一个 tree 的 hash,然后一层层寻找相对应的 blob 目标,载入具体内容再写入文件就行了!

这便是 git revert 的基本原理了。

自然,如果每一个版本号都得自己记牢高层 tree 的 hash 也太费劲了。

因此 git 又制定了 commit 目标。

能通过 commit-tree 指令把某一 tree 目标创建一个 commit 目标。

echo 'guang 111' | git commit-tree 9ef7e5

这儿参数值便是上边的 tree 对象 hash:

commit、branch、储存区这都是如何达到的,怎么搞的

再换 cat-file -t 看一下返回对象种类:

git cat-file -t b5f92e68912595dbb3b6cbda9123838546b18f7d

的确,这是一个 commit 目标:

commit、branch、储存区这都是如何达到的,怎么搞的

那 commit 目标都存着什么呀?

最好用 cat-file -p 看一下:

git cat-file -p b5f92e68912595dbb3b6cbda9123838546b18f7d

commit、branch、储存区这都是如何达到的,怎么搞的

今天的文章很了解,可是多了一个 tree 节点偏向,这一没什么问题,commit 的内容是某一 tree 对应的版本号嘛。

commit、tree、blob 三个目标就是这个样子关联:

commit、branch、储存区这都是如何达到的,怎么搞的

commit 中间还可以关系,其实就是有顺序。

这一用 commit-tree -p 来特定:

就像我们再建立2个 commit:

echo 'guang 111' | git commit-tree 9ef7e5 -p b5f92e6
echo 'guang 222' | git commit-tree 9ef7e5 -p c3f9f5

commit、branch、储存区这都是如何达到的,怎么搞的

这时候你用了 git log 看一下:

git log 1d1234

你可以看到平常常常看到的 commit 历史时间:

commit、branch、储存区这都是如何达到的,怎么搞的

这便是 commit 的完成基本原理!

自然,这儿要会 commit 的 hash 同时也太麻烦了。

大家平时如何使用?

用 branch 或是 tag 呀!

branch 和 tag 本身就是记载了这一 commit 的 hash。

这一部分那就不是 object 了,称为 ref:

commit、branch、储存区这都是如何达到的,怎么搞的

建立 ref 应用 update-ref 的命令:

git update-ref refs/heads/guang 1d1234e77de6de0bb8edcf90cbd1a9546d7b1d9a

比如说我创建了一个称为 guang 的朝着一个 commmit 对象 ref。

这儿会多一个文档,具体内容留着偏向的 commit 是什么:

commit、branch、储存区这都是如何达到的,怎么搞的

那么你 git branch 看一下:

commit、branch、储存区这都是如何达到的,怎么搞的

其实这也是建立了一个新的支系。

这便是 branch 的基本原理。

tag 也是一样,只不过是这是放到 refs/tags 目录下的:

git update-ref refs/tags/v1.0 1d1234e77de6de0bb8edcf90cbd1a9546d7b1d9a

commit、branch、储存区这都是如何达到的,怎么搞的

blob、tree、commit 和 ref 之间的关系就是这个样子:

commit、branch、储存区这都是如何达到的,怎么搞的

汇总

现在我们研究了 git 的完成基本原理,通常是 3 个 object 及其2个 ref。

3 个目标是:

  • blob: 存储文件具体内容
  • tree: 存放目录结构和文件夹名称,偏向 blob 和 tree
  • commit:存放版本升级,偏向不一样版本号的通道 tree

2 个 ref 是:

  • branch:偏向某一 commit
  • tag:偏向某一 commit

除此之外,储存区放到 .git/index 文档里,具体内容实际上也是个 tree 对象具体内容。

也有,hash 的计算公式是相近 blob 3\0aaa 那样 “对象类型 具体内容长短\0具体内容”的格式,对它做 sha1 随后变为十六进制。

基本上看明白这张图片就行了:

commit、branch、储存区这都是如何达到的,怎么搞的

使用了那好多个指令:

  • hash-object:建立 blob 目标
  • cat-file -t: 查询对象类型
  • cat-file -p: 查询目标具体内容
  • update-index: 升级储存区
  • commit-tree: 建立 commit 连接点
  • write-tree: 储存区载入版本库
  • update-ref:建立和更新 ref

明白了这种,你就能明白 git add、git commit、git log、git revert、git branch、git tag 等绝大部分 git 指令的完成基本原理了。

乃至依照这个逻辑来,自身写一个 git 是否并不难呢?