10 — 对象模型:.git/objects 里到底装了什么

10 --- 对象模型:.git/objects 里到底装了什么

摘要: 本章深入解析 Git 对象模型,揭示 .git/objects 目录中哈希文件的本质。你将理解 Git 存储的四种核心对象:blob(文件内容)、tree(目录结构)、commit(提交)和 tag(标签),掌握它们如何通过内容寻址构成不可变的快照链。通过动手实践 git cat-file 等底层命令,你将直观看到「改一字节即生新对象」的原理,理解路径信息存储在 tree 而非 blob 中,并建立「Git 历史是对象链而非差异日志」的思维模型。最终,你将能亲手查看对象、绘制对象关系图,并为后续理解 Git 命令如何操作对象打下坚实基础。

写在前面:这一章要解决什么

上一章你打开了 .git,看到 objects/ 里有一堆哈希命名的文件。这一章就告诉你 那堆东西是什么------

  • 提交(commit)到底是什么
  • 为什么说「Git 存的是快照不是差异」
  • blob、tree、commit、tag 四类对象各装什么
  • 怎么用 git cat-file 亲自看

学完后,你应该能:

  1. 用大白话解释四类对象
  2. git cat-file 亲手查看对象内容
  3. 理解「改一字节就变新哈希」
  4. 不再觉得 Git 历史是玄学

读者设定: 大一同学,看过 .git 地图,对「哈希」有概念即可。


1. 定位:为什么要讲对象

1.1 一句话先记住

Git 的历史就是四种对象拼起来的:blob(文件内容)、tree(目录结构)、commit(提交)、tag(附注标签)。

每类对象都有自己的格式,用一个哈希当「身份证号」。

1.2 看了对象能懂什么

不看 看了
提交是「一次改动日志」 提交指向当时整棵目录树
改文件就改了提交 改文件 = 新 blob + 新 tree + 新 commit
分支是文件夹拷贝 分支只是指向某 commit 的名字
rebase/cherry-pick 像魔法 都是对象复制 + 重连

1.3 和你已经会的对比

你已经会的 对象对应
文件内容 blob(只存内容,不存路径)
文件夹里的清单 tree(路径 → 哪个 blob)
一次存档 + 说明 commit(指向 tree + 作者 + 说明)
给存档贴标签 tag(附注标签是个对象)

1.4 本章内容目标

目标 你能做到
说清四类 用白话讲清 blob/tree/commit/tag
查看 cat-file 亲手看对象
理解哈希 改一字节 = 新对象
画图 画一次提交的对象关系图

2. 本质:四类对象各装什么

2.1 一张图先建立感觉

图:commit 指向 tree,tree 列出路径 → blob。分支名指向 commit。

2.2 用白话拆开四类

类型 装什么 谁的身份证
blob 文件的内容字节(不含路径、不含名字) 内容的哈希
tree 目录清单:哪个路径对应哪个 blob/tree 清单内容的哈希
commit 指向一棵 tree + 父提交 + 作者 + 说明 提交内容的哈希
tag 指向某对象 + 标注者 + 说明(附注标签) 标签对象内容的哈希

2.3 关键性质

性质 白话
内容寻址 哈希是根据内容算的;同内容 = 同哈希
不可变 改一字节就变新对象;旧对象还在
去重 同样内容的 blob 只存一份
路径在 tree 里 blob 不知道自己叫啥

2.4 几条硬规矩

规矩 白话
哈希是身份证 同内容同哈希;不同内容必不同
提交指向 tree,不是直接指向文件 「快照」结构
改文件 = 新对象链 blob → tree → commit 都变
轻量标签不是对象 只是个引用;附注标签才是对象

2.5 新手最常踩的坑

表现 办法
以为提交存的是「差异」 期待 diff 输出 提交存的是整棵树快照
改文件以为「改了提交」 困惑哈希变了 改文件会造新 commit
以为 blob 知道路径 想从 blob 找文件名 路径在 tree 里
背哈希 记不住 看说明,不背哈希

3. 建议学习顺序

text 复制代码
先看一张图
  → 建文件 + add + commit
  → 用 cat-file 一步步看
  → 画对象关系图
  →(预告)下一章看命令动了哪些对象

4. 动手准备

bash 复制代码
mkdir lab-objects
cd lab-objects
git init -b main
git config user.name "Ada Example"
git config user.email "ada@example.com"

5. 跟着做:亲手查看对象

5.1 建文件 + add,看 blob

bash 复制代码
printf 'alpha\n' > a.txt
git add a.txt
git rev-parse :a.txt

输出一串哈希,类似:

text 复制代码
4a58007052a65fbc2fc3f910f2855f45a4058e74

