Git 与大多数其他版本控制系统 (VCS) 不同。
大多数 VCS-es 存储各种形式的“增量”。例如,如果整个存储库中最尖端的提交是C9,由master 标识,并且您提取它,您可能会按原样获取存储库中的所有文件,而如果您提取C5(之前的提交从C9),您将从所有最新文件开始,然后C5 说“撤消这个,撤消那个,撤消另一件事”,版本控制系统会撤消这些,这会让您获得截至提交C5。
同样,git 不会 这样做。
相反,git 的存储库存储 git 所谓的“对象”。有四种类型的对象:“commits”、“annotated tags”、“trees”和“blob”。我们将忽略带注释的标签(为此目的不需要它们),只考虑其他三个。
每个对象都有一个唯一的 160 位名称,以 SHA-1 哈希表示。哈希值是通过计算对象内容(加上其类型)的 SHA-1 来构造的。 Git 假定存储库中没有两个 不同的 对象将永远计算相同的 SHA-1(如果它们这样做,git 会乱七八糟地爆炸;但这从未发生过)。 (但请注意,same 对象——例如,许多提交中的相同 foo.c 文件——具有一个唯一的 SHA-1。)
提交对象如下所示:
$ git cat-file -p 5f95c9f850b19b368c43ae399cc831b17a26a5ac
tree 972825cf23ba10bc49e81289f628e06ad44044ff
parent 9c8ce7397bac108f83d77dfd96786edb28937511
author Junio C Hamano <gitster@pobox.com> 1392406504 -0800
committer Junio C Hamano <gitster@pobox.com> 1392406504 -0800
Git 1.9.0
Signed-off-by: Junio C Hamano <gitster@pobox.com>
也就是说,它有一个tree、一个parents 的列表、一个author-and-date、一个committer-and-date 和一条短信。这也是它的全部。每个parent 都是父提交的SHA-1;根提交没有父提交,合并有多个父提交,但大多数提交只有一个父提交,这就是您发布的图表中的箭头。
树对象如下所示:
$ git cat-file -p 972825cf23ba10bc49e81289f628e06ad44044ff
100644 blob 5e98806c6cc246acef5f539ae191710a0c06ad3f .gitattributes
100644 blob b5f9defed37c43b2c6075d7065c8cbae2b1797e1 .gitignore
100644 blob 11057cbcdf4c9f814189bdbf0a17980825da194c .mailmap
100644 blob 536e55524db72bd2acf175208aef4f3dfc148d42 COPYING
040000 tree 47fca99809b19aeac94aed024d64e6e6d759207d Documentation
100755 blob 2b97352dd3b113b46bbd53248315ab91f0a9356b GIT-VERSION-GEN
[snip lots more]
树为您提供了与该提交相关的顶级目录。大多数树条目是blobs;子目录更多trees。 blob 的 mode 为您提供可执行位(这些看起来像 Unix 文件模式,但 git 实际上只使用一个可执行位,因此模式始终为 100644 或 100755)。还有一些特殊情况的模式(例如,符号链接),但我们现在可以忽略它们。在任何情况下,每个条目都有另一个唯一的 SHA-1,这是 git 查找下一个条目(子树或 blob)的方式。
每个blob 对象 都包含实际文件。例如,GIT-VERSION-GEN 的 blob 是 git 版本生成器脚本:
$ git cat-file -p 2b97352dd3b113b46bbd53248315ab91f0a9356b
#!/bin/sh
GVF=GIT-VERSION-FILE
DEF_VER=v1.9.0
[snip]
所以,要提取提交,git 只需要:
- 将
HEAD 或master 等符号名称转换为提交的SHA-1
- 提取提交对象以找到顶层树
- 提取顶级树对象以查找所有文件和子树
- 对于每个文件,提取文件对象;对于每个子树,递归地提取该树及其对象。
(Git 对象被压缩存储,并最终被进一步压缩为“包文件”,确实使用 deltas,但与其他 VCS-es 的方式非常不同。没有必要 delta-将文件foo.c 压缩到foo.c 的先前版本;例如,git 可以对树进行增量压缩,或者对某些文档进行一些 C 代码。确切的包文件格式也经历了几次修改:如果一些未来的版本有更好的压缩方式,例如,包格式可以从版本 4 更新到版本 5。无论如何,“松散”对象只是 zlib 压缩而不是 delta 压缩。这使得访问和更新它们的速度非常快。包文件用于更静态的项目(未修改的文件)和网络传输。它们是在git gc 期间构建的,并且还用于推送和获取操作[使用称为“的变体”薄”包装,如果可能的话]。)
有关允许您读取和写入单个对象的更多 git“管道”命令,请参阅 the Pro Git book(提醒自 gatkin's answer)。