Git 核心原理:三种状态与对象模型
说实话,我刚接触 Git 的时候,也觉得它就是个「高级版 Ctrl+Z」。但用着用着就发现,不理解底层原理,迟早会踩坑。有一次我手滑执行了 git reset --hard,差点把同事三天的代码弄丢……嗯,从那以后,我老老实实把 Git 的对象模型啃了一遍。
这一章,我们就来聊聊 Git 最核心的三个概念:三种状态、对象模型,以及它们怎么串成一条工作流。
Git 的三种状态
你想想看,一个文件在 Git 仓库里,其实可以处于三种状态:
- 已修改(Modified)——文件改了,但还没告诉 Git 我要记下来。
- 已暂存(Staged)——我告诉 Git:「这个版本我准备保存了」,但还没真正存进仓库。
- 已提交(Committed)——文件已经安全地存到了 Git 的本地数据库里。
这三种状态对应着 Git 的三个工作区域:工作目录、暂存区、Git 仓库。我个人习惯把暂存区想象成一个「购物车」——你先挑要买的商品(暂存),最后统一结账(提交)。
核心要点:只有已提交的数据才是真正安全的。已修改和已暂存的数据,一旦丢失就很难恢复。我见过太多人以为 git add 之后就万事大吉了……其实不是。
Git 对象模型:Blob、Tree、Commit
Git 本质上是一个内容寻址文件系统。说白了,它用哈希值(SHA-1)来定位每一个对象。Git 内部有三种核心对象:
| 对象类型 | 存储内容 | 类比 |
|---|---|---|
| Blob | 文件内容(二进制数据) | 文件本身,不包含文件名 |
| Tree | 目录结构(文件名 + 权限 + 指向 Blob 或子 Tree 的指针) | 文件夹清单 |
| Commit | 快照信息(指向 Tree + 父 Commit + 作者 + 时间 + 提交信息) | 一次「拍照」记录 |
我举个例子你就明白了。假设你有一个项目,结构是这样的:
project/
├── README.md
└── src/
└── main.c
当你执行 git add 和 git commit 后,Git 内部会生成:
- 两个 Blob:分别存储 README.md 和 main.c 的内容。
- 一个 Tree 对应
src/目录,里面记录 main.c 的文件名和它对应的 Blob 哈希。 - 一个 Tree 对应根目录,里面记录 README.md 和
src/子目录。 - 一个 Commit 指向根目录的 Tree,并记录提交信息、作者和时间。
小技巧:你可以用 git cat-file -p <哈希值> 查看任意 Git 对象的内容。我曾经用这个命令帮同事找回了一个被误删的分支——只要提交还在,数据就丢不了。
Git 的工作流程
理解了三种状态和对象模型,工作流程就顺理成章了。日常开发中,你基本就在这三个区域之间来回切换:
- 在工作目录中修改文件——状态变为「已修改」。
- 将修改添加到暂存区(
git add)——状态变为「已暂存」。 - 将暂存区的内容提交到仓库(
git commit)——状态变为「已提交」。
这里有个容易混淆的点:git commit 只提交暂存区的内容,不会包含工作目录中未暂存的修改。我刚开始用 Git 时,经常以为改了文件直接提交就行,结果发现提交的版本还是旧的……
避坑指南:我曾经在项目上线前,直接 git commit -a 提交了所有修改,结果把调试用的日志代码也提交上去了。建议养成习惯:先 git status 看看哪些文件变了,再用 git diff 确认改动内容,最后才 git add 和 git commit。
核心逻辑:一张图看懂
下面这张 SVG 图,把 Git 的三种状态、三个区域和对象模型串在了一起。你可以把它当作 Git 的「心法口诀」:
这张图里,上半部分是三个区域和状态转换,下半部分是 Git 内部的对象模型。你每次 git commit,Git 都会生成一个 Commit 对象,它指向一个 Tree,Tree 又指向若干 Blob 和子 Tree。这就是 Git 为什么能瞬间切换分支——它只是换了一个 Commit 指针而已。
总结一下:Git 的三种状态对应三个工作区域,对象模型是 Git 存储数据的底层机制。理解这两点,你就掌握了 Git 的「内功心法」。后面讲分支、合并、变基,都是在这个基础上展开的。