git 索引陈旧速查:假修改与六个根因
更新时间:2026-10-24。FAQ 速查页,面向明明一行代码没动,
git status却报几十个文件 modified、git diff又空空如也的同学。git 索引陈旧的含义一句话:git 判断「文件改没改」靠索引(index)里缓存的 stat 信息(修改时间、inode、大小)与工作区文件的 stat 做快比较——stat 对不上才去比内容;一旦索引里缓存的 stat 与文件系统当前的真实 stat 错位,就会出现「内容没变却被标 modified」的假修改。收敛在六根轴上:索引与文件系统时间戳错位(检出、压缩、构建脚本 touch 过文件)、换行符配置翻转导致内容级差异、文件可执行位被改、符号链接或大小写敏感的挂载变化、索引文件本身损坏、以及排查无效后的重克隆兜底。本篇主线命令是git update-index --refresh——让 git 重读工作区 stat 刷新索引,九成假修改一条命令收工。
本文要回答的问题
git status报 modified 但git diff为空,文件到底改没改?git update-index --refresh在做什么,会不会丢改动?- 为什么构建脚本跑完整个仓库都「脏」了?
core.fileMode与换行符配置怎么制造假修改?- 什么时候该放弃治疗直接重克隆?
一、报错原文与典型现象
git 索引陈旧的典型现象与命令输出如下(路径以 /proj/demo 占位):
$ git -C /proj/demo status
Changes not staged for commit:
modified: src/main.c
modified: src/util.c
modified: docs/readme.md
...(共 47 个文件,但你一个都没碰)$ git -C /proj/demo diff
$ # 没有任何输出——内容根本没变$ git -C /proj/demo update-index --refresh
src/main.c: needs update
src/util.c: needs update$ git -C /proj/demo diff
diff --git a/src/main.c b/src/main.c
old mode 100644
new mode 100755读现象抓三个信号:status 说改了、diff 空白,是 stat 级假修改的典型指纹;update-index --refresh 输出 needs update 是它在逐个文件确认「stat 变了但内容没变」,刷新后假修改消失;diff 只显示 old mode/new mode 则不是内容变了,是文件权限位翻了——这是另一类根因。先分清「内容假修改」与「模式真差异」,修法不同。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| status 全脏、diff 空白 | 索引 stat 与工作区错位 | update-index --refresh 后恢复干净 | 刷新索引即可,内容无恙 |
| 构建后全仓库 modified | 构建脚本 touch 了源码 | 看文件 mtime 是否全是构建时刻 | refresh;构建产物加 .gitignore |
| diff 只有 old/new mode | 可执行位被改 | ls -l 看权限位 | git config core.fileMode false 或 chmod 还原 |
| diff 全文件每行都变 | 换行符 CRLF/LF 翻转 | file 或 git diff 看 ^M | 统一 core.autocrlf 与 .gitattributes |
| 挂载或同步后全脏 | 文件系统 stat 语义变了 | 换文件系统/同步工具后出现 | refresh 一次;core.fileMode 按需关闭 |
| refresh 后仍报异常 | 索引文件损坏 | git fsck 与 status 行为怪异 | rm .git/index; git reset 重建索引 |
| 怎么修都反复脏 | 多重根因叠加或工具链循环 touch | 逐条排除无效 | 备份改动后重克隆兜底 |
三、排查决策图
下面这张图是标准排查路线,颜色按角色区分:红是现象入口,蓝是排查动作,绿是通过分支,橙是修复动作,紫是防复发沉淀。
读这张图抓主线顺序:第一步永远是先跑 update-index --refresh 再下结论——它不碰工作区内容,只重读 stat,是无创的探针;刷新后还剩下的差异才是「真差异」,再按 mode 变化、换行符、索引损坏逐类归因。跳过刷新直接改配置,会把真改动和假修改搅在一起,越修越乱。
四、逐条排查展开
第一条:stat 错位——假修改的最大宗。 git 为性能把每个文件的 mtime、inode、大小缓存进索引,status 先比 stat、stat 一致就认定没改。反过来,任何「内容没变但 stat 变了」的操作——检出分支、解压缩、备份还原、某些编辑器保存——都会让 stat 对不上,git 退而求其次去比内容,发现内容一致后标「其实没改」但 status 里已经刷出一排 modified。处置一条命令:
git -C /proj/demo update-index --refresh它重读工作区 stat 刷新索引,假修改当场消失。这条命令只读工作区、不写内容,绝不会丢你的真实改动。
第二条:构建脚本 touch 源码——团队级高发。 某些构建系统为了触发依赖重编,会先 touch 一遍源文件更新 mtime;构建一跑全仓库 stat 全变。短期 refresh 收工,长期要让构建只 touch 产物目录,产物目录加进 .gitignore。判这类根因有个土办法:看 status 报脏的文件 mtime 是不是集中在同一分钟——全仓库同一时刻「被修改」,人不可能干出这事,必是脚本。这类「工具链污染环境」的问题与 git 钩子与 CI 偶发失败速查 里的环境差异同源。
第三条:文件模式翻转——old mode/new mode。 在 Windows 挂载的目录、或 chmod -R 777 之后,文件可执行位变了,diff 显示 old mode 100644 / new mode 100755。团队不关心执行位的直接关掉:
git config core.fileMode false关心执行位的(脚本仓库)则 chmod 还原成仓库登记的模式。注意 core.fileMode 在仓库初始化时按文件系统能力自动设定,跨平台协作时常是翻供的源头。
第四条:换行符翻转——全文件每行都「变」。 core.autocrlf 一边开一边关,或 .gitattributes 的 text 规则改了,检出时换行符被重写,diff 显示整个文件每行都动过。处置不是 refresh(stat 和内容都变了),而是团队统一配置:.gitattributes 里显式写死 *.c text eol=lf 之类,再 git add --renormalize . 一次性归一。换行符乱局与 git 中文乱码与编码速查 里的跨平台问题同一族。
第五条:文件系统语义变化——挂载与同步工具。 网络盘、NTFS 挂载、rsync/云盘同步过的目录,inode 与 mtime 语义跟原生文件系统不同,索引里的旧 stat 大面积失效。一般 refresh 一次即可;反复出现的把 core.fileMode false、core.trustctime false 按需调整,减少 stat 比较的敏感度。
第六条:索引损坏与重克隆兜底。 rm .git/index && git reset(不带 --hard,不丢工作区改动)可以重建一份全新索引,对付索引文件本身损坏的怪症——索引损坏的典型指纹是 git status 直接报 index file corrupt 或行为彻底错乱,与 stat 错位不是一回事。如果连这都不灵、且刷新后仍反复大面积报脏,说明根因叠加或工具链在循环 touch——把未提交改动备份成 patch(git diff > /proj/demo/fix.patch)后重克隆,是成本最低的终点。判断「该修了还是该重拉」的思路与 git 远程分支冲突速查 里「清干净重建」的兜底哲学一致。
五、规范做法与预防
- 构建不碰源码 mtime:构建脚本只写产物目录,产物一律
.gitignore,从源头消灭 stat 污染。 - 换行符写进 .gitattributes:不依赖每人本地的
core.autocrlf,仓库级配置跟着代码走。 - 跨平台仓库设 core.fileMode false:除非仓库真有执行位语义(脚本集),否则关掉少一半假修改。
- status 异常先 refresh:把
git update-index --refresh当橡皮擦,看到大面积假修改先擦一遍再归因。 - 真实改动随时提交或 stash:假修改再凶也只吓得到「工作区有一堆未提交改动」的人,勤提交是最硬的安全网。
这一篇在知识体系里的位置
本篇是 Git 排错速查簇的工作区状态篇,定位「内容没变却被标脏」:换行符与编码的跨平台乱局看 git 中文乱码与编码速查;工具链环境差异引发的诡异失败看 git 钩子与 CI 偶发失败速查;配置层写错导致 git 拒动看 git 配置解析失败速查;仓库损坏与资源类问题看 git 资源耗尽速查;工作区与提交的基础概念见 add、commit、status。
一句话总结
git 索引陈旧的本质是「索引缓存的 stat 与工作区真实 stat 错位」:status 大面积报脏但 diff 空白,先 git update-index --refresh 无创刷新;刷新后残留的 mode 差异关 core.fileMode、换行符差异用 .gitattributes 归一、索引损坏 rm .git/index 后 git reset 重建,多重根因叠加就备份 patch 重克隆。构建不 touch 源码、仓库级锁死换行符与 fileMode,假修改基本绝迹。