← 返回 Git内部原理图解 目录

🔄 Git工作流与暂存区图解

三区域流转 + git add/commit/reset内部流程 + checkout vs restore · SVG可视化

① 三个区域

Git有三个"房间",文件可以在它们之间流转:
工作区(Working Directory) = 你的工作台——你正在编辑的文件
暂存区(Staging Area / Index) = 试衣间——准备好了要拍照的东西
版本库(Repository) = 档案柜——已经拍好照存档的底片
你每次编辑→暂存→提交,就是把东西从工作台→放到试衣间→拍成底片存进档案柜。
Git三区域流转图 🔧 工作区 Working Directory README.md (已修改) pom.xml (未修改) App.java (新文件) 你实际看到的文件 = 磁盘上的文件 🧥 暂存区 Staging Area / Index README.md (v2) App.java (新) .git/index 文件 = 下次要拍的照片清单 ⚠️ pom.xml没add, 所以暂存区没有它! 🗄️ 版本库 Repository (.git/) commit abc123 → tree → blobs 所有commit历史 = 已存档的底片 README.md (v1) pom.xml (v1) git add git commit git checkout git reset 🔑 文件流转:工作区 →(add)→ 暂存区 →(commit)→ 版本库 | ←(reset)← | ←(checkout)←
三区域流转:add把修改放入暂存区,commit把暂存区拍成快照存入版本库

git status 读懂三区域

$ git status On branch main Changes not staged for commit: # ← 工作区有改,暂存区没有 modified: README.md # 红色 = 在工作区改了但没add Changes to be committed: # ← 暂存区有,版本库没有 new file: App.java # 绿色 = 已add,等commit Untracked files: # ← 工作区有,Git不管 config.properties # 红色 = Git还没追踪过

② git add内部流程

git add就像把东西放进试衣间——你不是在拍照,只是在准备"哪些东西要入镜"。具体来说,Git做了3件事:给文件内容拍底片(blob)、更新试衣间清单(index)、把底片存入档案室(objects/)。
git add README.md 内部流程 ① 创建blob对象 读取工作区README.md → 计算 SHA-1 → 存入 objects/xx/yyyy ② 更新index文件 .git/index 中添加条目: 文件名 → blob SHA 模式(100644) → 时间戳 ③ 暂存区已更新 index现在包含: README.md → 新blob pom.xml → 旧blob(不变) .git/index 文件内容(简化): 100644 e74d2c1f... 0 README.md 100644 9c2f4a6b... 0 pom.xml 100644 f3a1b2c3... 0 App.java ↑ 权限 ↑ blob SHA ↑ 暂存号 ↑ 文件名
git add = 创建blob对象 + 更新index文件(暂存区就是一个index文件)
terminal — git add的底层命令
📁 .git/
📁 objects/
e7/4d2c1f...
index
# git add README.md 等价于以下两步底层操作: # 步骤1:创建blob对象并返回SHA-1 $ git hash-object -w README.md e74d2c1f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d # 步骤2:把blob SHA-1登记到暂存区 $ git update-index --add --cacheinfo 100644,e74d2c1f,README.md # 查看暂存区内容 $ git ls-files --stage 100644 e74d2c1f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d 0 README.md 100644 9c2f4a6b8c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f 0 pom.xml 100644 f3a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9 0 App.java
暂存区的真相:暂存区就是 .git/index 这一个文件!它是一个二进制文件,记录了"下次commit要包含哪些文件的哪个版本"。所以暂存区不是一个目录,而是一个清单文件。

③ git commit内部流程

git commit就是按下快门拍照——把试衣间里准备好的所有东西,拍成一张完整的底片存进档案柜。具体来说:先把index清单转成tree对象,再创建commit对象指向这个tree,最后移动分支指针。
git commit 内部流程(4步) ① index → tree 把暂存区清单 转成tree对象 write-tree ② 创建commit commit对象引用 tree + parent commit-tree ③ 更新分支指针 refs/heads/main → 新commit SHA update-ref ④ HEAD跟随 HEAD → main → 新commit (自动完成) commit对象包含什么? tree 5a1b3c README.md → e74d App.java → f3a1 pom.xml → 9c2f commit 3f8e9a tree → 5a1b3c (上面的tree) parent → abcd12 (上一个commit) author/committer/date/message refs/heads/main 3f8e9a2b... 分支指针更新
commit = index→tree + 创建commit对象 + 更新分支指针。一次commit只生成1个tree+1个commit
terminal — git commit底层操作
📁 .git/
📁 objects/
5a/1b3c4d...
3f/8e9a2b...
📁 refs/heads/
main
# git commit -m "feat: 添加App.java" 等价于: # 步骤1:把暂存区(index)写入tree对象 $ git write-tree 5a1b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b # 步骤2:创建commit对象 $ echo "feat: 添加App.java" | git commit-tree 5a1b3c4d \ -p abcd1234 3f8e9a2b1c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f # 步骤3:更新分支指针 $ git update-ref refs/heads/main 3f8e9a2b # 验证 $ cat .git/refs/heads/main 3f8e9a2b1c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f

④ reset三种模式

