Git 对象
Git 的核心本质是一个 键值对文件系统 (Content-addressable filesystem)。当你向 Git 仓库中插入任何类型的内容时,Git 会返回一个唯一的键(Key),你可以通过这个键在任意时刻取回该内容(Value)。
这套机制由四种基本的对象类型支撑:
- Blob (二进制大对象)
- Tree (树对象)
- Commit (提交对象)
- Tag (标签对象)
让我们逐一拆解。
1. Blob 对象 (文件内容)
Section titled “1. Blob 对象 (文件内容)”Blob (Binary Large Object) 仅存储文件的数据内容,不包含文件名、权限或时间戳等元数据。
- 如果你有两个文件
README.md和intro.txt,内容都是Hello,Git 只会存储一个 Blob 对象。 - 这也是 Git 高效的原因之一:重复的内容在数据库中只占一份空间。
我们可以通过 git cat-file 验证这一点:
# 创建一个包含 "version 1" 的文件echo "version 1" > test.txtgit hash-object -w test.txt83baae61804e65cc73a7201a7252750c76066a302. Tree 对象 (目录结构)
Section titled “2. Tree 对象 (目录结构)”既然 Blob 只存内容,那文件名存哪儿了?答案是 Tree 对象。
Tree 对象解决了文件名和目录层级的问题。一个 Tree 对象代表了一个目录及其包含的信息。它包含了一系列的记录,每一条记录指向一个 Blob(代表文件)或另一个 Tree(代表子目录)。
一个 Tree 对象的内容看起来像这样:
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt040000 tree 0ab42... lib你可以把它想象成操作系统的文件夹,它将文件名映射到对应的 Blob 哈希值。
3. Commit 对象 (提交快照)
Section titled “3. Commit 对象 (提交快照)”Tree 对象虽然保存了目录结构,但它不知道是谁保存的、什么时候保存的,以及为什么要保存。Commit 对象 解决了这个问题。
Commit 对象存储了:
- 顶级 Tree 对象的指针(代表项目在该时刻的完整快照)。
- 父提交 (Parent) 的指针(如果有的话,指向上一次提交)。
- 作者 (Author) 和 提交者 (Committer) 的信息(姓名、邮箱、时间戳)。
- 提交信息 (Message)。
提交通过 parent 指针形成一个有向无环图 (DAG):初始提交没有父提交,普通提交有一个父提交,而合并提交可以有多个父提交,因此 Git 的历史并非简单的单链结构。
4. Tag 对象 (标签)
Section titled “4. Tag 对象 (标签)”Tag 对象 非常像 Commit 对象,它常用于标记某个特定的 Commit(如 v1.0),但语义上可以指向任意类型的 Git 对象。Tag 对象包含标签的创建者信息、日期、标签信息,以及可选的 GPG 签名。
一般建议标签保持不可变,但技术上它们是可以被改写或删除的(例如通过 git tag -f 强制覆盖)。
对象关系图解
Section titled “对象关系图解”让我们通过一张图来理清它们的关系:
graph TD subgraph Git Database
C1["Commit Object<br/>(Initial Commit)"] --> T1["Tree Object<br/>(Root Directory)"] C2["Commit Object<br/>(Second Commit)"] --> T2["Tree Object<br/>(Root Directory)"] C2 --> C1
T1 --> B1["Blob Object<br/>(file1.txt)"] T1 --> B2["Blob Object<br/>(file2.txt)"]
T2 --> B1 T2 --> B3["Blob Object<br/>(file2.txt - modified)"]
Tag["Tag Object<br/>(v1.0)"] -.-> C2
end
classDef blob fill:#38a169,stroke:#276749,color:#fff classDef tree fill:#3182ce,stroke:#2c5282,color:#fff classDef commit fill:#e53e3e,stroke:#c53030,color:#fff classDef tag fill:#805ad5,stroke:#6b46c1,color:#fff
class C1,C2 commit class T1,T2 tree class B1,B2,B3 blob class Tag tag- Commit 指向一个 Tree。
- Tree 指向具体的 Blob 或子 Tree。
- 如果文件 (
file1.txt) 在两次提交间没有变化,新的 Tree 会直接指向旧的 Blob(复用)。 - 如果文件 (
file2.txt) 发生了变化,Git 会创建一个新的 Blob (B3),新的 Tree 指向它。
你可以在任何 Git 仓库中通过 git log 找到一个 Commit 哈希,然后开始顺藤摸瓜:
# 1. 查看最新的 Commitgit cat-file -p HEADtree 5d6e...parent ...author ...committer ...message ...# 2. 拿着上面的 tree 哈希值,查看 Tree 对象git cat-file -p 5d6e...100644 blob 83ba... README.md040000 tree 0ab4... src# 3. 拿着 blob 哈希值,查看文件内容git cat-file -p 83ba...这就是 Git 的全部奥秘:简单的对象通过哈希指针链接在一起,构成了一个强大、去中心化的版本控制系统。