这是 a.txt 内容(alpha\n)对应的 blob 哈希git rev-parse :a.txt 的意思是「暂存区里 a.txt 那个对象的哈希」。

看对象类型和内容:

bash 复制代码
git cat-file -t 4a58007052a65fbc2fc3f910f2855f45a4058e74
git cat-file -p 4a58007052a65fbc2fc3f910f2855f45a4058e74
text 复制代码
blob
alpha

白话翻译:

  • -t 看类型:blob
  • -p 看内容:就是 alpha\n(文件内容本身)
  • blob 不含路径,只含内容字节

5.2 commit,看 commit 和 tree

bash 复制代码
git commit -m "add a.txt"

拿到 commit 哈希:

bash 复制代码
git rev-parse HEAD

类似:

text 复制代码
bf63851b0799854999c00cbb38cc495e1d6f0e41

看 commit 内容:

bash 复制代码
git cat-file -p HEAD
text 复制代码
tree 42d4c5245460645340a0b5b189f055b93cca0f7e
author Ada Example <ada@example.com> 1784883528 +0800
committer Ada Example <ada@example.com> 1784883528 +0800

add a.txt

白话翻译:

  • commit 指向一棵 tree(哈希 42d4c52...
  • 有作者、提交者、时间
  • 有说明 add a.txt
  • 注意:没有直接列文件,文件在 tree 里

看 tree:

bash 复制代码
git cat-file -p 'HEAD^{tree}'
text 复制代码
100644 blob 4a58007052a65fbc2fc3f910f2855f45a4058e74	a.txt

白话翻译:

  • tree 是一张清单:路径 a.txt、模式 100644、指向哪个 blob
  • 这就是「路径」住在的地方

也可以用更顺手的:

bash 复制代码
git ls-tree HEAD

5.3 改文件再提交,看哪些哈希变了

bash 复制代码
printf 'alpha\nbeta\n' > a.txt
git add a.txt
git commit -m "update a.txt"
git log --oneline
text 复制代码
b6a0b1b update a.txt
bf63851 add a.txt

对比:

对象 第一次 第二次
a.txt 的 blob 4a58007...(内容 alpha\n 新哈希(内容 alpha\nbeta\n
tree 42d4c52... 新哈希(清单里 blob 变了)
commit bf63851... b6a0b1b...(指向新 tree + 父提交)

白话翻译: 改文件 → 新 blob → 新 tree → 新 commit。旧对象还在,所以你能回看老版本。

5.4 不写文件也能算哈希

bash 复制代码
printf 'hello\n' | git hash-object --stdin
text 复制代码
ce013625030ba8dba906f756967f9e9ca394464a

白话翻译: git hash-object --stdin 把 stdin 的内容算成 blob 哈希,但不一定写入对象库。这是 Git 「内容寻址」的最直观体验------同内容永远同哈希。

图:再看一遍------commit 指向 tree,tree 列出路径→blob。分支名只是指向 commit 的便利贴。


6. 四类对象速查

6.1 各装什么

类型 装什么 怎么看
blob 文件内容 git cat-file -p <哈希>
tree 路径清单 → blob/tree git ls-tree HEAD
commit tree + 父 + 作者 + 说明 git cat-file -p HEAD
tag 指向对象 + 标注者 + 说明 git cat-file -p <标签>

6.2 上层 vs 底层命令

上层(日常) 底层(探查)
commitlog cat-filehash-objectls-treerev-parse

底层命令(plumbing)给你看原始对象;上层命令(porcelain)给你友好输出。


7. 对照表:降低记忆负担

7.1 一次提交的对象关系

text 复制代码
commit  bf63851...
  │
  ├─ tree  42d4c52...
  │    └─ "a.txt" → blob 4a58007...  ("alpha\n")
  ├─ parent: (根提交无)
  └─ message / author

7.2 路径 vs 内容

blob tree
存路径吗 不存
存内容吗 存字节 存「路径→哈希」清单
改文件会变吗 内容变就变 清单变了就变

7.3 轻量 vs 附注标签

轻量标签 附注标签
是对象吗 不是,只是引用 是,tag 对象
带说明吗 不带
怎么建 git tag 名字 git tag -a 名字 -m "..."

8. 安全习惯

8.1 建议这样做

习惯 原因
cat-file 看,别手编辑对象 对象是不可变的二进制
多次提交后画一画对象图 加深理解
备份 .git 就等于备份对象 历史都在对象库里
改文件后看哪些哈希变 直觉建立

8.2 别这样做

原因
手改 objects/ 里的文件 破坏完整性
以为删提交就没了 对象可能还在,直到 gc
背哈希 没意义,每次都不同

9. 真实场景

9.1 「历史里那个文件长啥样」

bash 复制代码
git show <老提交>:a.txt

底层就是:找到那个 commit → 它的 tree → 找到 a.txt 的 blob → 打印内容。

9.2 「为什么改一行提交哈希就全变」

因为改一行 → 新 blob → 新 tree → 新 commit。父链上的后续 commit 也会因为父指针变了而全部换哈希(这就是 rebase 会改哈希的原因,进阶章细讲)。

9.3 「内容相同会重复存吗」

不会。同内容 = 同哈希 = 同一个 blob 对象复用。


10. 稍微多懂一点点(可选)

  1. 哈希算法:现代 Git 默认 SHA-1;新版本支持 SHA-256。
  2. 对象存的是 zlib 压缩 :所以 .git 不会因为重复内容膨胀。
  3. packfile :很多对象压缩打包放 objects/pack/gc 维护)。
  4. git fsck:检查对象完整性,发现损坏或悬空对象。

11. 小实验(请一定动手)

实验甲:看一次提交的三个对象

  1. 建仓、建 a.txtaddcommit
  2. git cat-file -p HEAD 拿到 tree 哈希。
  3. git ls-tree HEAD 看 tree 清单。
  4. 在纸上画:commit → tree → blob。

实验乙:改文件看哈希变

  1. a.txtaddcommit
  2. 对比两次的 blob 哈希、tree 哈希、commit 哈希。
  3. 确认旧 blob 还在:git cat-file -p <旧 blob 哈希>

实验丙:算哈希

  1. printf 'hello\n' | git hash-object --stdin
  2. 改一个字符再算,确认哈希变。

通过标准: 能画一次提交的对象关系图,并解释「改一字节 = 新对象」。


12. 常见问题

问 1:blob 是文件吗?

不是。blob 是文件内容字节,不含路径。

问 2:路径存在哪?

在 tree 里。tree 是「路径 → blob/tree 哈希」的清单。

问 3:提交存的是差异吗?

不是。提交指向一棵 tree(整份快照)。差异是查询时算出来的。

问 4:哈希每次都不同吗?

同内容同哈希;改了就变。所以别背哈希。

问 5:附注标签和轻量标签啥区别?

附注标签是个对象,带说明;轻量标签只是个引用。

问 6:删了提交对象就没了吗?

短时间内可能还在,直到 gcreflog 能看到。

问 7:cat-file 安全吗?

安全,只读。

问 8:.git 多大和对象有关吗?

有关。对象越多越大;gc 打包压缩。


13. 总结、学习路线与思维升华

13.1 这一章请记住的

记住什么
四类对象 blob / tree / commit / tag
内容寻址 同内容同哈希
不可变 改 = 新对象
提交指向 tree 快照结构
路径在 tree blob 不知路径
下一章 命令动了哪些对象/引用

13.2 在整个系列中的位置

text 复制代码
09 .git 地图
10 对象模型          ← 当前
11 操作→文件
12 引用与 HEAD
13 暂存区深入
14 历史是图

13.3 思维升华

历史不是差异日志,而是不可变快照串成的链。

改一字节就长出新对象;旧对象还在,所以你能回看。

理解对象,rebasecherry-pick 就不再是魔法,而是「复制对象 + 重连」。

13.4 参考资料

命令输出样例验证环境:Git 2.43.0 ;演示作者信息为虚构:Ada Example <ada@example.com>

13.5 本章检查清单

  • 能用白话讲清四类对象
  • cat-file 看过 blob/tree/commit
  • 改文件后对比过哈希变化
  • 画过一次提交的对象关系图
  • 知道路径在 tree 里,不在 blob
  • 用过 hash-object --stdin 算哈希
  • 知道旧对象改后还在

理解了对象,Git 的历史就不再是玄学。下一章把命令和「动了哪些对象/引用」对上号。

相关推荐
沫璃染墨3 小时前
《从零入门Linux系统篇(十四):系统工具篇·五——Git版本控制:从版本管理到协同开发》
linux·运维·服务器·git·gitee·github
Slow菜鸟4 小时前
第5篇:进阶规范篇 · 团队协作最佳实践
git
qingyulee4 小时前
git常用指令
大数据·git·elasticsearch
雨声不在5 小时前
Git fetch 失败: gnutls_handshake() failed 的排查与修复
git
snowfoootball6 小时前
fork的仓库怎么使用git rebase与git merge同步上游仓库的更新及解决冲突问题的总结
git
小溪彼岸6 小时前
Git命令行可视化管理工具:lazygit
git
badhope7 小时前
入职第一天就把main分支搞崩了——我的Git血泪史和团队规范诞生记
git·devops
Eloudy9 小时前
git clone --mirror 完整迁移仓库的全流程
git
:-)1 天前
开源项目二开 Git 规范工作流
git