Git 基本上是如今文本文件版本管理的唯一方案。
从表面上看,Git 机制十分复杂,常用的包括 commit、fetch、merge、push 等多个命令,涉及提交、分支、冲突、合并等多种概念。
然而从底层原理上,Git 本质就是一个对象存储系统。
对象
Git 对象(Git Object)是 Git 能够管理的最小单位。 对象可以是一个特定版本的文件,也可以是一个目录、一个提交甚至是一个 Tag(仅限于 Annotated Tag)。 但底层 Git 实际上并不区分对象的类型,而是简单地把它们全部视作二进制数据块。
使用 git cat-file 命令可以查看特定对象的内容。
一般会用 -p 选项使 Git 自动判断对象的类型,以便以人类可以理解的方式打印:
git cat-file -p 7d126678e806745ba16e0eb540b340e9881a4f38
# tree 90ffbdf9d7d964db96957b5cb33ac38fe420c8d4
# parent 883101ea8034db1e79778f7f4b98c549ab79d19b
# author ... <...> 1766905722 +0800
# committer ... <...> 1766905722 +0800
#
# bugfix: ...
上述命令打印了哈希为 7d126678e806745ba16e0eb540b340e9881a4f38 的对象,是一个提交,因此以纯文本的方式打印了出来。
哈希
在平日的 Git 使用中,一般经常使用到提交的哈希。 但实际上,每个 Git 对象都有其哈希。
以前文中所打印的提交为例,其本身的哈希是 7d126678e806745ba16e0eb540b340e9881a4f38,其前序提交(parent)的哈希为 883101ea8034db1e79778f7f4b98c549ab79d19b,而其根目录的哈希为 90ffbdf9d7d964db96957b5cb33ac38fe420c8d4。
Git 根据如下内容计算一个对象的哈希:
- 对象的类型:对于提交、目录和文件,分别是字符串
commit、tree和blob; - 一个空格;
- 对象的长度,以字节为单位,十进制转换为字符串;
- 一个空字符(
\0); - 对象的实际内容。
将上述内容依次连接后,计算 SHA-1(最新的 Git 支持 SHA-256)作为这个对象的哈希值。
存储
所有 Git 对象都存储在 Git 根目录下的 .git/objects 目录中。
换而言之,只要这个目录不丢失,所有的数据就都可以恢复。
对象哈希的前 2 个字符作为一级目录名,而剩余 38 个字符作为文件名。
例如,哈希是 7d126678e806745ba16e0eb540b340e9881a4f38 的对象实际存储在 .git/objects/7d/126678e806745ba16e0eb540b340e9881a4f38 中。
这些文件里面存储的是经过 zlib 压缩的对象内容。
并非所有对象都以这种单独的形式(称为 Loose Object)存储。 一些对象会被打包存储在 Packfile 中。