☰
Git推送被拒怎么解决:从Push rejected到五种成因与实操排查
2026/10/26 3:47:43 网站建设 项目流程

“Push rejected: Push to origin/master was rejected”——这句话我第一次撞见的时候,正对着一个刚建好的仓库发愣。网页上仓库看起来是空的(至少我以为它是空的),本地代码提交得干干净净,点一下推送,弹窗甩过来这么一行,没有堆栈、没有行号、没有原因,只配一个“Push rejected”的红字标题。后来我才搞明白,这条提示根本不是 Git 本身在说话,而是开发工具里的 Git 集成插件把命令行返回的一屏信息压缩成了一句几乎等于没说的话。

这个报错的本质,是远程仓库拒绝了你的这次推送请求,它拒绝的理由可能有好几种,而弹窗只告诉了你结果,把原因藏了。所以真正要解决的不是“怎么把这个弹窗关掉”,而是“怎么让命令行把真实原因吐出来,再对症下药”。这篇内容我会把这条报错背后最常见的五种成因、四条解决路线、以及权限和平台侧设置的排查方法全部摊开讲一遍,每一步都给到可以直接复制的命令和判断依据。适合两类人看:一类是刚把本地代码往远程仓库推、被这个弹窗卡住的新手;另一类是团队协作里偶尔遇到推不上去、想搞清楚“到底是谁把历史改乱了”的老手。前面的部分我会讲清原理,后面的部分全是实操,赶时间的话可以直接跳到第 3 章照着做。

1. 这条报错到底是谁发出来的

1.1 弹窗式提示与命令行输出的差距

先用一句话把这个报错“归案”:Push rejected这个字面格式,绝大多数情况下不是git push原生命令的输出,而是集成开发环境的 Git 插件自己拼的提示。原生命令行在推送失败时会打印一长串信息,典型长这样:

