第 6 章 · 01 对象模型:内容寻址存储 本节摘要:这是第 6 章的地基。Git 最优雅、最核心的设计是它的「对象模型」——所有数据都存成「对象」,每个对象用「内容的 SHA1 哈希」作为键。这叫「内容寻址存储」:相同内容得到相同哈希(键),天然去重、天然校验。本节讲清四类对象(blob 文件内容、tree 目录结构、commit 快照、tag 标签),以及为什么 Git 用快照而非差异存历史。理解对象模型,Git 的所有命令都是它的自然推论。 内容来源:基于「Build your own X」Git 域与 Git 内部模型整理的导读。 学习目标 阅读完本节,你应当能够: 说清「内容寻址存储」:用内容哈希做键,相同内容得相同键。
本节摘要:这是第 6 章的地基。Git 最优雅、最核心的设计是它的「对象模型」——所有数据都存成「对象」,每个对象用「内容的 SHA1 哈希」作为键。这叫「内容寻址存储」:相同内容得到相同哈希(键),天然去重、天然校验。本节讲清四类对象(blob 文件内容、tree 目录结构、commit 快照、tag 标签),以及为什么 Git 用快照而非差异存历史。理解对象模型,Git 的所有命令都是它的自然推论。
内容来源:基于「Build your own X」Git 域与 Git 内部模型整理的导读。
阅读完本节,你应当能够:
无数人用 Git 几年,仍说不出「commit 到底存了什么」「分支是什么」。根因是没理解 Git 的对象模型——而它其实是 Git 最优雅的部分。一旦你理解「Git 是一个内容寻址的对象数据库,所有命令都是在这个数据库上读写对象」,git add/commit/branch/log 全部豁然开朗。
本节建立这个心智模型,后续四节(add、commit、分支、合并)都是它的展开。
传统文件系统用「文件名」做键(你给文件起名)。Git 用「内容的 SHA1 哈希」做键:
内容 "Hello, world!" → SHA1 = 943a... 这个对象存进 .git/objects/94/3a...(哈希前两位做目录) 键 = 哈希, 值 = 内容
好处:
1. blob(文件内容)
存文件的内容字节流,不含文件名。
blob: "Hello, world!" 哈希: 943a...
2. tree(目录结构)
存一个目录:它的「文件名 → blob 哈希」映射,以及子目录 → 子 tree 哈希。
tree: README.md → blob aaa1...(内容哈希) src/ → tree bbb2...(子目录的 tree 哈希)
3. commit(提交快照)
存「一次提交」:指向一个顶层 tree(项目根)+ 父 commit(可能多个,合并产生)+ 作者/时间/信息。
commit: tree: ccc3...(项目根的 tree 哈希) parent: ddd4...(上一次 commit) author: alice <...> message: "fix bug"
4. tag(标签,可选)
带标注的指向某个 commit 的引用。
很多人误以为「Git 存的是相对上次的差异」(像某些 VCS)。其实 Git 存的是每次 commit 时的完整快照——commit 指向的 tree 描述「这次提交时整个项目的状态」。
commit3 (tree: 描述现在的全部文件) │ parent commit2 (tree: 描述上次的全部文件) │ parent commit1 (tree: 描述最初的全部文件)
如果某文件两次 commit 间没变,它的 blob 哈希相同,两个 tree 都指向同一个 blob——内容寻址天然去重,「存完整快照」并不浪费空间(不变的部分共享)。这就是为什么 Git 切分支这么快(分支只是指针,快照已存在)、为什么历史是快照链而非差异链。
hash_object(类型, 内容):算 SHA1,存到 .git/objects/。read_object(哈希):按哈希取出对象。git cat-file -p 哈希 在真实 Git 仓库里看对象,对照你的实现。下一节讲索引与 staging——git add 到底干了什么。