13 — 暂存区深入:你挑出来准备交的作业 写在前面这一章要解决什么如果你已经会了git addgit commit的两步走可能心里还有一个没消化的疙瘩为什么非要多一个「暂存区」我不能直接把书桌上的东西全交吗这一章就是专门回答这个问题的——而且会比你想象中挖得更深。学完后你应该能用自己的话说明暂存区不是「过渡文件夹」而是一份「快照提案」会用git ls-files --stage看 index 里面到底记了什么能解释git add和git commit各自从 index 读了什么、写了什么知道为什么「部分暂存」git add -p是暂存区存在的最强理由理解.git/index是二进制文件用文本编辑器打不开、也不该手动改读者设定大一同学已经跟着第 02 章走过三区模型的「书桌/作业篮/档案柜」比喻会基本的 add commit但不理解为什么要有暂存区。1. 定位为什么要讲暂存区深入1.1 一句话先记住暂存区不是「过渡用的临时文件夹」——它是你向 Git 提出的下一份快照长什么样的方案。白话翻译你先往篮子里挑东西Git 就按篮子里的样子拍一张快照。篮子里有什么下一笔提交就记什么篮子里没有的就算书桌上改翻天提交也不会带上。1.2 不理解暂存区会怎样痛点场景表你遇到的问题根因后果git commit后发现少了文件以为 commit 会自动带上书桌所有修改丢三落四提交不完整交了不该交的东西密码、调试代码习惯git add .全加敏感信息进历史很难彻底删一次提交里混了修 bug 和加新功能不知道可以只挑一部分暂存以后回退时拆不开git add之后又改了文件提交的不是最新版不理解 add 只记录那一瞬间以为 Git 坏了看到git diff输出为空就慌不理解「没进篮子」和「已经在篮子里」的差异浪费时间排查「假 bug」核心痛点总结不理解暂存区你就无法精确控制「这一笔提交里到底记了什么」。1.3 和你已经会的事对比你已经会的第 02 章本章要往深走暂存区 「待交作业篮」篮子里到底记了什么怎么偷看git add 往篮子里放add 的瞬间Git 在篮子里写了什么git commit 从篮子往档案柜记commit 是怎么读篮子的提交前先git status除了 status还有更硬核的「X 光」git add .全加部分暂存一个文件只挑几行加进去认知锚点第 02 章让你「知道有篮子」本章让你「打开篮子看看里面到底长什么样」。2. 本质暂存区到底是什么白话 比喻 图解2.1 先看总图图三区模型详图。暂存区index在工作区和仓库之间是「当前提议的快照」。2.2 比喻升级从「篮子」到「快照提案」旧比喻篮子 你把作业放进去等一会儿一块交。新比喻篮子 你在填一张「快照申请单」。单子上列着下次拍照时每个文件该拍哪个版本。Git 按这张单子拍出来的照片就是下一笔提交。为什么要升级因为「篮子」容易让人以为是「临时堆东西的地方」实际上暂存区更精确它不是「堆东西」——它记的是每个路径对应哪个文件内容blob 对象的哈希它不是「临时的」——提交完它不会自动清空而是变成和最新提交一致的状态它不是「只能全放或全不放」——你可以只放一个文件的某几行部分暂存2.3 用书桌比喻拆开暂存区的每一层你的动作篮子里发生了什么白话解释git add a.txt篮子里记录路径a.txt→ 当前内容算出的哈希篮子不是「存了一份副本」而是记了一条映射再git add b.txt篮子里多了路径b.txt→ 对应哈希每加一个文件篮子里的清单就多一行git commitGit 照着篮子里的清单生成 tree commit 对象提交就是「按清单拍照」git add a.txta.txt 又改了篮子里a.txt对应的哈希更新了篮子里的映射指向的是 add 那一刻的内容关键理解暂存区存的是「路径 → 内容哈希」的映射表不是文件本身的副本。真正的文件内容已经由 Git 算好哈希、存进对象库.git/objects/了暂存区只是记了一条「引用」。2.4 暂存区的正式名字index三个名字说的是同一个东西名字出现场景暂存区staging areagit status输出、入门教程indexGit 源码、底层文档、git ls-files的参数缓存cachegit diff --cached里的 cached底层文件叫.git/index所以「index」这个名字最「原汁原味」。2.5 为什么要有暂存区——一个场景说服你你正在写课程设计书桌上同时开了三个文件report.md写了一半新章节bugfix.py刚修好一个运行错误config.json临时加了调试用的密码不想交现在助教让你「先交修 bug 的部分报告晚点再交」。如果没有暂存区你要么全部一起提交要么手动把另外两个文件先复制出去、删掉、再提交——太痛苦了。有了暂存区你只需要gitaddbugfix.pygitcommit-mfix: 修复运行错误report.md和config.json都还在书桌上但没进篮子所以不会被这次提交带走。这就是暂存区存在的核心理由让你精确挑选「这一笔提交要记录什么」。3. 建议学习顺序先理解「暂存区 快照提案」 → 用 ls-files --stage 偷看 index → 动手add 前后对比 index 变化 → 学部分暂存add -p → 理解 commit 怎么读 index → 常见踩坑场景 → 完成章末小实验不要一上来就背ls-files的各种参数。先搞懂「index 里记了什么」再看命令只是换个方式查看而已。4. 动手准备建可丢弃目录请找一个可以随便删的练习目录。mkdirlab-index-deepcdlab-index-deepgitinit-bmaingitconfig user.nameAda Examplegitconfig user.emailadaexample.com说明git init -b main在本目录创建 Git 仓库初始分支名叫main名字和邮箱只是写在提交记录上的「作者信息」演示用虚构身份即可本系列命令样例在 Linux 上用Git 2.43.0验证过你电脑版本接近即可git--versiongit version 2.43.05. 跟着做打开暂存区看看里面到底有什么5.1 先做一次完整提交制造一个「干净」状态printfhello\na.txtprintfworld\nb.txtgitadda.txt b.txtgitcommit-mdocs: 添加两个示例文件输出类似[main (root-commit) a1b2c3d] docs: 添加两个示例文件 2 files changed, 2 insertions() create mode 100644 a.txt create mode 100644 b.txt白话翻译在分支main上创建了第一笔提交根提交两个文件各插入一行。100644是文件模式暂时不用深究。5.2 用git ls-files --stage偷看 indexgitls-files--stage100644 fbbee8a7... 0 a.txt 100644 af17f6c1... 0 b.txt白话翻译这是暂存区index的完整内容清单。每一行分四列列内容白话解释第 1 列100644文件模式普通文件、非可执行第 2 列fbbee8a7...这个文件内容的 blob 对象哈希前几位第 3 列0暂存阶段编号0 正常1/2/3 合并冲突时用第 4 列a.txt文件路径你现在可以把 index 理解成一张表--------------------------------------- | 模式 | blob 哈希 | 阶段 | 路径 | --------------------------------------- | 100644 | fbbee8a7... | 0 | a.txt | | 100644 | af17f6c1... | 0 | b.txt | ---------------------------------------这就是「快照提案」的完整面貌每条记录说的是「这个路径用这个版本的文件内容」。5.3 修改一个文件后index 和工作区分道扬镳printfhello v2\na.txtgitstatusOn branch main Changes not staged for commit: (use git add file... to update what will be committed) modified: a.txt再看 indexgitls-files--stage100644 fbbee8a7... 0 a.txt 100644 af17f6c1... 0 b.txt白话翻译工作区的a.txt已经变成hello v2但 index 里a.txt对应的哈希还是fbbee8a7...旧版。这说明index 没有跟着工作区自动变——它只在git add时才更新。5.4 用git add更新 indexgitadda.txtgitls-files--stage100644 e087c4a2... 0 a.txt 100644 af17f6c1... 0 b.txt白话翻译a.txt的哈希从fbbee8a7...变成了e087c4a2...。index 更新了现在指向a.txt的新版内容。这就是git add做的事更新 index 里对应路径的哈希指到当前工作区版本。5.5git commit是怎么读 index 的gitcommit-mdocs: 更新 a.txt 为 v2[main b2c3d4e] docs: 更新 a.txt 为 v2 1 file changed, 1 insertion(), 1 deletion(-)白话翻译提交发生的事Git 读取 index 的全部内容所有「路径→blob」映射根据这份映射生成一棵tree 对象目录结构快照再把 tree 对象包进commit 对象加上作者、时间、说明、父提交移动分支指针到新的 commitcommit 不看工作区只看 index。如果你忘了addindex 里还是旧哈希那提交的就是旧内容。5.6 再验证提交后 index 和最新 commit 保持一致提交完再查看git ls-files --stageindex 没变——它和刚才提交时一模一样。这就是「干净状态」index 指向的内容 最新 commit 里的内容。5.7 删掉文件index 也会更新gitrmb.txtgitls-files--stage100644 e087c4a2... 0 a.txtgit rm做了两件事① 从工作区删文件 ② 从 index 移除记录。注意如果你只在工作区删了文件没用git rmindex 里b.txt的记录还在——Git 会告诉你「工作区少了东西但 index 还记着它」。5.8 撤销暂存git restore --stagedprintftemp\nc.txtgitaddc.txtgitls-files--stage100644 e087c4a2... 0 a.txt 100644 5e1f3b7a... 0 c.txtc.txt是临时文件不想提交。从 index 移除gitrestore--stagedc.txtgitls-files--stage100644 e087c4a2... 0 a.txt白话翻译restore --staged把c.txt从 index 里移除了取消暂存但c.txt文件本身还在工作区——只是不再被「提议进下一次快照」了。这就是「从篮子里拿回来」不是「从书桌上删掉」。6. 命令分组按场景分组不按字母排序6.1 偷看 index只读放心用命令干什么白话git ls-files --stage列出 index 里所有条目的完整信息给篮子拍 X 光git ls-files只列文件名看篮子里有哪些文件git status用人话告诉你哪块脏了仪表盘git diff工作区 vs index 的差异书桌比篮子多了什么git diff --stagedindex vs 最新 commit 的差异篮子比档案柜多了什么6.2 往 index 里写改变篮子内容命令干什么白话git add 文件把文件当前内容记录进 index往篮子里放整份文件git add -p 文件交互式挑选文件的某些块放进 index往篮子里放只挑几行git add .当前目录下所有改动都加进 index全部扫进篮子慎用git rm 文件删文件 从 index 移除从书桌删 从篮子移除git mv 旧 新改名 更新 index书桌改名 篮子跟着改6.3 从 index 里撤取消暂存命令干什么白话git restore --staged 文件从 index 移除保留工作区文件从篮子里拿回来书桌上还在git rm --cached 文件从 index 移除保留工作区文件同上历史原因两种写法6.4 特殊场景命令干什么白话git commit -a -m 说明对已跟踪文件自动 add commit跳过手动 add新文件不会自动加git read-tree HEAD把 HEAD 对应的 tree 写进 index底层命令用档案柜里的快照重置篮子7. 对照表前后对比 / 选项对比7.1 index 在不同时刻的内容对照时刻index 里a.txt指向说明刚 commit 完和 HEAD 一致的哈希干净状态改了a.txt但没 add还是旧的哈希工作区已变index 没跟上git add a.txt之后新的哈希index 更新了git restore --staged a.txt回到和 HEAD 一致从篮子里撤回来了git rm a.txta.txt从 index 消失不再提议这个文件进下次快照7.2 index 的三种「对照差异」命令你想看什么命令比的是哪两棵「树」书桌比篮子多了什么改git diff工作区 vs index篮子比档案柜多了什么git diff --stagedindex vs HEAD书桌比档案柜多了什么git diff HEAD工作区 vs HEAD记忆口诀diff看书桌、--staged看篮子、HEAD看整体。7.3--cachedvs--staged对照--staged和--cached完全一样——--staged是 Git 后来加的更直观的名字。新手建议用--staged见名知义。7.4 部分暂存 vs 整文件暂存对照做法什么时候用命令整文件暂存改动只和一件事有关git add 文件部分暂存同一文件里混了多种改动git add -p 文件全部暂存想快速提交、确认没有多余文件git add .先 status 确认8. 安全习惯硬规矩 踩坑提醒8.1 硬规矩规矩原因提交前先git statusgit diff --staged确认篮子里是你想要的东西不要无脑git add .容易把临时文件、密码文件加进去理解add之后再改文件提交的不是最新版index 记的是 add 那一刻的哈希git restore --staged只撤销暂存不删文件从篮子里拿回来书桌上还在敏感文件永远不要 add 进 index进了历史极难彻底清除8.2 踩坑提醒坑现象怎么避以为 commit 会自动 add提交后发现少文件新文件必须手动 addadd 之后改文件不重新 add提交的是旧版commit 前再看一眼git diffgit add .加了不该加的临时文件进历史先git status确认再用.gitignore把git rm当成「只删 index」工作区文件也没了只删 index 用git rm --cachedgit add -p选错了块暂存了不该暂存的行可以git restore --staged撤销重来8.3 推荐的暂存前检查流程# 1. 看总览gitstatus# 2. 看书桌上的改动细节gitdiff# 3. 挑选要暂存的文件gitadd某些文件# 或 git add -p 做部分暂存# 4. 确认篮子内容gitdiff--staged# 5. 确认没多余的东西gitstatus# 6. 提交gitcommit-m说明9. 真实场景作业提交、换电脑、紧急修 bug 等9.1 课程作业只想交修 bug 的部分你同时改了三个文件但只有一个和 bug 修复相关gitaddbugfix.pygitdiff--stagedgitcommit-mfix: 修复计算溢出错误另外两个文件还在书桌上不受影响。这就是暂存区帮你「挑作业」的能力。9.2 同一文件里混了多种改动你在report.md里既修了错别字又写了半段新内容。现在只想先提交修错别字的gitadd-preport.mdGit 会逐块显示改动问你每块要不要加进 index。输入y暂存这一块输入n跳过。选完之后index 里只有你挑选的那些行。-p就是「一块一块看你说加才加」。9.3 紧急修 bug手上工作还没做完你正在写新功能写到一半助教让你先修个 bug先把当前改动用git stash暂存到一边修 buggit addgit commit再git stash pop把之前的改动恢复回来这里暂存区的角色没变——你始终是通过add挑选「这次提交要记什么」。stash只是帮你临时腾出手。9.4 多人协作前的准备和同学合作前每次提交前用git statusgit diff --staged确认 index 里没有杂七杂八的东西。提交前审查diff --staged是协作的基本礼貌。9.5 误加了敏感文件gitrestore--stagedconfig.jsongitstatus如果还没 commitrestore --staged就够了。如果已经 commit 了那就要用更重的方法后面章节讲。10. 进阶补充选读10.1.git/index是什么文件暂存区在磁盘上对应.git/index这是一个二进制文件。用文本编辑器打开会看到乱码。想查看就用git ls-files --stage。不要手动删或改.git/index——删了等于暂存区信息全丢。10.2 index 的内部结构了解即可index 文件大致包含文件头版本号、条目数、条目列表模式 哈希 阶段 路径、扩展区可选额外数据、校验和SHA-1。更详细的格式说明见 index-format 文档。10.3 index 和 tree 对象的关系index是「提案」还没变成正式快照tree 对象是「已拍好的快照」commit 时根据 index 生成提交时Git 把 index 里的所有条目组织成一棵 tree存进.git/objects/。之后 index 的内容和这棵 tree 一致——但 index 本身不是 tree 对象它是一个独立的数据结构。10.4 合并冲突时 index 的特殊用法正常情况下 index 里每个文件只有一个条目阶段 0。合并冲突时同一文件会出现多个条目阶段含义1共同祖先的版本2你这边ours的版本3对方theirs的版本gitls-files--stage可能看到100644 abc1234 1 conflict.txt 100644 def5678 2 conflict.txt 100644 ghi9012 3 conflict.txt解决冲突后index 会回到阶段 0 的正常状态。这个在合并章节会详细讲这里先有个印象就行。11. 小实验动手练习 通过标准实验甲用 X 光看 index新建仓库创建x.txt和y.txtadd 并提交。用git ls-files --stage查看 index记下两个哈希。修改x.txt再次git add x.txt。再用git ls-files --stage查看 index确认只有x.txt的哈希变了。通过标准你能指出哪一行变了并用白话解释为什么会变。实验乙index 不自动跟踪工作区在实验甲的基础上修改x.txt但不 add。运行git ls-files --stage看x.txt对应的哈希有没有变。运行git status读懂输出。运行git diff确认「书桌比篮子多了什么」。运行git diff --staged确认「篮子比档案柜没多什么」。通过标准你能用「add 只记录那一刻index 不会自动跟着工作区变」解释结果。实验丙部分暂存创建mixed.txt内容为三行「原始内容」add 并提交。修改文件为第一行修了错别字、第二行不变、第三行新增功能代码。用git add -p mixed.txt只挑选第一行的修改进 index第三行不选。用git diff --staged确认 index 里只有第一行的改动。用git diff确认工作区还有第三行的改动。通过标准你能说清「同一个文件可以分两批提交」。实验丁从 index 撤销暂存新建temp.txtgit add temp.txt。用git ls-files --stage确认 index 里有它。git restore --staged temp.txt。用git ls-files --stage确认 index 里没它了。用ls确认文件本身还在工作区。通过标准你能区分「从 index 移除」和「从工作区删除」。12. 常见问题 FAQ问 1暂存区到底有什么用我不能直接 commit 所有改动吗可以但你会失去精确控制力。暂存区的核心价值是让你挑选下一次提交要包含什么。当你一次改了多个文件、或者同一文件里混了不同类型的改动时暂存区让你拆开提交保持历史清晰。问 2git commit -a是不是绕过了暂存区不算绕过。-a只是对已跟踪文件自动执行了一步add新文件仍然不会自动进提交。新手不推荐当默认用法。问 3index 里的哈希和工作区文件内容有什么关系Git 通过 SHA-1 算法把文件内容算出一个哈希值然后用这个哈希当「钥匙」把内容存进.git/objects/叫 blob 对象。index 里记的正是这个哈希——通过它找到对应的文件内容。问 4为什么git diff有时输出是空的因为你可能已经把改动 add 进 index 了。git diff无参数比的是「工作区 vs index」如果你刚 add 完、工作区没再改那两者一样diff 自然为空。想看「篮子比档案柜多了什么」用git diff --staged。问 5git add -p的选项都是什么意思常用选项y暂存这一块、n跳过、s拆成更小的块、q退出不再选。新手先用y和n就够了。问 6暂存区会不会占很多磁盘空间不会。index 文件通常很小只是映射表。真正的文件内容在.git/objects/里而且 Git 会压缩和复用——相同内容只存一份。问 7git add之后又改文件之前 add 的内容去哪了还在.git/objects/里作为 blob 对象只是 index 不再指向它了。那个旧的 blob 以后会被 Git 的垃圾回收机制清理。问 8.git/index文件能手动编辑吗不能。它是二进制格式手动改会损坏。想修改 index 的内容通过 Git 命令add、rm、restore --staged等来操作。13. 总结13.1 一页速记点记住什么暂存区是什么一份「快照提案」路径 → 内容哈希的映射表正式名字index.git/index、也叫 staging area、cachegit add做了什么把文件当前内容的哈希写进 indexgit commit做了什么读取 index 全部内容生成 tree commit 对象怎么偷看 indexgit ls-files --stage怎么只加部分改动git add -p 文件怎么从 index 撤销git restore --staged 文件为什么要有暂存区让你精确挑选「这次提交记什么」.git/index是二进制别手动改用 Git 命令操作13.2 本系列中的位置01 认识 Git、装好工具 02 三区模型书桌/篮子/档案柜 03 日常提交与查看历史 04 安全地撤销 05 分支与合并 06 远程协作 …… 12 提交历史深入 13 暂存区深入 ← 当前打开篮子看内部 14 分支深入 ……本章建在第 02 章的「三区模型」之上但更关注 index 的内部机制而不是停留在「篮子」的比喻层面。13.3 思维升华暂存区的本质不是「临时存放」而是「精确挑选」。没有 index你只能在「全交」和「不交」之间二选一有了 index你可以从一堆改动里挑出任意子集组合成一笔逻辑清晰的提交。学会部分暂存你就从「Git 用户」迈向了「Git 掌控者」。13.4 延伸阅读Pro Git 中文版 – 记录每次更新到仓库git ls-files 文档git add 文档index-format 文档gitglossary命令输出样例验证环境Git 2.43.0演示作者信息为虚构Ada Example adaexample.com。13.5 检查清单能用自己的话解释「暂存区 快照提案」而不只是「临时中转站」会用git ls-files --stage查看 index 内容读懂四列含义能解释git add更新了 index 的什么、git commit读了 index 的什么知道add之后改文件要重新add能解释为什么会用git add -p进行部分暂存知道git diff和git diff --staged分别比的是哪两棵树会用git restore --staged从 index 撤销暂存知道.git/index是二进制文件不能手动编辑提交前会看git diff --staged而不是闭眼提交