重复提交非 git 错误,而是因本地分支已含远程提交却仍执行 rebase/merge,导致已存在提交被重复重放;应先用 git log origin/main..head 确认独有提交,再交互式变基精简,慎用 squash/fixup,强推必用 --force-with-lease。

直接说结论:重复提交不是 Git 本身出错,而是你本地分支已包含远程分支的提交,却仍执行 git rebase origin/main 或 git merge origin/main —— 这会导致 Git 把“已经存在”的提交又重放一遍。
为什么 git rebase origin/main 会生成重复 commit
它默认把「当前分支所有不在 origin/main 上的提交」都搬过去,但如果你之前用 git pull(默认是 merge)同步过 origin/main,那些 merge commit 和它带进来的提交,其实早已在本地历史里了。rebase 不会智能跳过它们,只会机械重放,结果就是:同一个 diff 出现两次,hash 不同、内容相同、冲突反复来。
常见错误现象:
- 执行
git rebase origin/main后,git log显示一堆看起来一模一样的提交,只是 hash 和时间不同 - 解决完一次冲突,rebase 到一半又在同一个文件同一行报冲突
- 推送后同事发现某次修复在 master 上出现了两次
确认要变基的范围:先看,再动
别猜,用命令验证哪些提交真属于你、哪些其实是“借来的”:
- 运行
git log origin/main..HEAD—— 只显示「仅在你当前分支、不在origin/main上」的提交。如果输出为空或只有几条,说明你基本没新东西,不该 rebase - 如果输出里夹着
merge提交或大量疑似重复的 message(比如都含[fix]),立刻停手,改用交互式:git rebase -i origin/main - 在编辑器里,删掉所有你不确定来源的行(尤其是 merge 提交、调试临时 commit、或明显和别人 PR 内容重叠的),只留真正由你写的 feature 提交
squash 和 fixup 别乱配,顺序决定结果
二者都合并前序 commit,但行为完全不同:
-
squash:合并后打开编辑器,让你重写完整 commit message —— 适合需要提炼语义的场景(如把 5 次小修合成一句“修复登录态失效”) -
fixup:直接丢弃被合并 commit 的 message,只保留前一个的 —— 适合纯技术整理(如“加日志”“改变量名”这类辅助提交) - 典型误操作:想压 5 条成 1 条,却把第 1 行标
pick、第 2–5 行全标squash→ 编辑器会弹 4 次,极易漏改或填错;正确做法是第 2–5 行全标fixup,第 1 行保持pick - 注意:
fixup不校验代码逻辑,如果中间某次fixup的提交删了关键字段,合并后功能就直接挂了
强制推送必须用 --force-with-lease
变基后必须强推,但 --force 是危险开关:
-
--force:不管远程有没有新提交,直接覆盖 —— 如果同事刚 push 了一版 hotfix,会被无声抹掉 -
--force-with-lease:推送前检查远程引用是否和你本地 fetch 时一致,不一致就中止,给你留出沟通窗口 - 执行前务必确认:没人正在该分支上开发;已提前告知协作成员;本地
git fetch origin是刚刚做的 - 命令固定为:
git push origin your-branch --force-with-lease,别省略origin和分支名
最易被忽略的一点:变基不是万能清洁剂。如果分支已被多人基于它继续开发,或者已作为 base 被其他 PR 引用,强行变基等于重写共享历史 —— 此时应放弃 rebase,改用 git merge --squash 或直接接受线性但含 merge commit 的历史。干净的前提是可控,而非看起来整齐。











