一开始我还担心 git 的原理会不会很难懂,但在阅读了官方文档后我发现其实并不难懂,似乎可以动手实现一个简单的 git,于是就有了下面这篇学习记录。
本文的叙述思路参照了官方文档Book的原理介绍部分,在一些节点上探讨代码实现,官方文档链接。
看完本文你能:1. 了解 git 的设计思想。2. 收获一点快乐?
编程语言选择了 go,因为刚学不太熟悉想多使用一下。
这是我的仓库地址,但如果你和我一样是初学,直接看代码可能不能快速上手,推荐顺着文章看。
如果文章看得吃力可以跟着官方文档的原理部分操作一次再回头看,可能更易懂?(●’◡’●)
1. init
在学习 git 原理之前,我们先忘掉平时用的 commit,branch,tag 这些炫酷的 git 指令,后面我们会摸清楚它们的本质的。
要知道,git 是 Linus 在写 Linux 的时候顺便写出来的,用于对 Linux 进行版本管理,所以,记录文件项目在不同版本的变更信息是 git 最核心的功能。
大牛们在设计软件的时候总是会做相应的抽象,想要理解他们的设计思路,我们就得在他们的抽象下进行思考。虽然说的有点玄乎,但是这些抽象最终都会落实到代码上的,所以不必担心,很好理解的。
首先,我们要奠定一个 ojbect 的概念,这是 git 最底层的抽象,你可以把 git 理解成一个 object 数据库。
废话不多说,跟着指令操作,你会对 git 有一个全新的认识。首先我们在任意目录下创建一个 git 仓库:
我的操作环境是 win10 + git bash
$ git init git-test
Initialized empty Git repository in C:/git-test/.git/
可以看到 git 为我们创建了一个空的 git 仓库,里面有一个.git
目录,目录结构如下:
$ ls
config description HEAD hooks/ info/ objects/ refs/
在.git
目录下我们先重点关注 .git/objects
这个目录,我们一开始说 git 是一个 object 数据库,这个目录就是 git 存放 object 的地方。
进入.git/objects
目录后我们能看到info
和pack
两个目录,不过这和核心功能无关,我们只需要知道现在.git/objects
目录下除了两个空目录其他啥都没有就行了。
到这里我们停停,先把这部分实现了吧,逻辑很简单,我们只需要编写一个入口函数,解析命令行的参数,在得到 init 指令后在指定目录下创建相应的目录与文件即可。
这里是我的实现:init
为了易读暂时没有对创建文件/目录进行错误处理。
我给它取了个土一点的名字,叫 jun,呃,其实管它叫啥都可以(⊙ˍ⊙)
。
2.object
接下来我们进入 git 仓库目录并添加一个文件:
$ echo "version1" > file.txt
然后我们把对这个文件的记录添加进 git 系统。要注意的是,我们暂不使用add
指令添加,尽管我们平时很可能这么做,但这是一篇揭示原理的文章,这里我们要引入一条平时大家可能没有听到过的 git 指令git hash-object
。
$ git hash-object -w file.txt
5bdcfc19f119febc749eef9a9551bc335cb965e2
指令执行后返回了一个哈希值,实际上这条指令已经把对 file.txt 的内容以一个 object 的形式添加进 object 数据库中了,而这个哈希值就对应着这个 object。
为了验证 git 把这个 object 写入了数据库(以文件的形式保存下来),我们查看一下.git/objects
目录:
$ find .git/objects/ -type f #-type用于制定类型,f表示文件
.git/objects/5b/dcfc19f119febc749eef9a9551bc335cb965e2
发现多了一个文件夹5b
,该文件夹下有一个名为dcfc19f119febc749eef9a9551bc335cb965e2
的文件,也就是说 git 把该 object 哈希值的前2个字符作为目录名,后38个字符作为文件名,存放到了 object 数据库中。
关于 git hash-object 指令的官方介绍,这条指令用于计算一个 ojbect 的 ID 值。-w 是可选参数,表示把 object 写入到 object 数据库中;还有一个参数是 -t,用于指定 object 的类型,如果不指定类型,默认是 blob 类型。
现在你可能好奇 object 里面保存了什么信息,我们使用git cat-file
指令去查看一下:
$ git cat-file -p 5bdc # -p:查看 object 的内容,我们可以只给出哈希值的前缀
version1
$ git cat-file -t 5bdc # -t:查看 object 的类型
blob
有了上面的铺垫之后,接下来我们就揭开 git 实现版本控制的秘密!
我们改变 file.txt 的内容,并重新写入 object 数据库中:
$ echo "version2" > file.txt
$ git hash-object -w file.txt
df7af2c382e49245443687973ceb711b2b74cb4a
控制台返回了一个新的哈希值,我们再查看一下 object 数据库:
$ find .git/objects -type f
.git/objects/5b/dcfc19f119febc749eef9a9551bc335cb965e2
.git/objects/df/7af2c382e49245443687973ceb711b2b74cb4a
(゚Д゚)
发现多了一个 object!我们查看一下新 object 的内容:
$ git cat-file -p df7a
version2
$ git cat-file -t df7a
blob
看到这里,你可能对 git 是一个 object 数据库的概念有了进一步的认识:git 把文件每个版本的内容都保存到了一个 object 里面。
如果你想把 file.txt 恢复到第一个版本的状态,只需要这样做:
$ git cat-file -p 5bdc > file.txt
然后查看 file.txt 的内容: