第 6 章 · 03 提交与快照: 的真相 本节摘要:上一节讲了 add 把文件登记进索引(草稿),这一节讲 commit——把草稿定稿。 干三件事:把当前索引建成一棵 tree 对象(项目根的完整快照)、创建一个 commit 对象(指向这棵 tree + 父 commit + 信息)、移动当前分支指针到新 commit。理解这三步,你就理解「commit 是一次项目状态的不可变快照」,以及为什么 Git 历史是一条「快照链」。 内容来源:基于 Git 内部模型整理的导读。 学习目标 阅读完本节,你应当能够: 说清 的三步:建 tree、建 commit、移分支指针。 理解 tree 对象如何从索引构造(项目根的完整快照)。 解释 commit 的「父指针」如何把历史串成一条链。
git commit 的真相本节摘要:上一节讲了 add 把文件登记进索引(草稿),这一节讲 commit——把草稿定稿。
git commit干三件事:把当前索引建成一棵 tree 对象(项目根的完整快照)、创建一个 commit 对象(指向这棵 tree + 父 commit + 信息)、移动当前分支指针到新 commit。理解这三步,你就理解「commit 是一次项目状态的不可变快照」,以及为什么 Git 历史是一条「快照链」。
内容来源:基于 Git 内部模型整理的导读。
阅读完本节,你应当能够:
git commit 的三步:建 tree、建 commit、移分支指针。commit 是 Git 历史的基本单元——每次 commit 是「项目在某一刻的完整状态 + 指向上一状态的指针」。理解 commit 的结构,你就理解:
git log 能回溯历史(沿父指针)。索引是扁平的「文件名→blob 哈希」列表。commit 时,要把它组织成「层次化的 tree 结构」(目录树):
索引(扁平): { "README.md": blob1, "src/main.py": blob2, "src/util.py": blob3 } 构建 tree(层次): 根 tree: "README.md" → blob1 "src" → 子 tree: "main.py" → blob2 "util.py" → blob3
把扁平路径按 / 切分,递归构建嵌套的 tree 对象。每个 tree 对象算哈希、存对象库。
commit: tree: <根tree哈希> # 这次提交的项目状态 parent: <上次commit哈希> # 父(可能多个, 合并时) author/committer: 名字 <邮箱> 时间 message: "提交信息"
commit 对象算哈希、存对象库。它的核心是「一个 tree(状态)+ 一个或多个 parent(历史链)」。
Git 的「当前分支」是个指针(存在 .git/HEAD 指向分支、分支文件指向 commit)。commit 后,把当前分支指针移到新 commit:
commit 前: main → commit2 commit 后: main → commit3(新), commit3.parent → commit2
这就是为什么「分支只是一个指针」——它指向某个 commit,commit 后指针前移。
commit3 (tree: 现在状态) → parent → commit2 (tree: 上次状态) → parent → commit1 (tree: 初始状态)
每个 commit 持有完整状态(tree),父指针把它们串成链。git log 就是沿父指针从当前 commit 一直回溯。历史不可篡改——改任何 commit 都会产生新哈希(新对象),原链路不变。
commit:从索引建 tree(递归按目录组织)→ 建 commit 对象(指 tree + parent 当前分支指向的 commit)→ 移动分支指针。log:从当前 commit 出发,沿 parent 不断回溯,打印每个 commit 信息。下一节讲分支与 HEAD——本质都是指针。