Force push 覆盖了队友的 commit

执行 git push --force 后发现远端分支丢失了队友刚推送的提交,其他人拉取后工作消失。本文给出找回 commit、通知团队同步的完整操作步骤和防护机制。

你在本地做完 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 --forcegit 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 上对所有长期分支(maindeveloprelease/*)开启 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 中查看。

相关阅读

标签: #git #version-control #排查