你正在处理一个已经被 Git 跟踪的文件:之前已经把一部分修改用 git add 放进了暂存区,后来又在工作区继续改,结果改乱了。现在你想只丢弃工作区里那些未暂存的改动,让文件回到暂存区里的版本,同时让暂存内容原样保留。下面以单个文件为例,在本地仓库中按“确认差异—备份—撤销—核对”四步完成。准备一个终端、目标文件的路径,并先记住一条:撤销工作区修改会丢弃未暂存内容,Git 不会自动留副本;不要用 git reset --hard 或 git checkout HEAD -- 文件 代替。

第一步:确认工作区和暂存区差在哪里
先在仓库根目录打开终端,执行:
git status
重点看两栏输出。Changes to be committed 下面列出的是暂存区内容,也就是你准备提交的修改;Changes not staged for commit 下面列出的是工作区里尚未暂存的修改。如果目标文件同时出现在这两栏,说明暂存区里有一个版本,工作区里又叠加了新修改。你要撤销的是后一栏里的内容,而不是前一栏。
接着查看具体差异:
git diff -- "src/app.js"
git diff 默认比较工作区和暂存区,输出的是“还没有被 git add 的那部分修改”。确认这些差异确实是你想丢弃的。再看暂存区:
git diff --cached -- "src/app.js"
git diff --cached 比较暂存区和最新提交,显示的是你准备保留的暂存内容。核对时不要把两者的输出弄反:前者是准备丢弃的,后者是准备保留的。
如果文件路径不确定,可以直接复制 git status 里显示的路径。git status 通常显示相对于仓库根目录的路径;如果你在子目录里执行命令,路径要按当前目录来写。路径中有空格时,用引号包起来。
第二步:备份你还要保留的未暂存修改
如果 git diff 里有一部分内容你其实还想留着,只是不想现在提交,先备份。最直观的方法是把当前工作区文件复制到项目外:
cp -- "src/app.js" "../app.js.worktree.bak"
Windows PowerShell 可以用:
Copy-Item "src/app.js" "..app.js.worktree.bak"
另一种方法是只保存未暂存差异,生成一个补丁文件:
git diff --output="../app.js.worktree.patch" -- "src/app.js"
这个补丁只包含工作区里尚未暂存的修改,不包含暂存区内容。之后可以用 git apply --check "../app.js.worktree.patch" 检查能否应用,再用 git apply "../app.js.worktree.patch" 把修改加回来。备份文件建议放在项目目录外,避免它出现在 git status 里干扰判断。
如果你已经确认这些未暂存修改完全不要,可以跳过备份。但撤销后 Git 不会替你保留这部分内容,编辑器的本地历史或系统备份也不一定可靠。这个风险需要先明确。

第三步:撤销工作区修改,但不要动暂存区
Git 2.23 及以上版本推荐使用 git restore:
git restore -- "src/app.js"
这条命令默认把工作区文件恢复到暂存区里的版本。因为不加 --staged,它不会改动暂存区。执行成功时通常没有输出。此时工作区里那些未暂存的新修改被丢弃,暂存内容仍然保留。
如果你的 Git 版本低于 2.23,可以使用旧写法:
git checkout -- "src/app.js"
git checkout -- 路径 同样是从暂存区恢复工作区文件,不会取消暂存。命令中的 -- 用来明确后面是文件路径,建议保留。
需要特别避开几种看似相近、实际会影响暂存区的命令:
| 命令 | 影响范围 | 是否适合当前任务 |
|---|---|---|
git restore -- "src/app.js" |
只恢复工作区,暂存区不变 | 适合 |
git checkout -- "src/app.js" |
旧版本等效,只恢复工作区 | 适合 |
git restore --staged -- "src/app.js" |
只改暂存区,工作区不变 | 不适合,会取消暂存 |
git checkout HEAD -- "src/app.js" |
工作区和暂存区都覆盖为最新提交版本 | 不适合,会丢失暂存内容 |
git reset --hard |
工作区和暂存区一起重置 | 不适合,范围过大 |
如果你只想丢弃文件中的部分修改,不要直接整文件恢复。可以先备份,再手动编辑,或者使用 git restore -p -- "src/app.js" 按块选择要恢复的内容;这个交互式选项同样需要 Git 2.23 及以上版本。
第四步:核对暂存内容是否还在
撤销后先看简洁状态:
git status --short
目标文件如果只显示为 M src/app.js,表示修改在暂存区;如果显示为 M src/app.js,表示工作区还有未暂存修改;如果显示为 MM src/app.js,表示两边都有修改。你希望看到的是目标文件不再出现在未暂存那一侧,但暂存状态仍然存在。
再分别核对差异:
git diff -- "src/app.js"
git diff --cached -- "src/app.js"
第一条命令应该没有输出,说明工作区已经和暂存区一致;第二条命令应该仍然显示暂存内容,说明准备提交的修改还在。也可以看暂存统计:
git diff --cached --stat
如果这里还能看到目标文件,说明暂存内容没有被误清掉。

路径不对或修改还在时怎么查
如果执行 git restore 时出现类似下面的报错:
error: pathspec 'src/app.js' did not match any file(s) known to git
先检查路径。可以在仓库根目录执行 git status,直接复制其中的文件路径;如果文件在子目录里,确认当前终端所在位置。也可以用下面的命令确认文件是否已被 Git 跟踪:
git ls-files -- "src/app.js"
没有输出通常说明路径不对,或者该文件本来就没有被跟踪。未跟踪文件的清理不属于本文场景,不要用本文的恢复命令处理它。
如果命令执行后修改仍然存在,按顺序排查:
- 确认执行的不是
git restore --staged -- "src/app.js"。只加--staged只会取消暂存,不会改工作区。 - 确认路径没有写错。用
git diff --name-only查看当前还有哪些未暂存文件,复制准确路径后重试。 - 确认终端在正确的仓库和目录中。路径是相对于当前目录解析的,不是永远相对于仓库根目录。
- 确认文件名大小写与实际文件一致,尤其是在大小写敏感的系统上。
想恢复刚撤销的修改时,如果之前生成过补丁,先检查再应用:
git apply --check "../app.js.worktree.patch"
git apply "../app.js.worktree.patch"
如果之前复制过 .bak 文件,也可以把它复制回原路径。反过来,如果暂存内容也被覆盖了,通常需要依赖你事先的备份;git checkout HEAD -- 文件 和 git reset --hard 之所以危险,就是因为它们会同时影响暂存区和工作区。遇到不确定的情况,先停下并备份当前文件,再继续操作。
















































路径大小写真的坑
备份补丁这步救过我的代码
改乱了还舍不得暂存,太真实
diff 和 diff –cached 老看反
MM 状态第一次见真的会懵