! [rejected] master -> master (fetch first) error: failed to push some refs to 'https://example.com/xxx/yyy.git' hint: Updates were rejected because the remote contains work that you do hint: not have locally. This is usually caused by another repository pushing hint: to the same ref. You may want to first integrate the remote changes hint: (e.g., 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.

对比一下就能看出来差别有多大:命令行告诉你的是fetch first(先去抓取)、non-fast-forward(不是快进)、remote contains work that you do not have locally(远程有你本地没有的提交),每一条都直接指向一个具体原因;而弹窗只留下一个Push rejected,等于把病历撕了只给你留一句“你不舒服”。

所以处理这个问题的第一个动作,永远是关掉弹窗,打开真实终端。命令行在哪儿?如果用的是图形化开发工具,一般底部或侧边有 Terminal / 终端面板;如果是独立的 Git 客户端,找“打开命令行 / Open in Terminal”之类的入口。进去之后cd到项目根目录,确认pwd显示的是项目路径、ls能看到.git目录,然后手动敲一次:

git push -u origin master

让真实错误浮出水面。这一步看起来像废话,但我见过太多人对着弹窗猜了半天,最后发现真实原因是“远程分支受保护”而不是“历史冲突”,方向从一开始就错了。

提示:如果你连git命令都提示找不到,那问题比推送失败更靠前——命令行工具没装或者没加进环境变量。这种情况下弹窗能用只是因为开发工具自带了内置的 Git 运行时,而系统 PATH 里没有。先把这件事解决,再谈推送。

1.2 Git 拒绝推送的三类硬性条件

要理解“为什么被拒”,得先知道 Git 在推送这一刻做了什么检查。可以把它想象成快递站收件:不是你把包裹递过去就一定能寄,得同时满足几个条件。

第一类是历史关系的检查,也就是最常见的“非快进”判断。Git 的推送默认只接受“快进”(fast-forward)更新:远程分支的最新提交,必须是你本地分支的祖先。换句话说,远程那条线得是你这条线的过去,你只是在它后面接着写。如果你本地的提交和远程的提交是两条并行的、谁也不包含谁的线,Git 就会拒绝覆盖,因为一旦接受,远程上那些你没见过的提交就会“消失”——在 Git 看来这等于丢数据。

这个机制背后的逻辑很朴素:Git 假设“别人推上去的东西是有价值的,你本地没看到是你的问题,不是它的问题”。所以它宁可拦下来让你先看看,也不愿意默默帮你把别人的提交抹掉。理解了这一点,你就明白为什么--force能绕过——它是你明确告诉 Git“我知道会丢东西,我承担”。

第二类是权限与规则检查,发生在服务端。本地 Git 只做历史判断,真正决定“你能不能往这个分支写”的是远端仓库。受保护的分支、只读的成员角色、没通过邮箱验证的账号、写满了配额的空间,都会在服务端被拦下,返回的信息一般带pre-receive hook declined或者hook declined之类的字样。这类拒绝和历史无关,你把本地历史整理得再漂亮也没用。

第三类是内容本身的合规检查。单个文件太大、仓库总体积超过配额、被平台的敏感内容规则拦下,也会表现为推送失败。这类错误信息通常比较直白,会告诉你哪个文件超限、限制是多少。

三类检查的顺序大致是:客户端先做历史判断 → 通过后传给服务端 → 服务端做权限和内容校验。所以看到报错时,先分辨它属于哪一类,能省掉一大半无效操作。

1.3 为什么“第一次推送”几乎必然撞上它

有个现象值得单独说:这个报错在“刚建仓库、第一次推送”这个场景里出现频率高得离谱。原因不复杂——建仓库的时候,几乎所有代码托管平台都会问你要不要顺手初始化几个文件:说明文档、忽略规则、开源许可证。很多人图省事勾了,于是远程仓库创建完成的瞬间就已经有了一个提交。

而本地这边,你大概率是git init自己新建的仓库,也有一到几个提交。两条历史各自独立诞生,没有任何共同祖先,推到一起自然被判“非快进”。

这就是那个经典悖论的来源:你以为仓库是空的,其实它不空;你以为自己是第一个提交者,其实远程已经有人(平台)先提交了。所以下一章我们先把这类场景彻底拆开,看清楚每一条分支上到底有什么。

2. 五种高频成因的逐一拆解

2.1 远程仓库建的时候勾了初始化文件

这是占比最高的一种。判断方法特别简单,在项目目录下执行:

git fetch origin git log --oneline --graph --all --decorate -20

--all会把远程追踪分支也画进来。如果输出里出现两条互不相连的线(一条是你的master,一条是origin/master),中间没有交汇点,那基本可以确认:两条历史无关。

再确认一下远程到底有什么:

git log --oneline origin/master

如果只看到一两条提交,标题类似“Initial commit”,附带说明文档、忽略规则之类,那就完全对上了——远程只有平台自动生成的初始提交,你本地才是真正的业务代码。

这种情况下最省事的处理是把远程那个初始提交合并进来,因为那些文件(忽略规则、许可证)通常是有用的,直接丢掉反而可惜。具体操作放在 3.2 节,这里先记住结论:这属于“可以和平解决”的类型,不需要强制推送。

有一点容易忽略:远程的这些初始化文件经常和你本地的同名文件冲突。比如你本地已经手写了一份忽略规则,远程也有一份,合并时就会撞车。撞车不可怕,怕的是有人一看冲突就慌了直接git merge --abort然后跑来问“为什么还是推不上去”。冲突的处理原则后面会讲。

2.2 两条历史根本没有共同祖先

和上一条相关但不等价。远程有初始提交是一回事,“两条历史完全没有共同祖先”是另一回事,这个判断决定了你能不能用普通合并。

Git 在合并两个分支时会先找共同祖先(merge base)。如果找不到,从 2.9 版本开始,git merge会直接拒绝并提示:

fatal: refusing to merge unrelated histories

这句话的意思是“我拒绝合并两段互不相干的历史”。这个保护是有道理的——想象两本从第一页开始各写各的日记,你要把它们装订成一本,得先说清楚哪本在前、哪本在后,不能靠机器瞎猜。

对应的解法就是给合并动作加一个明确的授权参数:

git pull origin master --allow-unrelated-histories

这个参数相当于你拍着胸脯跟 Git 说“我知道这两段历史没关系,我就是要合,出事我负责”。加上之后 Git 才会真的去比对文件、生成合并提交。

需要提醒的是,这个参数是 Git 2.9 之后才有的。如果机器上的 Git 版本比较老(有些系统自带的版本确实偏旧),敲上去会报unknown option。先git --version看一眼,低于 2.9 就考虑升级,或者改用“重新克隆再搬文件”的路线绕过去。

2.3 分支保护、成员权限与邮箱验证

这一类跟历史一点关系都没有,但报错长得差不多,特别容易误判。典型特征是:命令行输出里出现pre-receive hook declined、You are not allowed to push code to protected branches on this project、permission denied这类字样,而不是non-fast-forward或fetch first。

分支保护是比较常见的坑。很多平台在仓库创建时会默认把主干分支设为受保护状态,规则通常是:不允许直接推送,只能通过合并请求合入;或者只允许特定角色直接推送。如果你是仓库的普通成员而不是管理员,直接git push就会被服务端拦下来。

判断方法:登录网页端,进仓库的设置页面,找分支管理 / 保护分支相关的配置,看主干分支是不是被勾了保护。是的话有两条路——要么让管理员临时放开直接推送权限,要么老老实实走“推一个功能分支 + 提合并请求”的流程。

成员权限是另一层。有些平台的仓库角色分得很细:访客只能看、报告者能提问题、开发者能推非保护分支、管理员啥都能干。如果你拿到的角色不够,推什么都白搭。

还有一个特别隐蔽的原因:账号邮箱没验证。有些平台要求账号完成邮箱验证之后才允许推送代码,未验证状态下所有推送都会被拒。这个提示往往藏在网页端的消息中心里,命令行看不出来。遇到“明明权限没问题、历史也没问题,就是推不上去”的情况,去账号设置页面扫一眼验证状态,能省掉半天排查。

2.4 体积超限与大文件拦截

这一类错误的提示通常比较明确,会出现exceeds、too large、file size limit之类的词,但很多人看到一堆英文就条件反射地当成“历史冲突”处理,白折腾。

代码托管平台对仓库体积普遍有约束,维度一般有三个:单个文件大小、单次推送的总体积、仓库累计体积。具体阈值各平台不一样,经验上单文件的上限普遍在几十兆到一百兆这个量级,仓库总量则在一到两个G左右浮动——具体数字请以平台当前的文档为准,这类限制会调整,别拿旧教程里的数字当圣旨。

真正麻烦的不是“超了”,而是“超了之后怎么退回去”。因为一旦那个大文件进了历史,你光把它删掉再提交是没用的——历史里那个对象还在,仓库体积不会降。这时候要么用历史重写工具把它从所有提交里剔除,要么干脆建个新仓库重新开始。

历史重写是有破坏性的动作,会把所有提交的哈希全部改掉,团队里其他人拉下来会全线冲突。所以我的建议是:动手之前先全员打招呼,约定一个时间点统一操作,别一个人闷头改完然后让大家集体懵圈。

顺带提一句大文件存储(LFS)。如果项目确实需要版本化管理二进制大文件(设计稿、音视频素材、模型权重),正确的做法是接入平台提供的大文件存储能力,而不是硬往普通 Git 对象里塞。接入没配好时,推送也会被服务端拒掉,报错通常和钩子有关。

2.5 凭证缓存与身份信息错配

这一类属于“环境问题”,特点是:命令没错、历史没错、权限看起来也没错,但就是推不上去,或者推上去的提交作者信息诡异。

最常见的表现是身份验证失败,报Authentication failed、403、Permission denied (publickey)之类。原因可能是:本地缓存的旧账号密码还在生效、换了账号但凭据没更新、SSH 密钥没加到平台、密钥加错了账号。

Windows 上尤其容易踩这个坑,因为系统有凭据管理器会记住上一次的账号密码。切换账号之后,命令行还在用旧凭据,表现就是“我明明有权限啊”。处理办法是打开系统的凭据管理界面,把和代码托管相关的旧条目全删掉,下次推送时会重新询问。

另一种是提交身份没配。第一次用新机器时如果没设过用户名和邮箱,提交时 Git 会提示:

Author identity unknown *** Please tell me who you are.

虽然这不是推送报错,但很多人是在“准备提交”这一步卡住的,顺手一起解决:

git config --global user.name "你的名字" git config --global user.email "你的邮箱"

邮箱建议和平台账号绑定的邮箱保持一致,否则提交记录在网页端可能关联不到你的头像和昵称,看着像匿名提交。团队协作里这种情况会被 code review 的同事念叨很久。

3. 从复现到解决的完整实操

3.1 第一步永远是打开真实终端

我把这一步单独拎出来讲,是因为它决定了后面所有排查的效率。流程固定为四步:

# 1. 确认在项目根目录 pwd ls -a # 应该能看到 .git # 2. 确认远程地址对不对 git remote -v # 3. 确认当前分支和它的追踪关系 git branch -vv # 4. 抓取远程最新状态后看真实报错 git fetch origin git push -u origin master

第三步的git branch -vv很有用,它会显示每个本地分支追踪的远程分支名。常见错误是本地分支叫main、远程叫master,或者远程地址配成了另一个仓库。远程地址不对的话,你推的其实是别人家的仓库,报什么错都有可能。

如果git push报的是src refspec master does not match any,那说明本地压根没有master分支——要么还没提交过(git log是空的),要么当前分支叫别的名字。先git log --oneline看有没有提交,再git branch看分支名,对上了再推。

这一步跑完,你手上就有真实报错信息了。接下来的四节对应四种典型情况,对号入座即可。

3.2 场景一:合并远程初始提交(推荐路径)

适用判断:git log --oneline --graph --all显示两条互不相连的线,远程那条只有平台自动生成的初始化提交。

这是最推荐的路线,因为它不丢任何东西,历史也保持完整(会多出一个合并提交)。操作如下:

# 抓取远程 git fetch origin # 合并远程到本地当前分支,明确允许无关历史 git merge origin/master --allow-unrelated-histories # 如果有冲突,处理完冲突文件后 git add <冲突文件> git commit -m "merge: 合并远程初始化提交" # 推送 git push -u origin master

如果你习惯用 pull 一步到位,等价写法是:

git pull origin master --allow-unrelated-histories

注意这里的顺序:先合并、再推送。我见过有人反过来操作——先试推送失败,然后想着“那我先提交一次再推试试”,结果是历史越堆越乱,冲突面越滚越大。

冲突处理是这个路线里唯一需要动脑的地方。执行合并后如果提示冲突,先git status看哪些文件是both modified,打开逐个看。最常见的冲突文件是忽略规则文件和说明文档:两边都写了内容,Git 会把两段内容用分隔符标出来,你手工挑要保留的部分,删掉分隔符,保存,然后git add。

注意:冲突文件的三个标记(<<<<<<<、=======、>>>>>>>)必须全部删干净,一个残留都会导致文件内容出问题,而且git add不会帮你检查这个。养成习惯:git add之前先全文搜一遍这几个符号。

合并完成后,git log --oneline --graph应该能看到一个分叉再汇合的形状,这就是正确的合并提交。推到远程之后,网页端的提交图也会显示同样的形状,看着很清爽。

3.3 场景二:用变基把本地提交“接”到远程之上

适用判断:和场景一相同,但你不想要那个合并提交,希望历史是一条直线。

变基(rebase)的思路是:把你本地的提交先“摘下来”,挪到远程最新的提交之后,再一个个“贴回去”。最终效果是历史线性、没有分叉,但代价是本地提交的哈希全变了。

git fetch origin # 把本地提交接到 origin/master 之后 git rebase origin/master # 如果中途有冲突 git add <冲突文件> git rebase --continue # 如果搞砸了想放弃 git rebase --abort # 全部完成后再推 git push -u origin master

有个细节值得说明:变基在“两条历史无共同祖先”的情况下通常不会像 merge 那样报unrelated histories,因为它不检查共同祖先,只是简单地把提交逐个重放。这也是为什么很多人发现“merge 不行但 rebase 可以”。不过重放过程中遇到冲突的概率会更高,尤其是两边都改过同一个文件时。

变基值不值得用,我的看法是看团队习惯。个人项目、单人开发,线性历史读起来确实舒服,git log一路往下很清晰。多人协作的项目,如果你已经把提交推给别人了,再在本地上变基重写它们,就会给别人制造麻烦——他们拉下来的历史和远程对不上,又得来一轮合并。这种情况下老老实实用 merge 更省事。

还有个容易忽略的点:如果本地提交比较多(比如十几个),变基过程中可能连续冲突好几次。每解决一个冲突git rebase --continue,下一个还可能冲突。这不是出错,是正常流程,耐心走完就行。真扛不住就--abort退回去换合并路线,没什么丢人的。

3.4 场景三:确定远程可丢弃时的强制推送

适用判断:远程那个提交你确实一点都不要,比如远程是你误操作建的仓库、里面的初始化文件你压根不需要、或者这是你个人的测试仓库。

这种情况下最直接的做法是强制推送,用本地历史直接覆盖远程:

git push -f origin master

但我强烈建议用下面这个更安全的变体:

git fetch origin git push --force-with-lease origin master

--force-with-lease的意思是“强制覆盖,但如果远程在我上次抓取之后又被别人动过,就拒绝”。它比裸的-f多了一道保险:万一在你操作期间有同事推了新提交,-f会把他的提交直接抹掉而且不打招呼,--force-with-lease则会拦下来告诉你“远程变了,你先看看”。

注意这个参数依赖本地记录的远程状态,所以前面那句git fetch origin不能省。有人直接敲--force-with-lease没反应,就是因为本地记录的远程状态已经过期了。

强制推送之前有一个动作我每次都做,也建议你养成习惯:先在本地给当前分支打个备份。

git branch backup-before-force-$(date +%Y%m%d)

或者更简单,git tag backup-20240101。这样万一推完发现推错了、想找回来,本地还有一份完整的历史,能从 reflog 或备份分支里捞。没有备份就强推,出了事只能靠git reflog在本地找,而 reflog 是有保留期限的,过期就真没救了。

注意:强制推送在多人协作的公共分支上是绝对的禁忌。如果那个分支已经有别人在基于它工作,你强推一次,所有人下次拉取都会遇到历史分叉,有的还得手工重置,代价远大于你省下的那点操作步骤。用之前先问自己一句:这个仓库除了我还有别人在用吗?

3.5 场景四:推倒重来,重新克隆再搬文件

适用判断:本地仓库的历史已经乱到自己都不想看了,或者折腾了几轮失败之后,宁可从头来过。

这条路线看着最“土”,但它是所有方案里成功率最高、理解成本最低的一条。步骤是:

# 1. 把当前项目目录备份(关键!别跳过) # 直接复制整个文件夹到别处,命名成 xxx-backup # 2. 换个位置重新克隆 git clone <仓库地址> fresh-copy cd fresh-copy # 3. 从备份目录里把业务文件拷过来 # 注意:不要拷 .git 目录,那个是罪魁祸首 # 4. 检查拷过来的文件,确认没有多余的临时文件 git status # 5. 提交并推送 git add . git commit -m "feat: 迁移本地代码到远程仓库" git push -u origin master

这条路线的好处是:克隆下来的仓库和远程历史天然一致,不存在“无关历史”的问题;坏处是本地原来的提交历史全没了(如果那些提交信息对你有价值,可以用git log导出一份文本存档)。

实际执行时有两个坑要避开。第一个坑是拷贝文件时把.git目录也带过去了——有人整个文件夹一拖,结果新目录里的.git还是老的,等于白折腾。第二个坑是把编译产物、依赖缓存、日志文件一并拷过去,git status一跑几百个未跟踪文件,看着就头疼。正确的做法是先看一眼原目录的忽略规则文件,照着配一份,或者干脆拷之前手工筛一遍。

3.6 四条路线的对比与选择依据

路线多了容易选择困难,所以我把四种方案摆在一起对比一下,按“我遇到这种情况会选哪个”来排。

路线操作成本历史形态是否丢数据适用场景
合并远程(merge)低,需处理冲突保留分叉,多一个合并提交不丢远程初始化文件有用、多人协作
变基(rebase)中,冲突可能多次线性,无分叉本地历史哈希改变个人项目、追求整洁历史
强制推送(force-with-lease)低完全以本地为准远程提交被覆盖远程内容明确可丢弃、个人仓库
重新克隆再搬中,需手工挑文件干净本地历史丢失历史已乱到无法收拾

选择依据其实就三条:远程的东西还要不要、本地历史的价值有多大、有没有别人在同一个分支上工作。三条里只要“有别人在”这条为真,前两条的答案基本就定了——走合并或者走功能分支,别碰强制推送。

4. 权限与平台侧设置的落地处理

4.1 保护分支怎么改

如果排除了历史冲突、确认报错里带的是权限类字样,就得去网页端处理。路径大同小异:进仓库 → 设置 → 分支管理 / 保护分支,找到主干分支那一行。

常见的保护项有三档:完全禁止推送、允许特定角色推送、允许所有成员推送。日常开发最实用的是“允许特定角色推送”,把主干分支的直推权限收给少数人,其他人走功能分支 + 合并请求。

如果你只是想把眼前的活干完,最快的做法是让有权限的同事临时放开直推,推完再恢复。如果你是有权限的那个人,改完之后记得提醒团队成员——很多人改完设置自己推成功了,别人还在原地报错。

还有一种情况是仓库本身设成了只读。这种情况下任何人都推不上去,页面上通常会有提示。遇到“所有人都推不上去”的现象,先去看仓库状态而不是查本地环境。

4.2 访问令牌与 SSH 密钥的取舍

身份验证这块,现在绝大多数托管平台都不建议直接用账号密码做命令行认证了,主流做法有两种:访问令牌(token)和 SSH 密钥。

访问令牌的好处是配置简单,当密码用,可以随时吊销、可以限定权限范围。坏处是明文存在本地凭据里,一旦泄露影响面比较大。SSH 密钥的好处是不用每次输,安全性也更好,坏处是首次配置稍麻烦,需要生成密钥对、把公钥传到平台、验证连通性。

命令行下用令牌的方式很简单,推送时提示输入密码,把令牌粘进去就行。如果嫌每次都输麻烦,可以让凭据管理器记住,但记得定期轮换令牌。

SSH 方式的标准流程:

# 生成密钥对(一路回车,或设置口令) ssh-keygen -t ed25519 -C "你的邮箱" # 查看公钥内容,复制到平台的 SSH 公钥设置页 cat ~/.ssh/id_ed25519.pub # 验证连通性 ssh -T git@<平台域名>

看到欢迎信息就说明通了。如果提示Permission denied (publickey),先确认公钥真的贴上去了、贴的是公钥而不是私钥,再确认用的是哪个密钥(有多把密钥时通过~/.ssh/config指定)。

远程地址也得跟着换,SSH 和 HTTPS 是两套地址:

git remote set-url origin git@<平台域名>:<用户名>/<仓库名>.git git remote -v # 改完确认一下

4.3 推送之后的验证动作

推送成功不等于万事大吉。我会做三件事确认这次的活真的干完了。

第一件,看推送结果的分支追踪是否建立:

git branch -vv

输出里应该能看到本地分支后面跟着[origin/master]和一个 ahead/behind 标记。如果显示ahead 0,说明本地和远程完全同步了。

第二件,确认远程的提交列表里能看到你刚才的提交:

git log --oneline origin/master -5

第三件,去网页端刷一下仓库首页,看提交记录、文件内容、分支状态对不对。有些平台的钩子会自动跑构建或检查,如果这些流程失败,本地是看不到的,只有网页端才有提示。

5. 报错速查表与踩坑记录

5.1 常见报错对照表

把我这些年遇到的、和这个报错相关的输出整理成一张表,按报错关键字对号入座,比一个个试快得多。

命令行的关键输出真实原因处理动作
fetch first/non-fast-forward远程有你本地没有的提交git pull或git fetch后合并
refusing to merge unrelated histories两条历史无共同祖先合并时加--allow-unrelated-histories
pre-receive hook declined服务端规则拦截,多为保护分支查分支保护设置或换功能分支
You are not allowed to push成员权限不足找管理员提权限
exceeds ... size limit文件或仓库体积超限清理大文件或改用大文件存储
Authentication failed/403凭据失效或权限不足清凭据缓存、重配令牌或密钥
src refspec master does not match any本地没有该分支或没有提交检查git branch和git log
Please tell me who you are未配置提交身份设置user.name和user.email
Permission denied (publickey)SSH 密钥未配置或未上传生成密钥并贴到平台

这张表我建议存一份在笔记里。下次再遇到,先看关键字,别凭感觉猜——凭感觉猜的命中率大概和我闭着眼改代码差不多。

5.2 强推之前必须做的三件事

这三件事我每次强推都做,做了十几年只后悔过没做的那几次。

第一,本地打备份。建一个临时分支或者 tag 指向当前 HEAD,成本两秒钟,收益是万一推错了还能找回来。

第二,确认没有别人在用这个分支。去网页端看最近的提交记录和活跃分支列表,看看有没有别人的提交。有的话就别强推,换成合并。这一条比上一条重要一百倍,因为本地备份救不了别人的工作。

第三,用--force-with-lease而不是裸的-f。多打的这十几个字符,能在关键时刻拦住一次误覆盖。

还有一个细节:强推会重写远程历史,如果你之前已经在远程打了 tag 或者开了合并请求,这些引用可能失效。推完之后顺手去网页端看一眼相关页面还正不正常,比事后被人发现要好。

5.3 几个容易误判的细节

换行符导致的全文件改动。这个问题在 Windows 和 Unix 混合的团队里特别常见。表现是:明明只改了一行代码,git status显示整个文件都变了。原因是换行符在提交和检出时被自动转换。处理方式是在项目里放一份属性配置文件固定换行规则,或者干脆统一让所有人用同一套配置。这个不解决,合并时冲突面会大到让人想砸键盘。

文件名大小写改不动。Windows 文件系统不区分大小写,把readme.md改成README.md,Git 可能压根检测不到改动。正确做法是用git mv命令重命名,或者先改成一个中间名再改回来。

钩子脚本导致的本地报错。有些团队会在仓库里放客户端钩子,提交前跑代码规范检查。钩子脚本写得不好、或者依赖没装,就会在提交或推送时莫名其妙地失败。排查办法是看.git/hooks目录里有没有启用的脚本,临时改名禁掉再看。

文件的忽略规则冲突。本地有一份忽略规则、远程也有一份,合并时容易撞车。更隐蔽的情况是:某个文件已经被提交进历史了,后来才加进忽略规则,那忽略规则对它不起作用,得先git rm --cached把它从索引里移出去,再提交一次规则变更。

运行环境的 Git 版本过旧。老版本 Git 缺一些新参数,报unknown option。这种情况不是操作错,是工具老。git --version看一眼版本号,太老就升级,别在老工具上硬试新语法。

6. 让这件事不再发生的流程习惯

6.1 建仓顺序比命令更重要

这个报错之所以高频,很大程度上是流程问题而不是技术问题。最省事的顺序只有一句话:先建远程仓库,再克隆到本地,别在本地git init。

克隆下来的仓库天然和远程一致,历史、分支、追踪关系全都是对的,你只需要往里加文件、提交、推送,不会遇到任何“无关历史”的麻烦。

如果一定要在本地init,那建远程仓库的时候就把所有初始化选项全部取消勾选,让它真的是个空仓库。空仓库没有任何提交,你推什么都行,也就不会有非快进的问题。

这两种做法的差别,我打个比方:先克隆就像拿着别人给你开好的户口本去办事,一路畅通;本地 init 再配远程就像自己先写了一本,然后要求把它并进官方的册子里,中间得走一道合并手续。手续本身不复杂,但能省则省。

6.2 团队约定的几条硬规矩

个人项目怎么折腾都行,多人协作就得定点规矩。我们团队现在的约定大概这么几条,都是踩过坑之后总结的。

主干分支只接受合并请求,不接受直接推送。这条拦住的不只是误推,还有一种更隐蔽的情况:有人本地历史乱了,图快直接强推,把别人的提交抹了,第二天所有人都在重拉代码。

每个功能一个分支,分支名带上简短的用途描述。这样做的好处是推送失败时影响面小——你推的是自己的分支,推不上去也不影响别人。

推送之前先拉一次。这个习惯养成之后,能挡掉八成的非快进报错,因为你的本地历史始终跟在远程后面。

删分支之前先确认已合并。远程删了但本地还留着追踪记录的情况很常见,下次推送时这些残留追踪会让人一头雾水。定期git remote prune origin清一下。

6.3 常用命令小抄

最后把这篇里反复用到的命令归拢一下,可以复制到本地笔记里当小抄。

# 看状态 git remote -v # 远程地址 git branch -vv # 本地分支与追踪关系 git log --oneline --graph --all # 全部分支的提交图 # 抓取与合并 git fetch origin # 只抓取不合并 git merge origin/master --allow-unrelated-histories git pull origin master # 抓取并合并 git rebase origin/master # 抓取后变基(先 fetch) # 冲突处理 git status # 看哪些文件冲突 git add <file> git commit -m "merge: xxx" git rebase --continue git rebase --abort # 安全强推 git branch backup-$(date +%Y%m%d) # 先备份 git push --force-with-lease origin master # 身份与凭据 git config --global user.name "名字" git config --global user.email "邮箱" ssh -T git@<平台域名> # 测 SSH 通不通

我个人在实际操作中的体会是:这个报错之所以让人抓狂,不是因为它难,而是因为它把原因藏起来了。只要你养成“先看命令行真实输出、再按关键字对号入座”的习惯,绝大多数情况五分钟内就能定位。真正需要小心的只有一件事——在公共分支上动手之前,先想清楚这个仓库除了你还有没有别人在用。想清楚这一条,剩下的都是命令的事。

最后再分享一个我自己常做的小动作:每次新建仓库、第一次推送之前,先跑一遍git log --oneline --graph --all --decorate。如果输出里只有一条线,放心推;如果有两条,先停下来看看另一条是谁。这个动作花三秒钟,能省掉后面半小时的折腾。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询