跳转到内容
关系图谱

Git 将已 merge 的 PR 改为 squash

666 字 2 分钟

Git 将已 merge 的 PR 改为 squash

合并 PR 时本想选 Squash,却误点了 Merge,主分支上多出一个合并提交和一串零碎的中间提交。本文记录如何把这次 merge 改造成 squash 效果。

一句话思路

把主分支 reset 回合并前,用 git merge --squash 重新合入,再强制推送覆盖远端。

事故现场

*   6471e6f 合并来自 chore/xxx 的合并请求 #3   ← 误操作产生的 merge commit
|\
| * aa06d11 fix: 中间提交 9
| * ...     fix: 中间提交 2~8
| * 82884e7 ci: 中间提交 1
|/
*   919dfd0 fork 点(合并前的 main)

期望的结果是 919dfd0 之上只有一个 squash 提交。

补救步骤

bash
# 1. 先留备份分支,指向原 merge commit,随时可回退
git branch backup/pre-squash 6471e6f

# 2. 把 main 重置回合并前
git reset --hard 919dfd0

# 3. squash 合入原分支的最终状态(aa06d11 是分支 tip)
git merge --squash aa06d11

# 4. 提交(见下文「提交信息」)
git commit

# 5. 强制推送覆盖远端
git push --force-with-lease origin main

推送前可用 git diff <原merge提交> HEAD 验证:输出为说明文件内容与原合并结果完全一致,只是历史形态变了。

相关命令详见 Git resetGit merge

提交信息:SQUASH_MSG 机制

git merge --squash 执行后会生成 .git/SQUASH_MSG,其中自动汇总了被压缩的所有提交信息(即平台网页 squash 时预填的那份列表)。

  • 直接运行 git commit(不带 -m):编辑器会加载这份模板
  • 运行 git commit -m "...":模板被覆盖,正文丢失
与 --ff 无关

--ff / --no-ff 只决定是否产生 merge commit,不影响提交信息内容,且与 --squash 互斥。

事后想补正文,用 git commit --amend 即可,中间提交的标题列表可以这样取回:

bash
git log <fork>..<分支tip> --format="- %s" --reverse

踩坑:amend 前先确认 HEAD

真实翻车记录

强推之后远端又合入了新 PR 并被本地 pull 下来。此时再执行 git commit --amend 改的是新 PR 的提交而不是 squash 提交,直接把人家的提交信息覆盖掉了。

恢复方法(提交内容都还在,靠 reflog 找回):

bash
git reset --hard <squash提>      # 回到 squash 提交
git commit --amend -F msg.txt      # 只改写它的提交信息
git cherry-pick <新PR提>          # 把新 PR 原样接回来
git push --force-with-lease origin main

参见 Git cherry-pick

注意事项

强推须知
  • --force-with-lease 而不是 --force,远端有预期外的新提交时会拒绝推送
  • 重写历史前先建备份分支或记下原 SHA
  • 仅适合个人 fork / 无协作者的分支;他人已基于旧历史开发时,改用 Git revert 或保持现状
  • amend / rebase 前先 git log -1 确认 HEAD 就是要改的那个提交