| English | 交互演示 | 可运行实验 |
当 AI Agent 回复“已经改完”,你还需要确认一件事:这些改动位于 Working Tree、Index,还是已经进入 commit?
这三个位置可以同时保存同一个文件的三个版本。理解这一点,就能准确判断 git add、git commit、git diff 和 git restore 会影响什么。
HEAD snapshot Index snapshot Working Tree
current commit next commit draft files on disk
version=1 version=2 version=3
HEAD 指向当前检出的 commit,该 commit 记录一个项目快照。git add 前,不属于 Index 或任何 commit。git commit 的输入来自 Index。Working Tree 中尚未暂存的内容不会进入这次 commit。
从使用者的逻辑模型看,每个 commit 记录项目在某个时刻的快照。git diff 根据两个状态计算差异,让你看到快照之间发生了什么。
Git 的底层存储可能在 packfile 中使用 delta compression 节省空间,但这种存储优化不会改变 commit 表达项目快照的心智模型。
三个常用比较方向:
HEAD -------- git diff --cached --------> Index
Index ----------- git diff -------------> Working Tree
HEAD ------------ git diff HEAD --------> Working Tree
| 命令 | 比较的状态 | 主要回答 |
|---|---|---|
git diff |
Index 与 Working Tree | 还有哪些改动没有暂存 |
git diff --cached |
HEAD 与 Index | 下一次 commit 会记录什么 |
git diff HEAD |
HEAD 与 Working Tree | 当前文件相对 commit 总共改了什么 |
打开“快照与状态”交互演示,依次执行“编辑、暂存、再次编辑、提交”,观察三个位置中 app.txt 的版本变化。
直接在本地浏览器查看时,可以从仓库根目录启动静态服务器:
python3 -m http.server 8000
然后访问:
http://localhost:8000/interactive/git-mental-model/snapshots-and-state.html
查看完成后,在运行服务器的终端按 Ctrl-C 退出。
下面的实验不会修改当前项目。它会创建一个新的临时仓库。
lab_dir=$(mktemp -d "${TMPDIR:-/tmp}/my-git-snapshots.XXXXXX")
git -c init.defaultBranch=main init -q "$lab_dir"
cd "$lab_dir"
git config user.name "My Git Lab"
git config user.email "lab@example.com"
printf 'version=1\n' > app.txt
git add app.txt
git commit -q -m "record version 1"
git status --short
预期输出:没有输出,表示 HEAD、Index 和 Working Tree 内容一致。
printf 'version=2\n' > app.txt
git status --short
预期输出:
M app.txt
左侧第一列表示 Index,右侧第二列表示 Working Tree。这里第一列为空、第二列是 M,说明改动只存在于 Working Tree。
此时:
HEAD=version=1 Index=version=1 Working Tree=version=2
git add app.txt
git status --short
预期输出:
M app.txt
M 移到第一列,表示 Index 相对 HEAD 已经改变,Working Tree 与 Index 一致。
HEAD=version=1 Index=version=2 Working Tree=version=2
printf 'version=3\n' > app.txt
git status --short
预期输出:
MM app.txt
现在同一个文件同时有三个版本。直接读取三个位置:
git show HEAD:app.txt
git show :app.txt
cat app.txt
预期输出依次是:
version=1
version=2
version=3
其中 :app.txt 表示读取 Index 中的 app.txt。
git commit -m "record version 2"
git status --short
commit 输出中的对象 ID 会因环境而变化,可以把它规范化理解为:
[main <object-id>] record version 2
1 file changed, 1 insertion(+), 1 deletion(-)
随后 git status --short 仍会显示:
M app.txt
最终状态:
HEAD=version=2 Index=version=2 Working Tree=version=3
这证明 commit 记录了 Index 中的 version=2。稍后写入 Working Tree 的 version=3 仍然没有提交。
暂存以后执行:
git diff
可能没有任何输出。这只能说明 Working Tree 与 Index 一致,不能证明仓库没有改动。继续检查:
git diff --cached
git status --short
因此,Agent 或人类在汇报“没有 diff”时,还应明确执行的是哪一种 diff。
如果 app.txt 已经被 Git 跟踪,并且只在 Working Tree 中误删,先检查:
git status --short
git diff -- app.txt
确认需要丢弃这次删除后,从 HEAD 恢复:
git restore --source=HEAD --worktree -- app.txt
git status --short
git restore 会覆盖 Working Tree 中对应路径的状态。执行前必须确认当前改动确实可以丢弃。
Git 无法恢复从未被 git add、commit 或其他工具保存过的未跟踪文件。这个边界比恢复命令本身更重要。
commit 记录 Index。未暂存的 Working Tree 内容仍留在磁盘上。
git diff 没输出就代表没有改动”git diff 默认只比较 Index 与 Working Tree。已暂存改动要用 git diff --cached 检查。
Git 对外呈现 commit 快照。差异是比较结果,底层 packfile 可以使用增量压缩。
只有进入 Git 对象、Index、stash 或其他备份的内容才有恢复依据。未跟踪且从未保存的内容不在 Git 的恢复范围内。
Agent 完成任务后,至少应区分以下证据:
| 要确认的事实 | 检查方式 |
|---|---|
| Working Tree 有哪些未暂存改动 | git diff |
| Index 准备提交什么 | git diff --cached |
| 当前相对 HEAD 的全部已跟踪改动 | git diff HEAD |
| 是否存在未跟踪、暂存或混合状态 | git status --short |
| 最近一次 commit 实际记录了什么 | git show --stat --oneline HEAD |
实践规则:
git diff 为空就跳过 git diff --cached 和 git status。git add -A。MM app.txt?MM app.txt 状态执行 commit,会提交哪个版本?