Git 差异审查 从工作区到暂存区与提交
你先暂存了一次改动,又继续编辑同一个文件。此时提交会带走哪一版?为什么 git diff 只显示后半段,而 git diff --cached 显示另一段?
答案取决于三个不同位置:工作区是正在编辑的文件,暂存区是下一次提交准备采用的快照,HEAD 在本文的普通分支场景中指向当前提交。本文用一个文件把它们分开,练习只读检查、按明确路径暂存和提交前审查。不涉及删除、清理、强推或历史重写。
三个位置不是三份备份
编辑不会自动更新暂存区;暂存也没有产生历史提交。
每条 diff 有两个端点
先说清比较什么,再解释空输出代表什么。
审查下一次快照
提交前检查暂存区内容、相关测试与敏感信息,不只看编辑器。
三个位置怎样改变
Pro Git 的基础章节说明,修改后的文件可以被选择性暂存,然后写入提交。git add 保存的是执行那一刻的内容;之后再编辑,暂存区不会跟着变化。
以下假设仓库已经有一次提交,文件 note.txt 已被跟踪,使用普通分支,没有合并冲突或子模块。HEAD 是当前提交的引用,不是一个普通文件目录;暂存区是 Git 的索引,不应手动编辑其内部文件。
flowchart LR W[工作区 当前文件] -->|git add 明确路径| I[暂存区 下一次提交快照] I -->|git commit| C[新提交 并移动当前分支] C --> H[HEAD 指向当前提交] W -->|后续编辑不会自动同步| W2[工作区继续变化]
一个文件的三版内容
通过编辑器创建和修改练习文件,不在命令中覆盖已有项目文件。初始提交的两行是 title=demo 和 mode=basic。
| 步骤 | HEAD 中的 mode | 暂存区中的 mode | 工作区中的 mode | 短状态 |
|---|---|---|---|---|
| 初始提交后 | basic | basic | basic | 无输出 |
| 编辑为 staged | basic | basic | staged | 空格 M |
| 对 note.txt 执行 add | basic | staged | staged | M 空格 |
| 再编辑为 working | basic | staged | working | MM |
| 普通 commit 后 | staged | staged | working | 空格 M |
最后一步的提交内容是 mode=staged,不是编辑器里当前的 mode=working。普通 git commit -m ... 使用暂存区;本文不使用会改变暂存行为的 -a 或路径限定提交形式。

三种 diff 的比较端点
在表中 MM 的那一刻运行下面三条命令。-- 分隔选项与路径;练习只涉及这个明确的文件名。
1 | git diff -- note.txt |
| 命令 | 左侧旧内容 | 右侧新内容 | 本例中的变化 |
|---|---|---|---|
| git diff | 暂存区 | 工作区 | staged → working |
| git diff –cached | HEAD | 暂存区 | basic → staged |
| git diff HEAD | HEAD | 工作区 | basic → working |
这些语义见Git diff 官方文档。--staged 是 --cached 的同义选项。普通 git diff 为空,只说明工作区与索引的这一类差异为空,并不表示暂存区没有改动;尚未跟踪的新文件通常也不会出现在普通 diff 中。

差异中的减号和加号表示旧行与新行,不表示程序运行时会“先删文件再建文件”。但删除路径确实可能被暂存,所以在真实项目中仍要检查状态和变更类型。
短状态的两列不要交换
1 | git status --short |
在本文无冲突的跟踪文件场景,第一列表示索引相对 HEAD 的变化,第二列表示工作区相对索引的变化。因此 MM note.txt 的意思是已经暂存了一版修改,工作区又有尚未暂存的修改。
| 状态记号 | 本文场景下的意思 | 接下来先检查 |
|---|---|---|
| 空格 M | 工作区修改,索引未修改 | git diff |
| M 空格 | 已暂存修改,工作区与索引一致 | git diff –cached |
| MM | 两个位置都有修改 | 分别看两份 diff |
| ?? | 未跟踪文件 | 文件正文及是否应纳入版本控制 |
Git status 文档还定义了未合并、重命名、子模块和忽略文件等状态,不能把这张简表扩展成所有场景的解释。--porcelain=v1 适合稳定的机器处理;若程序要解析任意文件名,应进一步使用 -z 的 NUL 分隔输出,而不是按空格切割。
提交前的安全检查顺序
先使用只读命令确认当前仓库、分支和差异。将 note.txt 换成经过确认的目标路径;这组命令不是让你在未知仓库直接照抄提交。
1 | git rev-parse --show-toplevel |
git diff --check 可以检查某些空白错误和冲突标记,但不是语法检查、测试或敏感信息扫描。还要执行项目约定的测试,核查所有已暂存路径的正文;不要因为本次只 add 一个文件,就忽略原先已经暂存的其他文件。
通过检查后才执行普通提交,并核对历史快照与剩余工作区改动:
1 | git commit -m "Explain staging snapshot" |
如果刚才是在 MM 状态提交,最后仍会看到 note.txt 的未暂存改动。这不是提交失败,而是提交保存了你审查过的暂存快照。
flowchart TD A[核对仓库与分支] --> B[检查状态和工作区差异] B --> C[按明确路径暂存] C --> D[审查所有已暂存改动] D --> E[相关测试与敏感信息检查] E --> F[提交后核对历史和剩余状态]
测试通过也要看测试的是哪一版
大多数本地测试读取工作区,不直接读取索引。若文件处于 MM,测试可能验证的是 working 版,提交却包含 staged 版,不能直接说“提交内容已测试通过”。
一个易执行的习惯是完成目标修改后重新暂存明确路径,再检查相关路径没有未暂存差异,运行相关测试并复核暂存 diff。它不是让你把其他人的改动一并暂存。部分暂存、多文件依赖或测试会修改文件时,应采用团队认可的独立验证环境,验证实际提交快照。
本地提交不等于远端发布
本文的实验不访问远端,不推送,也不修改凭据。git commit 只产生本地提交,git log 看到的本地记录不能证明远端已更新。真实发布应遵循项目规定的构建、扫描、远端分歧检查、推送和线上验证顺序。
不要为了让状态“变干净”而清理目录、批量删除文件或强制改写历史。遇到不属于本次任务的改动,先保留并确认归属。状态是需要解释的信息,不是必须消灭的提示。
复盘练习 预测提交会保存哪一版
在独立练习仓库中走完三版内容的步骤。提交前用三种 diff 预测结果,提交后用 git show HEAD:note.txt 检查。再解释:为什么普通 diff 为空和工作区完全干净不是同一句话?为什么 MM 时运行工作区测试不能证明暂存快照通过?
继续阅读
配合Visual Studio 编辑与调试快捷键,把编辑、验证与审查连成习惯;若在维护评测题集,接着阅读RAG 回归评测。更多入口见开发工具分类和资源阅读路径。