Git 的可怕之处在于“撤销”有太多种做法,选错了会把改动弄丢。先记一张决策表。
撤销的四种粒度
| 想撤销什么 | 命令 | 影响范围 |
|---|---|---|
| 工作区某个文件的改动 | git restore <file> |
只丢未暂存的修改 |
已 git add 但未提交 |
git restore --staged <file> |
只移出暂存区,改动还在 |
| 最近一次提交(未推送) | git reset --soft HEAD~1 |
提交没了,改动回到暂存区 |
| 已推送的提交 | git revert <commit> |
新增一个反向提交,历史不改写 |
核心原则:已推送的历史不要改写。 reset 之后 push 需要 --force,会把别人的提交一起冲掉;revert 才是协作环境下的正确做法。
reset 的三种模式
git reset --soft <commit> # 只动 HEAD:改动留在暂存区
git reset --mixed <commit> # 默认:改动留在工作区(未暂存)
git reset --hard <commit> # ⚠ 改动全丢,工作区被强制覆盖
--hard 会丢弃已跟踪文件的本地改动(未跟踪且未被忽略的文件保留)。所以:
- 部署机上用它前先确认“这里不该有本地改动”;
- 它不会删除被
.gitignore忽略的文件(比如.env、logs/)。
误提交了敏感信息或大文件
# 刚从暂存区加进去、还没提交
git restore --staged .env && echo '.env' >> .gitignore
# 已经提交但还没推送:改写那一次提交
git rm --cached .env && git commit --amend
# 已经推送:从**最新提交**里移除
git rm --cached .env && git commit -m 'chore: 移除误提交的 .env'
⚠ 重要认知:git rm --cached 只能让文件从当前版本消失,历史提交里仍然能检索到。要彻底清除必须重写历史(git filter-repo / BFG)+ 强制推送,并让所有人重新克隆——代价很大。所以密钥一旦提交过,就应该直接轮换,而不是指望删干净。
reflog:误操作的最后一根救命绳
git reflog # 列出 HEAD 的移动历史(含被 reset 丢掉的提交)
git reset --hard HEAD@{2} # 回到当时的状态
git checkout -b rescue <sha> # 或先建分支保住它
reflog 默认保留 90 天。在慌乱中执行 --hard 之前,先记住这个命令还能救回来。
与项目流程相关的小事
- 提交前用
git status --short与git diff --cached双重确认(别用git add -A一把梭,会把临时文件一起提交)。 - 提交信息写“为什么”而不是“改了什么”——改了什么看 diff 就知道。
- 打回退点用
git tag -a v1.0.0 -m '...',比记住一串 sha 可靠。
