包文件 (Packfiles)
在前面的章节中,我们要么在讨论松散的对象(Loose Objects),要么在讨论引用。如果 Git 只是简单地为每个文件、每个版本都存储一个完整的 Blob 对象,那随着项目的增长,.git 目录的大小会迅速失控。
幸运的是,Git 有一套非常聪明的空间优化机制:包文件 (Packfiles)。
松散对象 vs. 包文件
Section titled “松散对象 vs. 包文件”松散对象 (Loose Objects)
Section titled “松散对象 (Loose Objects)”当你运行 git add 或 git commit 时,Git 通常会以“松散对象”的格式将内容写入磁盘。
- 每个对象一个文件。
- 存储在
.git/objects/ab/cdef...这样的目录结构中。 - 内容经过 zlib 压缩,但仍然是完整存储。
包文件 (Packfiles)
Section titled “包文件 (Packfiles)”为了节省空间和提高网络传输效率,Git 会定期将这些松散对象打包成一个二进制文件,称为 Packfile。
- 存储在
.git/objects/pack/目录下。 - 配对出现:一个
.pack文件(数据)和一个.idx文件(索引,用于快速查找)。
增量存储 (Delta Compression)
Section titled “增量存储 (Delta Compression)”Git 的打包不仅仅是把文件拼在一起(像 zip 那样),它还进行了内容层面的去重。
如果在项目中你只修改了某个大文件的一行代码,Git 在打包时不会存储两个完整的大文件。它会:
- 选择一个合适的版本作为基对象 (base) 完整存储(具体选择由打包算法的启发式策略决定)。
- 计算其他版本与基对象的差异。
- 只存储这个二进制 delta(增量指令流),而非文本 diff。
这种机制使得 Git 仓库通常比对应的 SVN 仓库还要小,尽管 Git 存储了完整的历史记录!
演示:垃圾回收 (GC)
Section titled “演示:垃圾回收 (GC)”我们可以手动触发这个打包过程,这通常通过 git gc (Garbage Collection) 命令完成。
-
查看松散对象数量
Terminal window find .git/objects -type f | wc -l -
运行 GC
Terminal window git gc注意:
git gc还会清理悬空对象(dangling objects),即那些不再被任何引用指向的提交。但默认情况下,悬空对象会保留一段时间(通常为 2 周),不会被立即删除。 -
再次查看 此时你会发现
.git/objects/下的大量子目录不见了,取而代之的是pack/目录下的.pack和.idx文件。
Git 什么时候打包?
Section titled “Git 什么时候打包?”Git 非常智能,它会在以下情况自动进行打包:
- 此时仓库中有太多的松散对象(通常默认阈值是 6700 个左右)。
- 你向远程仓库执行
git push时(为了高效传输)。 - 你执行
git pull从远程获取数据时(远程传给你的就是包文件)。
深入 Packfile 结构
Section titled “深入 Packfile 结构”Packfile 的内部结构非常紧凑:
- 头部:包含签名 (
PACK) 和版本号。 - 对象条目:一系列的对象数据。每个条目包含对象类型、解压后的大小和压缩数据(或 Delta 数据)。
- 尾部:整个包文件的 SHA-1 校验和。
因为 Packfile 内容是紧密排列的,无法直接通过文件名查找,所以必须配合 .idx 索引文件使用。索引文件记录了每个对象哈希值在 .pack 文件中的字节偏移量。
- 松散对象:写入快,读取快,但占空间大。
- 包文件:存储极其高效,利用 Delta 机制消除冗余,适合长期存储和网络传输。
- Git 会自动在两者之间通过
gc进行转换,保证既有高性能的操作体验,又有高效的存储结构。