git reset就是"撤销操作",但有3种力度:
--soft = 只撤销"按快门"(commit没了,但试衣间里的东西还在)
--mixed(默认) = 撤销"按快门"+"把东西从试衣间拿回工作台"
--hard = 撤销"按快门"+"清空试衣间"+"把工作台也还原"——彻底回退,数据可能丢失!
git reset 三种模式对比 --soft 最温柔:只回退commit ✅ 版本库:回退到C2 ✅ 暂存区:C3的修改还在 ✅ 工作区:文件没动 用途 合并多个commit 修改commit消息 "我commit早了" 安全!不会丢数据 --mixed (默认) 中等:回退commit+暂存区 ✅ 版本库:回退到C2 ❌ 暂存区:C3的修改退出 ✅ 工作区:文件没动 用途 重新选择要add的文件 拆分一个commit "我add多了" 安全!不会丢文件内容 --hard 最狠:三个区域全回退 ✅ 版本库:回退到C2 ❌ 暂存区:清空C3修改 ❌ 工作区:文件回退到C2 用途 彻底丢弃修改 回到某个确定状态 "我全搞砸了重来" ⚠️ 危险!可能丢数据
--soft只回退commit指针,--mixed额外清空暂存区,--hard连工作区都还原
模式版本库(HEAD)暂存区(Index)工作区(WD)数据安全
--soft回退 ✅不动 ✅不动 ✅🟢 安全
--mixed回退 ✅回退 ✅不动 ✅🟡 中等
--hard回退 ✅回退 ✅回退 ✅🔴 危险

⑤ checkout vs restore

Git 2.23之后把老checkout拆成了两个命令,就像把万能遥控器拆成了两个专用遥控器:
git restore = 专门恢复文件(从试衣间或档案柜拿东西回工作台)
git switch = 专门切换分支(换一根红绳指向不同的照片)

checkout / restore / switch 对照表

你想做什么旧命令 (checkout)新命令 (restore/switch)
丢弃工作区修改git checkout -- filegit restore file
从版本库恢复到暂存区git restore --staged file
从指定commit恢复文件git checkout HEAD~1 -- filegit restore --source=HEAD~1 file
切换分支git checkout branchgit switch branch
创建并切换分支git checkout -b branchgit switch -c branch
git restore 默认恢复工作区文件(从暂存区拿),git restore --staged 恢复暂存区文件(从版本库拿)。两个操作都不改版本库!安全但容易搞混方向。

⑥ 面试速答

Git暂存区(staging area)的本质是什么?

暂存区就是 .git/index 这个二进制文件。它记录了"下次commit要包含哪些文件、每个文件的blob SHA-1、文件模式"。git add 就是往这个文件里添加/更新条目,git commit 就是把这个文件转成tree对象存入版本库。

git add做了什么?

3件事:
1. 读取工作区文件内容
2. 创建blob对象(计算SHA-1,zlib压缩,存入objects/)
3. 更新index文件(在暂存区清单中添加/更新该文件的条目)
注意:如果文件内容没变,add不会创建新blob(因为SHA-1相同)。

git commit做了什么?生成了几个对象?

commit做3件事:
1. 根据暂存区(index)创建tree对象(遍历index,为每个目录层级创建tree)
2. 创建commit对象(包含tree指针、parent指针、作者、时间、消息)
3. 更新分支指针指向新commit
最少生成2个对象:1个tree + 1个commit。如果有子目录,会生成更多tree。

git reset --soft / --mixed / --hard 的区别?

核心区别在于移动HEAD指针后,是否修改暂存区和工作区:
--soft:只移动HEAD/分支指针,暂存区和工作区不变。适合"改commit消息"或"合并多个commit"。
--mixed(默认):移动指针 + 重置暂存区(=取消add),工作区不变。适合"重新选择要提交的文件"。
--hard:移动指针 + 重置暂存区 + 重置工作区。三个区域全部回退,未提交的修改会丢失!

git reset和git revert的区别?

git reset:回退HEAD指针,"删除"历史。已push的commit不能用reset(会导致远程和本地不一致)。
git revert:创建一个新commit来"反转"指定commit的修改,历史是保留的。适合已push的代码。
简单记:没push的用reset,已push的用revert。

git restore和git checkout恢复文件有什么区别?

Git 2.23把checkout拆成了两个命令:
git restore:专门恢复文件(从暂存区/版本库 → 工作区/暂存区)
git switch:专门切换分支
checkout一个文件 = restore该文件。新命令更清晰,不会搞混"切换分支"和"恢复文件"两个完全不同的操作。

git diff的几种用法?

git diff — 工作区 vs 暂存区(你改了什么还没add)
git diff --staged — 暂存区 vs 版本库(你add了什么还没commit)
git diff HEAD — 工作区 vs 版本库(你改了什么还没commit)
git diff branch1 branch2 — 两个分支之间的差异
git diff commit1 commit2 — 两个commit之间的差异

为什么Git要有暂存区?不能直接commit工作区吗?

暂存区的好处:
1. 精确控制提交内容:你可以只add部分修改,而不是把工作区的所有改动都commit
2. 分批提交:修了5个bug,可以add+commit 3个,再add+commit 2个,保持commit干净
3. 审查机制:git diff --staged让你在commit前检查将要提交的内容
SVN没有暂存区,是直接commit工作区的所有改动——这在实际开发中很不灵活。

← 上一篇:Git对象与存储 下一篇:Git分支与合并 →
核心三原则速记

Git三原则:① 提交信息写清楚为什么改而不是改了什么 → ② 永远不在共享分支force push → ③ 合并前先rebase保持线性历史