2

git已经非常普及,几乎是程序员的标配。在熟练使用git命令的同时,你是否对底层的实现机制有所好奇?是否想更深入的了解git原理?本文将从较底层的视角逐步揭开git的秘密。
文章主要参考git book 第十章,根据笔者的理解提炼,简化不必要的细节,希望能帮助大家更好的理解git。

解决文件存储(blob)

版本控制系统本质就是一个特殊的文件系统,能够对不同的文件、不同的版本进行存储和恢复。git最基本的存储是一个key-value的数据库。git提供了一些命令来操作该数据库,hash-object命令能够将工作区文件插入数据库,返回一个hash,cat-file命令能够根据hash查询内容。数据文件存储于.git/objects目录。
下面的例子创建一个test.txt文件并将其内容存储到git数据库。
clipboard.png

当文件内容改变时,重新存储即可,此时会返回新的hash。只要记录了的hash和文件名,就能进行简单的版本管理了。下面的例子演示了一个简单的版本恢复:首先修改test.txt,并将新值存入数据库,然后又通过hash将工作区的文件内容回退到上一个版本。
clipboard.png

这种最基本的存储单元git称为blob。

支持文件名和集合(tree)

普通blob无法支持文件名和集合的存储,git引入tree来解决此问题。tree类似目录的概念,包含一系列blob/tree的hash和文件名。
创建tree的方法是,先用update-index命令将内容添加到暂存区,然后通过write-tree命令生成tree。

如下,先添加一项内容的暂存区。
clipboard.png
ps:100644是linux文件类型常量。

如下,再添加一项内容到暂存区(update-index命令可直接传入文件名,此时git会做两个步骤,读取文件内容存至数据库和添加至暂存区)。
clipboard.png

此时暂存区有两个项目,通过write-tree命令将暂存区内容生成tree,返回值一个hash。可以看到tree也是以一个blob来存储的。
clipboard.png

支持备注信息(commit-tree)

前面已经解决了存储的问题(包括内容和文件名),但想添加一些额外的备注信息(比如作者、时间、说明等)还不行,commit-tree能够解决此问题。
以下代码对一个tree添加commit。commit-tree主要包含三部分:指向另一个tree的hash、作者信息、commit message。
clipboard.png

commit-tree还有另外一个字段parent,指向前一个commit-tree的hash。以下代码创建了一个新的commit-tree并指向前者。
clipboard.png

有了commit-tree之后,不仅能添加备注信息,还能形成一条commit链,通过这条链,能够更加方便地进行版本的切换和回退。

分支的本质(branch)

理论上commit-tree能够满足任意复杂的版本管理,只不过你需要记住一些关键节点的commit hash。hash对人是不友好的,git引入分支来记录关键节点的commit hash。分支就是一个普通的文本文件,文件名即分支名,内容即commit hash。
以下代码创建了一个master分支。
clipboard.png

通过git log就能查看分支对应的commit链。
clipboard.png

分支存储于.git/refs目录,本地分支位于heads目录,远程分支位于remotes目录,.git/HEAD指向当前所在的本地分支。从这里可以看到git分支模型确实是非常轻量,就是一个简单的文本文件而已。

远程同步(remotes)

仅仅本地进行版本管理还不够,要和远程仓库通信才能多人协作。git的远程仓库设计也很简单,本地和远程互为拷贝。在同步信息时,只需同步.git/objects和.git/refs/remotes目录的文件即可。数据传输可采用http/ssh协议,服务器交互地址配置于.git/config。

总结

git命令非常多,概念也相对较多。但git的底层逻辑设计是非常简单的。一个好的设计一定是简单的,一个好的设计一定是具有美感的。


crossea
1.1k 声望43 粉丝