Git Internals

Git 对象数据库

理解 blob、tree、commit、tag 四类对象,以及 Git 为什么说自己是内容寻址的对象数据库。

适合谁看
  • 想建立稳定 Git 心智模型的学习者
  • 经常遇到历史、引用、恢复问题的开发者
前置知识
  • 会看基础命令输出
  • 知道提交、分支、HEAD 这些名词
常见风险
  • 只背底层术语却不连接到实际命令
  • 把对象、引用、工作区混成一层理解

一句话理解

Git 最底层不是“文件版本列表”,而是一个把内容写成对象、再用对象 ID 反向定位内容的数据库。

先别把 Git 想成网盘

更稳的理解是:Git 先保存对象,再用分支、HEAD、tag 这些“名字”指向对象。你平时看到的是名字,Git 真正长期保存的是对象。

为什么对象模型是第一层基础

Git 对象数据库的四类对象Git 以四类对象为核心:blob 存储文件内容,tree 组织目录结构,commit 记录历史快照,tag 为提交打标签。所有对象通过哈希 ID 互相链接。
Blob → 文件内容
ABCD
引用指向: main
Tree → 目录结构
ABCM
BEF
Commit → 历史快照 | Tag → 命名标记
ABCE'F'
引用指向: feature

很多 Git 现象只有放到对象模型里才说得通:

  • 为什么一次 commit 会生成新的对象
  • 为什么同样内容在不同分支上仍然可以复用
  • 为什么 branch 创建几乎是瞬时的
  • 为什么 reset 之后旧提交往往还没有真的消失

1. 什么叫内容寻址

Git 写入对象时,不是先给对象随便编号,而是先根据内容计算对象 ID。也就是说:

  1. 内容先存在
  2. ID 由内容决定
  3. 之后 Git 再通过这个 ID 反向找到对象

这也是“content-addressable”最重要的含义。

2. 四类最常见对象

blob

blob 只保存文件内容,不保存文件名,也不保存它在目录树中的位置。

tree

tree 负责表达目录结构。它会记录目录下有哪些名字,以及这些名字指向哪个 blob 或哪个子 tree。

commit

commit 用来固定一次项目状态。它通常会记录:

  • 当前快照对应的 tree
  • 父提交
  • 作者和提交者信息
  • 提交说明

tag

tag 为某个对象提供更稳定、更可读的名字,常见于发布版本。

3. 为什么 blob 不保存文件名

这件事经常让人第一次接触 Git internals 时觉得反直觉。Git 把“内容”和“结构”分开保存:

  • 文件内容放在 blob
  • 文件名和目录位置放在 tree

好处是内容复用会更自然。只要内容一样,底层就能共享对象,而不必把“路径”和“内容”绑死。

4. 一次 commit 在对象层到底发生了什么

从对象数据库的视角看,一次提交大致是:

  1. 根据暂存区内容写出 tree
  2. 创建一个新的 commit 对象
  3. 让 commit 指向这次 tree 和父提交
  4. 再把当前分支引用移动到新提交

所以 commit 的本质不是“改写旧历史”,而是“新增对象,再移动名字”。

5. 对象和工作区不是一回事

很多混乱来自把这两层混在一起。更稳的理解是:

  • 工作区:你正在编辑的文件
  • 暂存区:准备写入下一次快照的内容
  • 对象库:已经被 Git 固化下来的对象

只有进入对象库之后,内容才真正成为 Git 历史的一部分。

6. 为什么对象模型能解释很多命令

git add

它不是直接创建 commit,而是把当前内容朝下一次 tree 靠近。

git commit

它会真正创建新的 tree 和 commit 对象。

git branch

它通常不需要复制对象,只是新建一个指向已有提交的引用。

git checkout / git switch

它们会让工作区去匹配另一个引用所指向的对象状态。

7. 对象“存在”不等于“容易找到”

这是理解恢复操作时最关键的一点。一个对象就算还在数据库里,只要没有分支、tag、reflog 之类的名字把它接住,你就会越来越难找到它。

所以很多恢复问题其实不是“对象没了”,而是“引用丢了”。

这条理解对恢复特别重要

很多“东西丢了”的 Git 事故,本质并不是对象被立刻删除,而是当前没有一个明显的引用还指向它。

常见误区

Git 会为每个版本完整复制整个项目吗

不应该这样理解。Git 底层是对象模型,加上后面的 packfiles 压缩和复用机制,并不是简单粗暴地重复存整份目录。

commit 只是写一段日志吗

不是。提交说明只是 commit 对象里的一部分元数据。真正关键的是它把 tree、父提交和作者信息固定了下来。

一个最实用的记忆方式

如果只记一件事,可以记住:

  1. blob 保存内容
  2. tree 保存结构
  3. commit 保存一次快照与父子关系
  4. branch 和 HEAD 只是指向这些对象的名字

后面理解 refs、历史图、rebase、reset 就会容易很多。