git push 被拒 non-fast-forward 速查:rejected 与强制推送纪律
更新时间:2026-10-24。FAQ 速查页,面向
git push被远端弹回、报! [rejected]的同学。non-fast-forward 的含义一句话:远端分支上有你本地没有的提交,直接推会把别人的提交顶丢,Git 拒绝当这个凶手。收敛在六根轴上:同事先推了新提交、你 rebase/amend 改写了已推送的历史、推错了远端或分支、分支受保护禁强推、fetch 之后忘了合、以及 tag 与分支同名撞车。本篇的主旋律是「先合再推,强推是例外」,--force什么时候能用、用哪一款,逐条说清。认证都过不去(Authentication failed)的同学请转 Git 认证失败速查,本篇假设你已经能连上服务端。
本文要回答的问题
non-fast-forward到底在保护什么,为什么「先 pull 再 push」能治本?- rebase / amend 之后 push 必被拒,这时候该走哪条路?
--force和--force-with-lease差在哪,为什么后者是纪律?pre-receive hook declined与 non-fast-forward 是同一种拒绝吗?- 怎么在 push 之前就知道这一把会不会被拒?
一、报错原文与典型现象
push 被拒的几种真实措辞如下(主机名以 git.example.com 占位,路径以 /proj/demo 占位):
$ git -C /proj/demo push origin master
To git.example.com:zhangsan/demo.git
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'git.example.com:zhangsan/demo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.$ git -C /proj/demo push --force origin master
! [rejected] master -> master (stale info)
error: failed to push some refs to 'git.example.com:zhangsan/demo.git'$ git -C /proj/demo push origin master
remote: error: GH006: Protected branch update failed for refs/heads/master.
remote: error: Cannot force-push to this branch
! [remote rejected] master -> master (pre-receive hook declined)读报错抓三个信号:括号里的理由(non-fast-forward / stale info / pre-receive hook declined)决定走哪条处置分支;hint 段落是 Git 给你的标准答案,先照做再深究;[remote rejected] 开头的是服务端钩子在拦,本地怎么合都没用,要去查分支保护规则。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| 第一次 push 就 non-fast-forward | 远端建仓时带了 README,本地是全新历史 | git log origin/master 有别人的初始提交 | git pull --rebase origin master 后推 |
| 团队分支上 push 被拒 | 同事先推了新提交,你的基点落后 | git fetch 后 git status 显示 behind N | git pull --rebase 合入后再推 |
| rebase / amend 后 push 必拒 | 历史被改写,与远端公开历史分岔 | git log --oneline --graph 看分岔 | 个人分支 --force-with-lease;共享分支禁 |
| 强推报 stale info | 你 fetch 之后远端又变了 | 重新 git fetch 对比 | 再 fetch 确认无人动过,重发 force-with-lease |
| remote rejected / hook declined | 受保护分支禁推或禁强推 | 平台分支保护设置页 | 提 MR/PR 走评审,或找管理员放行 |
| push 错地方被拒 | 推错了远端或分支名 | git remote -v;git branch --show-current | 核对后按 远端 本地:远端 明确推 |
| fetch 完立刻推还是被拒 | fetch 只下载不合并,远端新提交没进你的历史 | git status 仍 behind | 还差 merge/rebase 这一步 |
三、排查决策图
下面这张图是标准排查路线,颜色按角色区分:红是报错入口与命中的根因,蓝是排查动作,绿是通过分支,橙是修复动作,紫是防复发沉淀。
读这张图抓住唯一的分岔口:git fetch 之后画图看历史,分岔是「远端多了别人的提交」就走 rebase 合入这条正道;分岔是「我自己把已推送的提交改写了」才轮到 force-with-lease 这条险道。两条路处理的是同一句报错,用错路就是丢同事提交的事故现场。
四、逐条排查展开
第一条:最常见的剧本——远端有你没拉过的提交。 同事推了代码、或者你在网页端改了 README、或者建仓时平台自动生成了初始提交,你的本地基点就「落后」了。push 时 Git 发现你的历史不能快进到远端的尖端,直接拒。标准处置:
git fetch origin
git status # 看 behind origin/master by N
git pull --rebase origin master # 把你的提交重放到远端最新之上
git push origin master用 --rebase 而不是默认 merge,是为了不制造「Merge branch master」这种噪音提交;冲突解决、rebase 语义见 rebase、cherry-pick、stash。
第二条:改写了已推送的历史。 git commit --amend、git rebase -i 改提交信息、压提交,只要这些提交已经推过远端,你的本地历史和远端就是两条分岔的线——此时 pull --rebase 会把旧提交又拉回来,越合越乱。正确姿势是确认这个分支只有你一个人在用,然后:
git push --force-with-lease origin feature-x第三条:force-with-lease 的纪律。 --force 是盲覆盖:远端现在是什么都不管,拿你的本地顶上去。--force-with-lease 加了一道保险:只有你本地记录的远端状态(上次 fetch 到的)与服务器当前一致才推,期间有人推了新东西就报 stale info 拒推——这正是保险在工作,重新 fetch 看清楚再决定,千万别看到拒推就换 --force 硬来,那等于亲手拆掉保险丝。纪律三条:共享分支(master/main、develop)永不强推;强推前在群里吱一声;一律用 with-lease 不用裸 force。
第四条:受保护分支是另一档拒绝。 [remote rejected] ... (pre-receive hook declined) 不是快进问题,是服务端规则:master 禁直接推、禁强推、必须走 MR/PR。本地怎么合都没用,去平台看分支保护设置,或者把改动推到特性分支再提合并请求。GitLab/GitHub 的默认主干现在基本都带这层保护。
第五条:推错目标的乌龙。 git remote -v 核对远端是不是你以为的那个(fork 场景里 origin 指着自己仓库、upstream 才是上游);git branch --show-current 核对本地分支。要推的分支名和远端不一致时显式写:git push origin 本地名:远端名。remote 配置细节见 remote、pull、push 用法。
第六条:fetch 不等于合入。 有同学跑完 git fetch 立刻 push,还是被拒——fetch 只是把远端新提交下载到本地的 origin/master 引用上,你的 master 一步没动,历史照样分岔。fetch 之后必须接 merge 或 rebase,合完再推。想提前知道这一把会不会被拒,可以在推前跑 git push --dry-run,被拒的推演不会弄脏任何东西。想把被顶乱的历史捋回去,见 reset、revert、restore 三件套 与 reflog 找回。
第七条:tag 推送的同名撞车。 git push origin v1.0 时如果远端已有一个同名但指向不同提交的 tag,同样报 ! [rejected] v1.0 -> v1.0 (already exists)。tag 的设计语义是不可移动的路标,正确做法是删本地 tag 改指向前先在团队内确认,远端 tag 要用 git push origin :refs/tags/v1.0 显式删除再重推——这套动作影响面与强推分支同级,纪律也同级。
第八条:pull 默认行为未定导致的半吊子合并。 新版 Git 在 git pull 未配置策略时会打印一大段「You have divergent branches」并拒绝行动。这其实是 Git 在逼你表态:配置一次 git config --global pull.rebase true(或个人偏好的 merge),之后 pull 的行为就确定了。这不是故障,但新手常把它当成报错卡住,顺手配掉即可。
第九条:force-with-lease 也有盲区。 它校验的是「你本地记录的远端引用」与服务器是否一致,如果你在另一个终端里 fetch 过、或者 IDE 后台自动 fetch 了,本地记录就悄悄更新了——此时 with-lease 也拦不住覆盖别人的提交。所以纪律的最后一环永远是「推之前自己亲手 fetch 一次并看一眼新增了什么」,工具只是兜底,眼睛才是最后一道把关。
五、规范做法与预防
- 推前必 fetch:
git fetch && git status看一眼 behind,落后就先 rebase 再推,把拒绝消灭在本地。 - 已推送的提交不改写:amend/rebase -i 只用在还没 push 的提交上;push 过的错提交用
git revert反做,不改历史。 - 强推三件套:只推个人特性分支、只用
--force-with-lease、推前吱一声。 - 共享分支上保护:master/main 开分支保护,从制度上杜绝「一失手全队加班」。
- dry-run 当彩排:重要推送前
git push --dry-run先推演一遍。
这一篇在知识体系里的位置
本篇是 Git 排错速查簇的推送篇,定位「连得上但推不进」:认证环节就失败看 Git 认证失败速查;连仓库都认不出看 not a git repository 报错速查;pull --rebase 的冲突解决与变基语义看 rebase、cherry-pick、stash;远端与分支的日常操作看 remote、pull、push 用法;分支合并策略看 branch 与 merge;推送搞砸后的找回手段看 reset、revert、restore 与 tag、bisect、reflog。
一句话总结
! [rejected] non-fast-forward 先 git fetch + git log --graph 看分岔来源:远端多了别人的提交就走正道 git pull --rebase 合入再推;是自己 amend/rebase 改写了已推送历史才走强推,且只在个人分支上用 --force-with-lease(报 stale info 是保险在工作,重新 fetch 看清再说,绝不换裸 --force);[remote rejected] (pre-receive hook declined) 是分支保护在拦,提 MR/PR 走评审。推前必 fetch、已推送的提交用 revert 不改写、共享分支开保护,这个报错基本绝迹。