跳转到内容

包文件 (Packfiles)

在前面的章节中,我们要么在讨论松散的对象(Loose Objects),要么在讨论引用。如果 Git 只是简单地为每个文件、每个版本都存储一个完整的 Blob 对象,那随着项目的增长,.git 目录的大小会迅速失控。

幸运的是,Git 有一套非常聪明的空间优化机制:包文件 (Packfiles)

当你运行 git addgit commit 时,Git 通常会以“松散对象”的格式将内容写入磁盘。

  • 每个对象一个文件。
  • 存储在 .git/objects/ab/cdef... 这样的目录结构中。
  • 内容经过 zlib 压缩,但仍然是完整存储。

为了节省空间和提高网络传输效率,Git 会定期将这些松散对象打包成一个二进制文件,称为 Packfile

  • 存储在 .git/objects/pack/ 目录下。
  • 配对出现:一个 .pack 文件(数据)和一个 .idx 文件(索引,用于快速查找)。

Git 的打包不仅仅是把文件拼在一起(像 zip 那样),它还进行了内容层面的去重

如果在项目中你只修改了某个大文件的一行代码,Git 在打包时不会存储两个完整的大文件。它会:

  1. 选择一个合适的版本作为基对象 (base) 完整存储(具体选择由打包算法的启发式策略决定)。
  2. 计算其他版本与基对象的差异。
  3. 只存储这个二进制 delta(增量指令流),而非文本 diff。

这种机制使得 Git 仓库通常比对应的 SVN 仓库还要小,尽管 Git 存储了完整的历史记录!

我们可以手动触发这个打包过程,这通常通过 git gc (Garbage Collection) 命令完成。

  1. 查看松散对象数量

    Terminal window
    find .git/objects -type f | wc -l
  2. 运行 GC

    Terminal window
    git gc

    注意:git gc 还会清理悬空对象(dangling objects),即那些不再被任何引用指向的提交。但默认情况下,悬空对象会保留一段时间(通常为 2 周),不会被立即删除。

  3. 再次查看 此时你会发现 .git/objects/ 下的大量子目录不见了,取而代之的是 pack/ 目录下的 .pack.idx 文件。

Git 非常智能,它会在以下情况自动进行打包:

  • 此时仓库中有太多的松散对象(通常默认阈值是 6700 个左右)。
  • 你向远程仓库执行 git push 时(为了高效传输)。
  • 你执行 git pull 从远程获取数据时(远程传给你的就是包文件)。

Packfile 的内部结构非常紧凑:

  • 头部:包含签名 (PACK) 和版本号。
  • 对象条目:一系列的对象数据。每个条目包含对象类型、解压后的大小和压缩数据(或 Delta 数据)。
  • 尾部:整个包文件的 SHA-1 校验和。

因为 Packfile 内容是紧密排列的,无法直接通过文件名查找,所以必须配合 .idx 索引文件使用。索引文件记录了每个对象哈希值在 .pack 文件中的字节偏移量。

  • 松散对象:写入快,读取快,但占空间大。
  • 包文件:存储极其高效,利用 Delta 机制消除冗余,适合长期存储和网络传输。
  • Git 会自动在两者之间通过 gc 进行转换,保证既有高性能的操作体验,又有高效的存储结构。