Git 出事了怎么办:8 个真实事故的恢复路径
Git 的日常操作只有 5 个命令,但出事时的救火操作有几十种。这篇按「事故场景」组织,每个场景给出现象、判断依据和最小恢复步骤。所有命令都在真实仓库上验证过。
场景 1:误 commit 到 main 分支
**现象**:刚写完的代码直接提交到了 main,还没 push。
**判断**:`git log --oneline -3` 确认 commit 在本地 main 上,且 `git log origin/main..main` 有输出说明未推送。
**恢复**:
# 保留改动,撤销 commit
git reset --soft HEAD~1
# 切到正确的分支
git switch -c feature/my-work
git commit -m "正确的提交信息"
`--soft` 保留暂存区,`--mixed`(默认)保留工作区但取消暂存,`--hard` 丢弃改动。**只要没 push,三种都可以用。**
场景 2:已经 push 到远程 main
**现象**:误提交已经推送到共享分支。
**判断**:`git log origin/main --oneline -3` 能看到自己的提交。
**恢复**:
# 方法 A:revert(安全,推荐用于共享分支)
git revert <commit-hash>
git push origin main
# 方法 B:force push(危险,仅限确认无他人基于此提交工作时)
git reset --hard HEAD~1
git push --force-with-lease origin main
**关键区别**:`revert` 生成一个反向提交,历史保留,其他人不受影响。`force push` 改写历史,如果同事已经拉取过,他们的本地会与远程分叉。
**`--force-with-lease` 比 `--force` 安全**:它在推送前检查远程是否有你不知道的新提交,有则拒绝。养成用 `--force-with-lease` 的习惯。
场景 3:误删了分支
**现象**:`git branch -D feature/x` 之后发现还需要它。
**判断**:`git reflog` 能看到分支最后指向的 commit。
**恢复**:
# 从 reflog 找到被删分支的最后 commit
git reflog | grep "feature/x"
# 重建分支
git branch feature/x <commit-hash>
**reflog 默认保留 90 天**(可达对象),gc 之后才会真正消失。这是 Git 的"后悔药",比任何备份都可靠。
# 如果连 reflog 都清空了,试试悬空 commit
git fsck --lost-found
场景 4:reset --hard 丢掉了未提交的改动
**现象**:`git reset --hard` 之后发现工作区改动没了。
**判断**:这类改动**没有进入 Git 对象库**,reflog 帮不上。
**恢复**:
# 如果之前 git add 过,对象还在
git fsck --lost-found
# 查看 .git/lost-found/other/ 下的 blob
# 或者用 IDE 的 Local History(IntelliJ/VSCode 都有)
**这是唯一真正危险的场景。** `--hard` 之前必须确认三件事:
1. `git status` 是否干净
2. 有没有 `git stash` 过
3. IDE 的 Local History 是否开启
**预防**:把 `git reset --hard` 换成 `git stash`,随时能 `git stash pop` 回来。
场景 5:合并冲突解错了
**现象**:解决冲突后提交,发现逻辑坏了。
**判断**:`git log --oneline --merges -3` 找到合并提交。
**恢复**:
# 撤销合并提交(保留历史)
git revert -m 1 <merge-commit-hash>
# 或者回到合并前,重做
git reset --hard <merge-commit-hash>^
git merge --no-commit feature/x
# 重新解决冲突
`-m 1` 表示保留第一个父提交(通常是目标分支)。撤销合并提交后,**再次合并同一分支会失败**,因为 Git 认为该分支已合并过。需要先 revert 那个 revert:
git revert <revert-commit-hash>
git merge feature/x
场景 6:提交里包含了不该提交的文件
**现象**:`.env`、密钥、大文件被提交了。
**判断**:`git log --all --full-history -- .env` 确认历史中存在。
**恢复**:
# 仅从当前提交移除,保留本地文件
git rm --cached .env
echo ".env" >> .gitignore
git commit -m "chore: remove .env from tracking"
**但如果已经 push 且包含真实密钥**,仅移除不够——历史里仍有。需要用 `git filter-repo` 重写历史:
# 安装:pip install git-filter-repo
git filter-repo --path .env --invert-paths
git push --force-with-lease --all
**重写历史后必须做的三件事**:
1. 通知所有协作者重新 clone(他们的本地历史已失效)
2. **立即轮换泄露的密钥**(历史可能已被他人拉取、被 CI 缓存、被 fork)
3. 检查 CI/CD 缓存是否需要清理
**最重要的一条**:密钥一旦进入 Git 历史,就应当视为已泄露。重写历史只是止损,不能替代轮换。
场景 7:pull 之后出现意外的合并提交
**现象**:`git pull` 后 `git log` 多了一个 "Merge branch 'main' of ..." 提交。
**原因**:本地有提交,远程也有新提交,`git pull` 默认用 merge 策略。
**修复**:
# 撤销这个合并提交
git reset --hard HEAD~1
# 改用 rebase 拉取
git pull --rebase origin main
**永久配置**:
git config --global pull.rebase true
**区别**:merge 保留分叉历史(有合并提交),rebase 把本地提交"重放"到远程最新提交之上(线性历史)。团队规范应该统一,混用会导致历史混乱。
场景 8:仓库变得极慢或极大
**现象**:`git clone` 要几分钟,`.git` 目录几个 GB。
**判断**:
# 找出最大的对象
git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '/^blob/ {print $3, $4}' \
| sort -rn | head -10
**恢复**:
# 如果大文件在最近提交
git rm --cached bigfile.zip
git commit --amend
# 如果在大段历史里
git filter-repo --path bigfile.zip --invert-paths
git push --force-with-lease --all
**预防**:用 Git LFS 管理二进制文件,或在 `.gitignore` 里提前排除构建产物。
git lfs install
git lfs track "*.psd" "*.mp4"
git add .gitattributes
一张决策表
遇到问题先问三个问题,能覆盖 90% 的场景:
| 问题 | 是 | 否 |
|---|---|---|
| 改动是否 commit 过? | 用 reflog / reset / revert | 只能靠 IDE Local History |
| 是否已 push 到共享分支? | 用 revert,避免 force | 可自由 reset |
| 是否有他人基于此工作? | 绝不改写历史 | 可 force-with-lease |
三条铁律
**1. 不确定就先备份**
git branch backup-$(date +%Y%m%d-%H%M%S)
一条命令,零成本,能救回任何 `reset --hard`。
**2. 优先 revert,慎用 reset**
共享分支上 `reset` 是团队事故,`revert` 只是多一个提交。
**3. 密钥泄露必须轮换,不是删掉就完事**
Git 历史是不可变的分布式账本。任何进入过历史的密钥,都应当视为已公开。
小结
Git 的恢复能力远比大多数人以为的强:
- **commit 过的东西几乎都能找回**(reflog 90 天)
- **没 commit 的东西基本找不回**(除非 IDE 有 Local History)
- **共享分支优先 revert**,`--force-with-lease` 是底线
- **密钥泄露 = 立即轮换**,重写历史只是止损
真正难的不是记命令,是**在慌乱时先做一次备份分支**。
- 点赞
- 收藏
- 关注作者
评论(0)