← 返回 Git内部原理图解 目录
🔄 Git工作流与暂存区图解
三区域流转 + git add/commit/reset内部流程 + checkout vs restore · SVG可视化
① 三个区域
Git有三个"房间",文件可以在它们之间流转:
• 工作区(Working Directory) = 你的工作台——你正在编辑的文件
• 暂存区(Staging Area / Index) = 试衣间——准备好了要拍照的东西
• 版本库(Repository) = 档案柜——已经拍好照存档的底片
你每次编辑→暂存→提交,就是把东西从工作台→放到试衣间→拍成底片存进档案柜。
三区域流转: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 = 创建blob对象 + 更新index文件(暂存区就是一个index文件)
terminal — git add的底层命令
# 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,最后移动分支指针。
commit = index→tree + 创建commit对象 + 更新分支指针。一次commit只生成1个tree+1个commit
terminal — git commit底层操作
# 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 = 撤销"按快门"+"清空试衣间"+"把工作台也还原"——彻底回退,数据可能丢失!
--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 -- file | git restore file |
| 从版本库恢复到暂存区 | — | git restore --staged file |
| 从指定commit恢复文件 | git checkout HEAD~1 -- file | git restore --source=HEAD~1 file |
| 切换分支 | git checkout branch | git switch branch |
| 创建并切换分支 | git checkout -b branch | git 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三原则:① 提交信息写清楚为什么改而不是改了什么 → ② 永远不在共享分支force push → ③ 合并前先rebase保持线性历史