Rebase 之后 commit 消失了

执行 git rebase 后发现部分提交从分支历史消失,log 里找不到。本文用 reflog 精准找回丢失 commit,给出完整恢复和预防方案。

你在 feature/payment 分支上辛苦写了五个 commit,执行 git rebase main 后终端提示成功,但 git log --oneline 只剩两个提交。另外三个 commit 消失了,git status 干净,工作区也没有那些改动。这种情况并非真正丢失——Git 的对象数据库在默认 90 天内不会删除任何对象——但需要通过 reflog 把它们找回来。

常见原因

1. Interactive rebase 中误操作 drop 或 squash

git rebase -i HEAD~5 打开编辑器,操作者误把某行从 pick 改成 drop,或把多个 commit squash 成一个但丢弃了消息。保存退出后那些 commit 就不在当前历史里了。

怎么判断git reflog | head -20,找 rebase (start) 条目前后,看哪些 commit SHA 消失了。

2. rebase 解冲突时执行了 git rebase --skip

遇到冲突后执行 --skip 会完全丢弃引发冲突的那个 commit,而不是解冲突后继续。很多人误以为 skip 是「跳过这次冲突步骤」,实际上是「丢弃这个 commit」。

怎么判断git reflog | grep 'rebase --skip' 若有输出,说明有 commit 被 skip。

3. git pull --rebase 在多人协作时把本地 commit 丢在远端基准之前

git pull --rebase 会把本地 commit 移到远端最新 commit 之后。如果远端已经包含了与本地某个 commit 内容完全相同的改动(比如通过 cherry-pick 已经合并),rebase 会认为该 commit 变成了空 commit 并自动跳过。

怎么判断git log origin/main..HEAD 对比本地与远端差异,若某个 commit 消失,再查 git log origin/main --oneline | grep "关键词" 是否已包含该改动。

4. rebase 时误用了 --onto 导致基准偏移

git rebase --onto newbase oldbase branch 语义复杂,写错参数会把 commit 范围算错,导致部分 commit 落在新基准之外而被忽略。

怎么判断:查看 git reflog 中 rebase 前后的 commit 数量,若减少,检查 --onto 的三个参数是否都正确。

5. 在 detached HEAD 状态下完成了 rebase 后切换分支

部分工作流里会在 detached HEAD 状态 rebase,rebase 结束后切回命名分支,detached HEAD 上的新历史就变成了悬挂对象,不在任何分支上。

怎么判断git reflog | grep 'HEAD detached' 若有条目,detached 阶段的 commit 可能未被纳入命名分支。

6. 自动化脚本 rebase 后 force-push 覆盖了原始历史

CI/CD 脚本执行 rebase 并 git push --force,原始 commit 被覆盖,其他开发者的本地分支还指向旧历史,而远端已无这些对象。

怎么判断git log origin/feature --oneline 与本地 git log feature --oneline 对比,若本地有远端没有的 SHA,说明远端已被覆盖。

最短修复路径

Step 1:查看 reflog,定位丢失的 commit

git reflog --all | head -50

找到类似 abc1234 HEAD@{5}: commit: fix payment validation 的条目,记录 SHA。

Step 2:备份当前状态

git tag backup/before-rebase-recovery HEAD

Step 3:验证丢失的 commit 内容还在

git show abc1234

若能看到 diff,说明对象未被 GC 清理,可以恢复。

Step 4a:恢复单个 commit — cherry-pick

git cherry-pick abc1234

若有多个连续 commit 需要恢复:

git cherry-pick abc1234^..def5678

Step 4b:恢复到 rebase 之前的整个状态 — reset

若需要完全撤销这次 rebase,找到 rebase 开始前的 commit SHA(reflog 里标注 rebase (start) 的前一条):

# 假设 rebase 前 HEAD 是 aaa0000
git reset --hard aaa0000

Step 5:确认恢复结果

git log --oneline -10
git diff HEAD backup/before-rebase-recovery

预防建议

  • rebase 前始终创建备份标签:git tag backup/pre-rebase-$(date +%Y%m%d),不增加任何历史负担。
  • 使用 git rebase -i 时,在编辑器里把所有行打印出来再确认,不要只看前几行。
  • 团队规范里明确禁止对已推送到 main/master 的 commit 执行 rebase,只允许在个人 feature 分支使用。
  • git pull 默认行为建议设为 merge 而非 rebase:git config --global pull.rebase false,避免不知情的自动 rebase。
  • CI 脚本中执行 rebase 后,force-push 前必须有人工审批步骤。
  • 配置 reflog 保留期更长:git config --global gc.reflogExpire 180(默认 90 天延长至 180 天)。
  • 重要 commit 使用 git notes 附加标注,即使 rebase 后 SHA 变化,笔记也能通过对象内容找回。

常见问答 (FAQ)

Q: reflog 里找不到 SHA 了,真的永久丢失了吗? A: 只要没有手动执行过 git gc --prune=now 或距离 commit 未超过 90 天,git fsck --unreachable 能列出所有悬挂对象,再用 git show <sha> 逐一验证内容。

Q: git rebase --abort 能撤销已完成的 rebase 吗? A: 不能。--abort 只在 rebase 进行中(有冲突暂停时)有效。已完成的 rebase 需要用 git reset --hard 回到 reflog 记录的 rebase 前状态。

Q: interactive rebase 中 squash 后 commit 消失,内容还在吗? A: 内容还在,只是合并到了 squash 目标 commit 中。git log -p 查看那个合并后的 commit,可以看到所有改动。若要拆分,用 git reset HEAD~1 再重新拆分提交。

Q: 多人协作时,我 rebase 后 force-push,队友怎么同步? A: 队友需要执行 git fetch origin && git rebase origin/feature,而不是 git pull。或者更安全地:git checkout feature && git reset --hard origin/feature(会丢弃本地未推送改动,操作前先备份)。

相关阅读

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