Skip to content

对象存储原理

Git 的底层是一个内容寻址文件系统(content-addressable filesystem)。理解对象存储的原理,有助于深入理解 Git 的数据模型。

内容寻址文件系统

Git 根据内容来存储和查找数据,而不是文件名或路径。相同内容的数据只存储一份。

bash
# 演示内容寻址
echo "Hello, Git!" | git hash-object --stdin
# 8ab686eafeb1f44702738c8b0f24f2567c36da6d

# 无论存储多少次,相同内容的哈希永远相同
echo "Hello, Git!" | git hash-object --stdin
# 8ab686eafeb1f44702738c8b0f24f2567c36da6d(完全一样)

# 写入对象存储(-w 参数)
echo "Hello, Git!" | git hash-object --stdin -w
# 8ab686eafeb1f44702738c8b0f24f2567c36da6d

松散对象与打包对象

松散对象(Loose Objects)

每个对象单独存储为一个文件,路径由 SHA-1 的前 2 位(目录)和后 38 位(文件名)组成:

.git/objects/8a/b686eafeb1f44702738c8b0f24f2567c36da6d

存储格式:

原始内容 = "<类型> <长度>\0<实际内容>"
存储内容 = zlib 压缩(原始内容)

例如,Blob 对象 "Hello, Git!\n":

原始内容 = "blob 13\0Hello, Git!\n"
文件内容 = zlib压缩("blob 13\0Hello, Git!\n")

松散对象的问题:

  • 文件数量多,文件系统性能下降
  • 没有利用对象间的相似性(全量存储,而非差异)
  • 网络传输效率低

打包对象(Packed Objects)

当松散对象过多,或执行 git gcgit push 时,Git 会将多个对象打包成 Pack 文件:

.git/objects/pack/
├── pack-abc123.idx    ← 索引文件(快速查找)
└── pack-abc123.pack   ← 数据文件(存储压缩数据)

打包的优化:

  • 利用对象间的相似性,存储差异而非全量
  • 减少文件数量,提升文件系统性能
  • 更小的存储空间和传输体积

git cat-file -t / -p 查看对象

bash
# 查看对象类型
git cat-file -t abc123
# blob(或 tree、commit、tag)

# 查看对象内容(格式化显示)
git cat-file -p abc123

# Blob 对象:显示文件内容
git cat-file -p HEAD:README.md
# # My Project...

# Tree 对象:显示目录结构
git cat-file -p HEAD^{tree}
# 100644 blob abc123 README.md
# 040000 tree def456 src

# Commit 对象:显示提交信息
git cat-file -p HEAD
# tree abc...
# parent def...
# author ...
# committer ...
# 
# feat: 添加功能

# 查看文件大小
git cat-file -s abc123
# 13(字节)

git hash-object 计算哈希

bash
# 计算文件的 SHA-1(不写入存储)
git hash-object README.md

# 计算并写入对象存储(-w)
git hash-object -w README.md

# 从 stdin 读取内容
echo "test content" | git hash-object --stdin

# 指定对象类型
echo "test" | git hash-object --stdin -t blob
echo "test" | git hash-object --stdin -t tag

git ls-tree 查看树对象

bash
# 查看 HEAD 指向的根树对象
git ls-tree HEAD

# 100644 blob abc123  README.md
# 040000 tree def456  src
# 100755 blob ghi789  run.sh

# 递归显示所有文件
git ls-tree -r HEAD

# 只显示文件名
git ls-tree -r --name-only HEAD

# 查看特定目录
git ls-tree HEAD src/

# 查看某个提交的特定路径
git ls-tree abc123 -- src/app.js

git ls-files --stage 查看暂存区

bash
# 查看暂存区中的所有文件
git ls-files --stage
# 100644 abc123... 0  README.md
# 100644 def456... 0  src/app.js

# 格式:<模式> <SHA-1> <冲突阶段>  <路径>
# 冲突阶段:0=正常,1=共同祖先,2=当前,3=被合并

# 只显示文件名
git ls-files

# 显示修改的文件
git ls-files -m

# 显示未跟踪的文件
git ls-files -o

对象统计和验证

bash
# 统计对象数量和存储大小
git count-objects
# 12 objects, 56 kilobytes

# 详细统计
git count-objects -v
# count: 12(松散对象数)
# size: 56(松散对象大小,KiB)
# in-pack: 1024(打包对象数)
# packs: 2(pack 文件数)
# size-pack: 512(pack 文件大小,KiB)
# prune-packable: 0(可以清除的冗余对象)
# garbage: 0(垃圾对象数)
# size-garbage: 0(垃圾对象大小)

# 验证对象存储的完整性
git fsck
git fsck --full    # 更彻底的检查

实践:手动创建对象

bash
# 手动创建一个 Blob 对象
echo "Hello, World!" | git hash-object --stdin -w
# abc123...

# 手动创建 Tree 对象(通常由 Git 自动管理)
git mktree << 'EOF'
100644 blob abc123  hello.txt
EOF
# 返回 tree 的 SHA-1

# 手动创建 Commit 对象
git commit-tree <tree-sha> -m "手动创建的提交"
# 返回 commit 的 SHA-1

总结

操作命令
计算对象哈希git hash-object <file>
写入对象存储git hash-object -w <file>
查看对象类型git cat-file -t <sha>
查看对象内容git cat-file -p <sha>
查看树对象git ls-tree HEAD
查看暂存区git ls-files --stage
统计对象git count-objects -v

深入理解 Git 的对象存储原理,会让你对 Git 的各种操作(如 reset、rebase、cherry-pick)有更清晰的认识——它们本质上都是在操纵这些对象和引用。