你在 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 历史里的旧版本文件
- Force push 覆盖了队友的 commit
- 我在 detached HEAD 上提交了,怎么办
- git pull —rebase 让冲突历史变乱
- Cherry-pick 解冲突后变成空 commit
- AI 意外修改了 Git 历史
- AI 帮你回滚代码改动
标签: #git #version-control #排查