目录
1.1 校验和
Git 中数据在存储前都计算校验和,Git 用于计算校验和的机制叫做 SHA-1 散列(hash,哈希)。这是一个由 40 个十六进制字符组成的字符串,基于 Git 中文件的内容或目录结构计算出来。它看起来就像这样:
955c6ff0f64eaed91a16be52a65a0daa9c4049d3
Git 数据库中保存的信息都是以这个哈希值来索引,而不是文件名。
使用 hash-object
指令来测试下:
git hash-object
指令是 git 底层指令,用于计算数据对象的校验和。- 可以看出文件修改前和修改后,两次计算的结果是相同的,但是修改前和修改后的校验和发生了变化。
1.2 Git 存储了那些对象?
测试文件结构
|-- test
|-- test1.txt
|-- dir
|-- test2.txt
将文件提交到版本库后,在 /test/.git/objects 目录下会新增若干两个字符(十六进制字符)的文件夹,打开可以看到里面会有38个字符命名的文件。
有点巧(38 + 2 = 40),好像是校验和?
使用 git cat-file
指令去查看合在一起的校验和,可以看到是以下 3 类对象:
Git 底层指令
git cat-file
从 Git 库中获取文件信息,-t
参数可以查看对象类型,-p
可以查看文件的内容。
1.2.1 数据对象(Blob Objects)
Git 的数据对象
可以是任何内容, 是由 Git 生成的文件快照,但需要注意的是文件名并没有被保存,而是以文件的校验和
作为文件快照的文件名。
文件在添加到暂存库后,object 目录中就会创建文件的快照。文件的校验和
前两个字符用于命名子目录,余下的 38 个字符则用作文件名。
上图查看了两个文件在版本库中存储的数据对象
- 4f 开头的数据对象保存着文件 test1.txt 中的内容 “test 1”
- 81 开头的数据对象保存着文件 test2.txt 中的内容是 “test 2”
1.2.2 树对象(Tree Objects)
因为数据对象(文件快照)不存储文件名和目录信息,所以 Git 使用树对象
来保存文件名和目录结构。通常, Git 会根据某一时刻暂存区所表示的状态创建树对象。
- 树对象可以包含其他的树对象,这样就可以构成一个树形结构,用来保存目录信息
- 树对象中可以保存文件对应数据对象的索引(校验和),通过它就可以找到此次提交的文件快照。
如图所示,当前版本库创建了两个树对象(对应根目录和 dir 目录)
- 91 开头的树对象中包含了:
66 开头的树对象索引(校验和)
指向test1.txt 数据对象的索引(校验和) - 66 开头的树对象仅包含:
指向 test2.txt 数据对象的索引(校验和)
1.2.3 提交对象(Commit Objects)
提交对象用来保存本次提交的信息, 包括本次提交对应的树对象
索引、提交人、提交时间、以及提交的备注信息等。
如上图所示,提交对象中保存了 tree 条目, 指向了 91 开头的树对象(与根目录对应)。文件中还保存了提交时的说明信息 --“init”。
1.2.4 总结
通过对比 Git 存储的对象和工作区文件,可以知道:
- Git 通过
数据对象
(blob)存放工作区文件内容快照,并使用校验和
来索引数据对象。 - Git 通过
树对象
(tree)保存提交时的工作区目录结构、文件名和文件快照的索引。 - Git 通过
提交对象
(commit)保存提交人、提交时间、提交注释和提交时的根树对象。
1.3 Git 对象如何存储的?
1.3.1 存储结构
Git 是一个内容寻址文件系统, 它的核心部分是一个键值对数据库
(key-value data store)。数据对象、树对象和提交对象作为 Value,它们相应的校验和作为 key 存放到版本库中。这些信息就存放在文件系统中 .git/objects 目录下。
1.3.2 存储的过程
Git 的存储对象的过程可以通过下图简单示意:
初始化
git init
命令执行完成后, 当前目录出现 .git 文件夹, 其中的 objects 目录中只有 info 和 pack 这两个空目录。
添加到暂存区
git add
命令将文件添加到暂存区后,会为每个文件计算校验和
,然后会把当前版本的文件快照保存到 Git 仓库中。最终将校验和
加入暂存区等待提交
提交
git commit
指令进行提时,Git 为每个子目录计算校验和
并在 Git 仓库中保存为树对象
。随后, Git 会创建一个提交对象
,这个对象中除了保存提交信息外, 还包含指向项目根目录树对象
的指针。如此一来,通过提交对象就可以在需要时重现本次保存的快照。
1.4 Git 版本
说到这里, Git 的版本由来也就非常清楚了
GIt 的版本就是每次使用 git commit
指令提交时,生成的提交对象。通过 git log
查看到 Git 的版本号(1c9d77…),正是提交对象
的校验和,可以通过它获取提交对象的信息。
1.4.1 多次提交
后一次提交产生的提交对象会包含指向上次提交对象的指针(parent)。多次提交形成 Git 的版本链。
1.4.2 HEAD 指针
Git 中有很多指令都使用了 HEAD
做为参数, 那么 Head
到底是什么呢?我们通过以下命令查看下。
可以看出,HEAD 的类型也是
commit
,说明它也指向一个提交对象,这个提交对象的内容与6269
指向的提交对象其实是同一个。
所以,
HEAD
就是一个方便记忆和使用的指针,指向本地仓库当前版本。
1.5 总结
- Git 使用数据对象、树对象和提交对象和他们计算出来的
校验和
来存储文件快照、文件目录结构和版本信息(提交)。 暂存时
生成数据对象(文件快照),提交时
生成树对象和提交对象。- 提交对象的
校验和
就是仓库中的版本号,多次提交形成版本链,后一次提交通过parent
指向上次提交对象。 HEAD
是一个为了方便记忆和使用的指针,指向本地仓库的当前版本(提交对象
)