第 4 篇index 为什么是 Git 的暂存区前几篇我们已经看到blob 保存文件内容 tree 保存目录快照 commit 保存历史节点 branch 指向最新 commit现在回头看一个最容易被误解的东西.git/index。配套可视化在本地项目中E:/mini-git/docs/visuals/index-demo.html。index 不是目录index 不是.git/index/目录而是一个文件.git/index它像一张表记录下一次 commit 准备提交哪些路径每个路径对应哪个 blob。比如a - da0f8e... b - c9c6af...人话index 下一次提交的准备清单为什么需要 index因为工作区是草稿。你可能同时改了a和b但这次只想提交a。如果没有 indexGit 就很难表达工作区里 a 和 b 都变了 但下一次 commit 只要 a 的新版本。index 的作用就是把这两件事分开worktree 你正在编辑的全部草稿 index 你挑出来准备提交的版本真实运行只提交 a不提交 b先创建第一次提交mkdir-p/tmp/mgit-article-04-realcd/tmp/mgit-article-04-real /mnt/e/mini-git/build/mgit initprintfa1\naprintfb1\nb /mnt/e/mini-git/build/mgitadda /mnt/e/mini-git/build/mgitaddb /mnt/e/mini-git/build/mgit commit-mv1实际输出里第一次提交前两个文件的 blob 是add a (blob da0f8ed91a8f2f0f067b3bdf26265d5ca48cf82c) add b (blob c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061) [master dc912628bbebd37b296827c95ce614bb78b51ad3] v1此时三份状态一致HEAD: a a1, b b1 index: a a1, b b1 worktree: a a1, b b1现在同时修改a和bprintfa2\naprintfb2\nb只 adda/mnt/e/mini-git/build/mgitadda输出add a (blob c1827f07e114c20547dc6a7296588870a4b5b62c)现在三份状态变成HEAD: a a1 b b1 index: a a2 b b1 worktree: a a2 b b2这就是 index 的意义a 的新版本进了 index。 b 的新版本还只在工作区。status 输出怎么理解执行/mnt/e/mini-git/build/mgit status真实输出On branch master Changes to be committed: modified a Changes not staged for commit: modified b Untracked files:这不是随便分类而是比较出来的。第一组比较HEAD vs indexa在 HEAD 里是a1在 index 里是a2所以a Changes to be committed第二组比较index vs worktreeb在 index 里是b1在 worktree 里是b2所以b Changes not staged for commitdiff 输出怎么理解执行默认 diff/mnt/e/mini-git/build/mgitdiff真实输出~ b (modified)默认 diff 比较的是index vs worktree所以它看到b。因为b的新版本还没有 add。执行 cached diff/mnt/e/mini-git/build/mgitdiff--cached真实输出~ a (modified)diff --cached比较的是HEAD vs index所以它看到a。因为a已经进入 index准备提交。commit 读的是 index如果此时执行/mnt/e/mini-git/build/mgit commit-mupdate a这次提交会保存a a2 b b1不会保存工作区里的b b2因为b2没有 add 进 index。所以最关键的一句话是commit 提交的是 index不是直接提交 worktree。这解释了为什么 Git 可以只提交一部分修改。index 和 tree 的关系index 是可变清单你 add 一次它变一次。tree 是正式对象commit 前Git 会把 index 写成 tree。所以index 下一次提交的草稿 tree 某次提交的正式目录快照源码入口主要文件src/core/index.c src/commands/cmd_add.c src/commands/cmd_status.c src/commands/cmd_diff.c src/commands/cmd_commit.cadd 主线读取工作区文件 - 写 blob - index_add(path, hash, mode) - index_write(.git/index)status 主线读取 HEAD tree 读取 index 扫描 worktree 比较 HEAD/index/worktreediff 主线默认 diffindex vs worktree cached diffHEAD tree vs indexcommit 主线读取 index - index_write_tree - commit_createindex 和真实 Git 的边界真实 Git 的 index 更复杂。它会保存更多元数据例如文件 stat 信息、冲突阶段 stage、扩展数据等。但教学主线是一样的index 是工作区和 commit 之间的暂存清单。面试怎么说Git 的 index/staging area 是下一次提交的候选快照。git add 会把工作区文件内容写成 blob并把 path - blob hash 写入 index。git commit 不直接提交工作区而是从 index 写出 tree再创建 commit。这也是为什么 Git 可以只提交一部分修改。status 和 diff 本质上是在比较 HEAD、index 和 working tree。这一篇先记住index 是.git/index文件不是目录。index 是下一次 commit 的准备清单。add更新 index。commit读取 index。status通过比较 HEAD、index、worktree 得出状态。默认diff是 index vs worktreediff --cached是 HEAD vs index。