你在本地做完 rebase,执行 git push --force origin feature/checkout 后,队友在 Slack 里告诉你他刚推的三个 commit 消失了,git log origin/feature/checkout 里完全看不到。更糟糕的是,他已经在那些 commit 的基础上继续开发,现在本地历史和远端对不上。Force push 是 Git 里最危险的操作之一,但造成的损失几乎都可以修复——只要对象还在远端的 reflog 里。
常见原因
1. rebase 后未用 --force-with-lease 而直接 --force
最高命中率。--force 会无条件用本地历史覆盖远端,不检查远端是否有新 commit。--force-with-lease 在推送前会验证远端 HEAD 是否与本地预期一致,若远端有新提交会拒绝推送。
怎么判断:git reflog show origin/feature/checkout 若能在覆盖前看到队友的 SHA,说明是 --force 直接覆盖。
2. CI/CD 流水线自动执行 force push
某些 CI 脚本在 release 分支上自动 rebase 并 force push,没有检查远端是否有未合并的新提交,导致在 push 窗口期内推送的 commit 被覆盖。
怎么判断:检查 CI 配置文件(.github/workflows/、.gitlab-ci.yml)里是否有 git push --force 或 git push -f。
3. 多人共用同一个 feature 分支时互相 rebase
团队多人在同一个 feature 分支协作,各自 rebase 后都 force push,后推者覆盖前推者。这是协作模型设计问题,而非单次操作失误。
怎么判断:git log --all --oneline | wc -l 与团队成员各自的 git log 对比,看谁的 commit 消失了。
4. 误操作:push 时指定了错误的本地分支
git push --force origin local-experiment:main 把实验分支的历史覆盖到了 main,与预期分支完全不同。
怎么判断:对比被覆盖分支的内容与本地实验分支内容是否一致。
5. 分支保护规则未开启或被临时关闭
GitHub/GitLab 上分支保护规则禁用了 force push,但有人临时关闭了保护后忘记恢复,或者用了拥有 bypass 权限的 token。
怎么判断:检查仓库 Settings > Branches > Branch protection rules,确认 force push 保护是否在位。
最短修复路径
Step 1:立即暂停,通知所有协作者停止推送
在团队频道里发出警告,避免更多人在已被覆盖的历史上继续工作,造成二次分叉。
Step 2:从 GitHub/GitLab 的远端 reflog 找回丢失 commit
GitHub 企业版和 GitLab 都保留服务端 reflog。在 GitHub 上:
# 用 GitHub API 查询远端 reflog(需要 repo 权限的 token)
curl -H "Authorization: token $GH_TOKEN" \
"https://api.github.com/repos/org/repo/git/refs"
更直接的方式是让队友提供他推送时的 SHA:
# 队友在自己机器上执行,找到被覆盖前的 commit
git reflog show origin/feature/checkout
Step 3:在本地验证 SHA 还存在
# 如果队友还有本地仓库,他的对象库里应该有这些 commit
git show <teammates-sha>
Step 4:备份当前远端状态
git tag backup/force-push-overwrote-$(date +%Y%m%d) origin/feature/checkout
Step 5:将队友的 commit 合并回来
# 方案 A:cherry-pick 队友的 commit
git cherry-pick <teammates-sha-1> <teammates-sha-2> <teammates-sha-3>
# 方案 B:把队友的分支状态 merge 进来(需要队友共享他的本地分支)
git remote add teammate git@github.com:teammate/repo.git
git fetch teammate
git merge teammate/feature/checkout
Step 6:用 --force-with-lease 安全推送恢复后的历史
git push --force-with-lease origin feature/checkout
Step 7:让所有协作者重新同步
每个协作者都需要执行(而不是普通的 git pull):
git fetch origin
git reset --hard origin/feature/checkout
预防建议
- 把
--force替换为--force-with-lease,这是唯一需要养成的肌肉记忆。全局配置别名:git config --global alias.fpush 'push --force-with-lease'。 - 在 GitHub/GitLab/Bitbucket 上对所有长期分支(
main、develop、release/*)开启 branch protection,禁止 force push,即使是管理员也不例外。 - 多人协作的 feature 分支改用 merge 而非 rebase 工作流,避免历史重写带来的 force push 需求。
- CI 脚本中禁止
git push --force;若必须,加人工审批 gate。 - 团队 onboarding 文档中明确标注:
git push --force是危险操作,需要在团队频道提前告知。 - 配置 pre-push hook 在检测到
--force时弹出确认提示:在.git/hooks/pre-push中检查$@是否包含-f或--force。
常见问答 (FAQ)
Q: --force-with-lease 和 --force-if-includes 有什么区别?
A: --force-with-lease 检查远端 ref 是否与本地追踪的 remote-tracking ref 一致;--force-if-includes 额外要求远端新 commit 已经被本地 fetch 并包含在 rebase 历史中。两者都比裸 --force 安全,--force-if-includes 更严格。
Q: 队友没有任何本地备份,SHA 完全找不到了,还能恢复吗? A: 联系 GitHub/GitLab 支持,他们通常在服务端保留 30 天的对象和 reflog 快照。企业版有更长的保留期。提供仓库名称、分支名称、大致操作时间,支持团队可以帮助恢复。
Q: 我自己的 feature 分支(无人协作)可以 force push 吗?
A: 单人独用的 feature 分支使用 --force-with-lease 是安全的,但在 PR 期间 reviewer 可能已经基于旧历史留下了评论,force push 后评论会失去上下文。推荐等 PR 合并前不要 force push。
Q: 分支保护开启后,管理员 bypass 了 force push,有没有审计日志? A: GitHub 企业版和 GitLab 均有 audit log,记录所有 protected branch bypass 操作,包括操作人、时间、被覆盖的 SHA。在 Settings > Audit log 中查看。
相关阅读
- Rebase 之后 commit 消失了
- 找回 Git 历史里的旧版本文件
- 分支保护规则让我的 PR 合不了
- 我在 detached HEAD 上提交了,怎么办
- git pull —rebase 让冲突历史变乱
- AI 意外修改了 Git 历史
- AI 帮你回滚代码改动
标签: #git #version-control #排查