git已经非常普及,几乎是程序员的标配。在熟练使用git命令的同时,你是否对底层的实现机制有所好奇?是否想更深入的了解git原理?本文将从较底层的视角逐步揭开git的秘密。
文章主要参考git book 第十章,根据笔者的理解提炼,简化不必要的细节,希望能帮助大家更好的理解git。
解决文件存储(blob)
版本控制系统本质就是一个特殊的文件系统,能够对不同的文件、不同的版本进行存储和恢复。git最基本的存储是一个key-value的数据库。git提供了一些命令来操作该数据库,hash-object命令能够将工作区文件插入数据库,返回一个hash,cat-file命令能够根据hash查询内容。数据文件存储于.git/objects目录。
下面的例子创建一个test.txt文件并将其内容存储到git数据库。
当文件内容改变时,重新存储即可,此时会返回新的hash。只要记录了的hash和文件名,就能进行简单的版本管理了。下面的例子演示了一个简单的版本恢复:首先修改test.txt,并将新值存入数据库,然后又通过hash将工作区的文件内容回退到上一个版本。
这种最基本的存储单元git称为blob。
支持文件名和集合(tree)
普通blob无法支持文件名和集合的存储,git引入tree来解决此问题。tree类似目录的概念,包含一系列blob/tree的hash和文件名。
创建tree的方法是,先用update-index命令将内容添加到暂存区,然后通过write-tree命令生成tree。
如下,先添加一项内容的暂存区。
ps:100644是linux文件类型常量。
如下,再添加一项内容到暂存区(update-index命令可直接传入文件名,此时git会做两个步骤,读取文件内容存至数据库和添加至暂存区)。
此时暂存区有两个项目,通过write-tree命令将暂存区内容生成tree,返回值一个hash。可以看到tree也是以一个blob来存储的。
支持备注信息(commit-tree)
前面已经解决了存储的问题(包括内容和文件名),但想添加一些额外的备注信息(比如作者、时间、说明等)还不行,commit-tree能够解决此问题。
以下代码对一个tree添加commit。commit-tree主要包含三部分:指向另一个tree的hash、作者信息、commit message。
commit-tree还有另外一个字段parent,指向前一个commit-tree的hash。以下代码创建了一个新的commit-tree并指向前者。
有了commit-tree之后,不仅能添加备注信息,还能形成一条commit链,通过这条链,能够更加方便地进行版本的切换和回退。
分支的本质(branch)
理论上commit-tree能够满足任意复杂的版本管理,只不过你需要记住一些关键节点的commit hash。hash对人是不友好的,git引入分支来记录关键节点的commit hash。分支就是一个普通的文本文件,文件名即分支名,内容即commit hash。
以下代码创建了一个master分支。
通过git log就能查看分支对应的commit链。
分支存储于.git/refs目录,本地分支位于heads目录,远程分支位于remotes目录,.git/HEAD指向当前所在的本地分支。从这里可以看到git分支模型确实是非常轻量,就是一个简单的文本文件而已。
远程同步(remotes)
仅仅本地进行版本管理还不够,要和远程仓库通信才能多人协作。git的远程仓库设计也很简单,本地和远程互为拷贝。在同步信息时,只需同步.git/objects和.git/refs/remotes目录的文件即可。数据传输可采用http/ssh协议,服务器交互地址配置于.git/config。
总结
git命令非常多,概念也相对较多。但git的底层逻辑设计是非常简单的。一个好的设计一定是简单的,一个好的设计一定是具有美感的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。