跳到主要内容
amend 之后 push 被拒:--force-with-lease 踩坑笔记,附 reflog 兜底

amend 之后 push 被拒:--force-with-lease 踩坑笔记,附 reflog 兜底

abanana
abanana

· 阅读约 3 分钟

今天在收尾一个小功能的时候,给上一个 commit 补了两个漏掉的文件,push 直接被拒了。操作就是 git commit --amend,报错是老熟人 non-fast-forward。顺手记一下这篇,讲三件事:为什么 amend 之后会被拒、怎么用 --force-with-lease 把改动安全地推上去、以及改历史改砸了的时候怎么用 git reflog 找回来。读完应该能照着在测试仓库里完整跑一遍。

起因是前几天在 DEV 上看到一篇整理中级 Git 命令的文章,覆盖 add、commit、push 之外的那一层,merge、rebase、amend、stash、cherry-pick 一大串。作者说这套东西最早是她做初级开发时跟自己的技术主管学的,后来又原样教给了团队里的初级和中级同事。这个传递链条我挺有共鸣——我收藏夹里这类清单少说十来篇,真在关键时刻被我掏出来用过的就两三条,所以这次只挑了跟手上这个坑直接相关的一条线拆开跑,其他命令先放着。

先拆开看看为什么会被拒。amend 这个名字很有迷惑性,我一开始也以为它是原地修改上一个提交,实际发生的事是:Git 造了一个全新的提交,内容加上你补的改动,然后把分支指针挪过去。新提交等于新哈希 → 本地历史和远程历史从分叉点开始不是一条线了 → push 判定为 non-fast-forward,直接拒绝。这个拒绝是 Git 在拦你,不是网络或者权限的问题。

git commit --amend --no-edit
git push
# ! [rejected]  fix/login -> fix/login (non-fast-forward)

--no-edit 的作用是沿用原来的提交信息,不加的话会弹编辑器让你再确认一遍,看需求。

被拒之后我的固定处理顺序是这样:

  1. 先确认这个分支是不是只有自己在推。这也是那篇文章里我最认同的一条经验法则:自己的本地工作随便 amend、随便 rebase,共享历史上的 rebase 要谨慎——哈希全变之后,同事手里会留一套对不上的旧提交。而且 rebase 是把提交一个个重新应用,冲突可能连着好几个提交反复冒出来,摊到别人身上就更难受了。
  2. 确认只有自己在用之后,用 git push --force-with-lease 推上去。
  3. 如果第 1 步的答案是“共享分支”,到这里就停,别推了。共享分支上的坏提交用 git revert 处理,它会创建一个方向相反的提交把改动抵消掉,原提交还留在历史里,同事 pull 下来整整齐齐,比 reset 加 force push 安全一个量级。

那为什么不直接 git push --force?force 的语义是“不管远程现在长什么样,都给我覆盖成本地的样子”。--force-with-lease 多检查了一层:只有远程分支的状态和你本地的预期一致,也就是这中间没有别人推过新东西,才执行强制推送;一旦发现远程比预期新了,直接拒绝。换句话说,它顶多让你覆盖掉自己一个人的工作,别人推的东西碰不到。我现在的习惯是当 force 这个命令不存在,反正 lease 能覆盖它全部的合法场景。

推完顺手验证一下:

git log --oneline -3
git status

本地和 upstream 对齐、working tree 干净,这一单才算收掉。

然后是 reflog,那篇文章里我第二个真用上的命令,之前另一篇笔记里也提过一嘴,这次算正式用明白了。它记的是 HEAD 这类本地引用的移动历史,你 reset 过、切过分支、rebase 过,每一次挪动都有留痕。最典型的场景是 reset --hard 之后发现扔掉的提交里还有

abanana
abanana

把自己踩过的坑整理成一篇能复现的笔记,写给三个月前的自己看。

查看主页 →