Git push rejected 排查:快进规则、远程校验与大文件清理
发布时间:2026/10/26 12:10:45 锦皓数字建站

遇到Push rejected: Push to origin/master was rejected这行提示的时候很多人的第一反应是网络问题、账号问题或者干脆怀疑是托管平台出了故障。我自己第一次撞上它的时候也一样反复重试了七八次最后发现远程仓库里安安静静躺着一个初始化时自动生成的说明文件本地提交历史和它压根没有共同祖先。这条报错的信息量其实很低它只是告诉你“推送这个动作被拒绝了”至于究竟卡在哪一环需要你自己一层层剥开。代码托管平台、Git 客户端、IDE 内置的推送面板三者的提示粒度差别很大某些 IDE 甚至只把服务端返回的那段详细原因折叠在日志窗口的第二屏导致不少人只看到了最上面一行红字。这篇内容会把这条报错拆成可操作的排查路径先讲清楚“被拒绝”的几种技术含义再给出一条从五分钟快速验证到彻底定位的排查链路然后针对每一类成因给出对应的修复命令和适用边界最后聊怎么在日常开发里把这类问题挡在提交之前。不管你是刚学会git push的新手还是带团队做代码评审的老手都能在里面找到能直接抄的内容。1. 这行报错真正的含义被快进规则或服务端挡在门外1.1 客户端提示与服务端提示不是一回事Git 的推送流程里有两道关卡报错来源完全不同但最终呈现给用户的文字经常被压缩成同一句。第一道是客户端的快进检查Git 在真正发出数据之前会先比对本地分支和远程分支的提交历史如果远程分支上有本地没有的提交本地又是在一个旧基点上继续提交的那么推送就不是“快进”fast-forward客户端会直接拒绝标准输出是! [rejected] master - master (non-fast-forward)。第二道是服务端的接收校验数据已经传上去了服务端在pre-receive钩子里检查权限、分支保护规则、仓库体积、文件类型任意一项不满足就整体拒绝返回里会带remote: error:或remote: rejected字样。问题在于很多图形化工具和 IDE 的推送面板只截取第一行展示把non-fast-forward这种关键限定词和后面的remote:详情都塞进了折叠区。你看到的那句Push to origin/master was rejected本质上是“服务端说不行”但不告诉你为什么不行。所以排查的第一步永远是离开 IDE打开一个终端在项目根目录手动执行一次git push。就这一条能把至少一半的人从瞎猜里捞出来。顺带说清楚一个容易被混淆的点non-fast-forward和Push rejected在语义上不是一回事。前者是本地能自己判断出来的、可以预防的冲突后者是远程的判决本地怎么调参数都没用必须去改远程状态或者改提交内容。分不清这两个就会陷入“我明明git pull了为什么还是被拒”的死循环。1.2 为什么“新建仓库后第一次推”最容易撞上我统计过自己经手的问题工单这条报错出现的场景有六成集中在一个时间点刚建好远程仓库把本地已有项目往里推的第一次。原因特别简单——大部分托管平台在创建仓库的页面上默认勾选了“使用说明文件初始化仓库”或者“添加 .gitignore 模板”。只要勾了远程就自动产生一个提交。而本地项目往往已经有好几个月的历史两者属于两条完全独立的时间线Git 找不到共同祖先自然判定为非快进。这里的认知误区在于很多人觉得“远程那个文件我没动过等于没有”但 Git 不看内容只看提交对象构成的有向图。两条没有交点的历史线合并时就等于要把两份互不相干的东西强行捏在一起Git 出于安全默认不干。理解这一点之后你就明白为什么“先把远程那个文件删了再推”这种土办法有时候管用——它绕过了合并但也顺手丢掉了远程那条历史如果那个仓库别人也在用你就把别人的东西一起抹了。还有一个小众但同样常见的场景本地仓库是从另一个项目复制过来的.git目录一起带过来了。这种情况下本地历史里可能藏着完全无关的提交记录和一堆陈旧的 remote 地址推上去被拒绝在意料之中。判断方法很简单git remote -v看一眼指向git log --oneline | head看最早几条提交是不是你自己的心里就有数了。1.3 拒绝原因的分类速查在动手之前先用一张表把可能性收窄。下面这张表是我按实际遇到的频次排的从上到下依次递减。你可以对照“现象”一栏找到离你最近的那条直接跳到后面第 3 章的对应环节去验证。排序成因典型现象验证命令1远程已有提交历史无交集首次推送被拒pull提示refusing to merge unrelated historiesgit fetch origin git log --oneline origin/master2目标分支受保护报错含protected branch、not allowed to pushgit push --verbose看remote:行3单文件或仓库体积超限报错含exceeds、too large、size limitgit count-objects -vH与大文件扫描4认证账号无写权限提示403、permission denied、提示输入密码git remote -v核对协议与地址5分支名不一致master 与 main提示远程分支不存在或推到了意料之外的分支git branch -vv、git ls-remote origin6服务端钩子校验规则报错含自定义文案如提交信息格式、文件后缀读remote:完整输出注意表格里的第 1 和第 5 条经常同时出现。老项目本地分支叫master新仓库默认分支叫main于是git push origin master会先在远程创建一个新分支如果恰好触发了保护规则或者触发了一次不该有的推送报错信息就会变得很绕。2. 一条能复现的排查链路从五分钟验证到彻底定位2.1 第一轮远程到底是空的还是非空的这是信息量最大、成本最低的一步。先git remote -v确认地址对不对再执行git fetch origin然后看远程有什么。三条命令下来基本就能判定是不是“历史无交集”这个最常见的坑git remote -v git fetch origin git log --oneline --graph --all -20如果git log --oneline origin/master有输出而git log --oneline master的输出里完全没有相同的提交哈希那结论就确定了两条独立的线。此时git merge-base master origin/master会返回空这也是一个非常干脆的判定依据——返回空就代表没有共同祖先。这一步还有个附带收益--graph --all能让你一眼看出本地是不是有多个分支在交叉有时候推送失败是因为你当前所在的分支根本不是你以为的那个。我就遇到过在feature/xxx分支上改了半天代码然后git push origin master的尴尬情况推上去的是 master 分支的旧内容跟工作区改动毫无关系。2.2 第二轮让 push 把真实原因吐出来确认不是空仓库之后就要拿到服务端的原始判决了。有三档手段按需使用# 第一档显示详细过程能看到 remote: 开头的服务端输出 git push --verbose origin master # 第二档协议层追踪排查认证与握手问题 GIT_TRACE1 git push origin master # 第三档HTTP 层追踪排查 403/413 这类状态码 GIT_CURL_VERBOSE1 git push origin master第一档足够解决八成问题。真正有用的信息通常长这样remote: error: GH006: Protected branch update failed或者remote: Resetting origin/master后面跟着一串说文件太大的行又或者remote: [ALERT] Your account is not authorized。看到这些方向立刻就明确了。第三档的输出很啰嗦只在怀疑是传输层问题时才用比如反复提示认证失败但你确信密码是对的——那多半是凭据管理器里缓存了旧账号。如果你用的是 Windows 系统自带的凭据管理可以在系统的“凭据管理器”里找到对应的托管平台条目把它删掉下次推送会重新询问。macOS 上对应的是“钥匙串访问”。这类缓存导致的“我明明换了账号还是被拒”非常常见尤其是共用过一台电脑或者换过公司邮箱之后。2.3 第三轮定位体积与可疑对象服务端返回里一旦出现size、large、exceed这类词就要转到体积排查。先看整个仓库的体量git count-objects -vH输出里的size-pack通常是最接近实际占用的数字。几十兆以内完全正常几百兆就值得怀疑上到 GB 级别基本可以确定历史里混进了不该进的东西——数据集、编译产物、数据库导出、虚拟环境目录这几样是重灾区。接着扫描历史中最大的若干个文件。下面这条命令遍历所有对象按体积倒序取前 20git rev-list --objects --all \ | git cat-file --batch-check%(objecttype) %(objectname) %(objectsize) %(rest) \ | awk /^blob/ {printf %.2f MB\t%s\n, $3/1048576, $4} \ | sort -rn \ | head -20跑出来的结果如果有一堆你根本不认识的路径那多半是某次误提交带进去的。这里要建立一个关键认知只要文件在历史里出现过一次它就一直占着仓库体积哪怕你后来git rm并提交了删除也一样。压缩包会对相似对象做增量存储但大二进制文件通常压不动。所以碰到体积问题光删文件再提交是没用的必须重写历史这部分放到第 5 章细说。2.4 第四轮确认身份、权限与分支指向前三轮都排除完了剩下的就是身份和配置问题。逐项核对git config user.name和git config user.email邮箱跟托管平台账号绑定的邮箱对不上时有些平台会拒绝提交来源。git remote -v看是 HTTPS 还是 SSH。HTTPS 走的是账号密码或访问令牌SSH 走的是密钥对两者的排错路径完全不同。git ls-remote origin这条命令只做只读握手它能成功说明认证没问题失败说明卡在身份这一层。git branch -vv看当前分支有没有上游跟踪推的时候目标分支名是否写错。SSH 方式失败时用ssh -T githost来单独测握手输出通常很明确。如果提示的是密钥未被接受去平台的密钥管理页面确认公钥是不是已经添加、有没有过期、是不是加到了别的账号下。这几种情况我在协作项目里都见过尤其是团队成员换机器之后忘了重新添加公钥。3. 对症下药四种处理策略的取舍与边界3.1 pull --rebase历史没有真实冲突时的首选确认远程多出来的只是初始化文件、说明文档这类无关痛痒的内容最省事的路子是变基git pull --rebase origin master git push origin master变基会把你的本地提交一个个“摘”下来接到远程最新提交的后面最终形成一条线性的历史。相比merge它不会产生一个额外的合并提交历史读起来干净得多。我个人的习惯是长期开着git config --global pull.rebase true这样每次git pull默认就是变基模式省得每次敲参数。但变基有它的代价必须提前知道它会改写提交哈希。如果你的分支已经推给别人、别人在上面继续开发了那你变基之后推送就会再次被拒而且这次是真正的历史分叉。所以判断标准很清晰只在还没推送成功、只有你自己在用的分支上做变基已经共享出去的分支老老实实用merge。还有一种情况是变基过程中出现冲突。这时候别慌git status会告诉你哪些文件冲突手工改完git add然后git rebase --continue。中途想反悔就git rebase --abort会完整回到变基前的状态这个逃生口一定要记住。我见过有同事在冲突时直接删了.git/rebase-merge目录结果仓库进入了半残状态最后只能重新克隆。3.2 allow-unrelated-histories两条平行历史的握手如果pull --rebase直接报refusing to merge unrelated histories说明两条历史确实毫无交集。这时候要么放弃合并要么显式声明“我知道它们无关但我要合”git pull origin master --allow-unrelated-histories执行之后大概率会弹出冲突因为远程那个初始化文件和你的项目文件可能同名比如都有README.md。手工合并成一个版本git add之后git commit完成合并再推就通了。这里有个更干净的替代思路值得单独说一下如果你确定远程那个初始化提交没有任何价值比如就是个默认的空说明文件仓库也是你自己刚建的完全可以换个顺序——先git clone远程仓库到一个新目录把你的项目文件拷进去注意别把.git一起拷再统一提交推送。这样历史是单线的不会留下一个“合并了两条无关历史”的奇怪节点。我自己现在建新仓库都走这条路虽然多两步操作但省掉了后面所有的麻烦。判断远程提交有没有价值看两点它是不是只有一两个自动生成的提交以及有没有别人基于它做过工作。前者看git log --oneline origin/master后者问一句团队里有没有人在用。两个答案都是“没有”就放心走 clone 路线。3.3 force-with-lease只在独享分支上动这把刀强制推送能解决几乎所有“历史对不上”的问题但它是把双刃剑。直接--force的风险在于万一在你推送的这几秒里别人也推了东西你会把人家的提交直接抹掉而且对方本地还留着下次他推上来又是一轮冲突追查起来非常痛苦。所以我只用--force-with-leasegit push --force-with-lease origin master它的机制是推送前检查远程分支是否还停在你上次拉取时的那个位置如果不是说明有人动过直接拒绝。相当于给强推加了一道乐观锁。这个参数不能省我在任何场景下都不建议用裸--force包括所谓“只有我一个人用的仓库”——你永远不知道自己什么时候会在另一台机器上操作。强推的适用边界我总结成三条全部满足才动手分支是你一个人在用、远程没有需要保留的提交、你已经做好了本地历史是最终版本的确认。三条缺一条就回去走合并路线。特别是刚入职或者刚接手项目的阶段千万别对主干分支强推那不是技术问题是协作事故。3.4 推倒重来把本地历史重置成一次干净的初始提交有些项目的历史已经乱到没法看——几十个fix、update、test提交中间还夹着误提交的压缩包。这种时候与其一个个清理不如直接把历史压成一次干净的初始提交重新建立基线# 确认工作区干净 git status # 建一个没有父提交的孤儿分支 git checkout --orphan clean-main # 把所有文件重新加入暂存区 git add -A git commit -m chore: 初始化项目基线 # 删掉旧分支把当前分支改回原名字 git branch -D master git branch -m master # 首次推送建立上游跟踪 git push -u origin master --force-with-lease--orphan这个参数的作用是创建一个没有任何历史的全新分支但工作区文件会原样保留非常适合做“历史归零”。操作完之后git log里只剩一条提交仓库体积也会在推送后大幅下降因为服务端只会收到这一个提交对应的对象。代价也要说清楚所有历史记录彻底没了没法再git log追溯某行代码是哪次改的。如果这个项目有审计需求或者多人协作不要用这招老老实实做历史清理。另外如果本地还有其他分支依赖旧历史重置后会变成孤儿引用需要一并处理或删除。4. 大文件与误提交内容的清理为什么删掉再提交没用4.1 历史对象不会因为你 rm 就消失这是我见过误解最深的一点。很多人的处理逻辑是发现仓库推不上去 → 找到那个 2GB 的数据文件 →git rm→git commit→ 再推。结果当然还是失败因为那 2GB 的对象仍然完完整整地躺在.git/objects里只是最新一次提交的目录树里不再引用它而已。Git 的存储模型是内容寻址的追加式存储只要对象还在克隆和推送时就会连带传输。理解这一点之后所有“清理历史”的操作本质都一样重写提交图让包含大文件的那些提交从可达集合里消失然后触发一次垃圾回收。区别只在于用哪个工具、自动化程度多高。4.2 用 filter-repo 重写历史的完整流程git filter-repo是目前官方推荐的工具比老的filter-branch快得多坑也少。完整流程大致是这样# 安装需要 Python 环境 pip install git-filter-repo # 强烈建议先做一份完整备份 git clone --mirror 项目目录 项目目录-backup.git # 删除指定路径在所有历史中的记录 git filter-repo --path data/dataset.zip --invert-paths # 如果是一批大文件可以用体积阈值先扫描后批量处理 git filter-repo --strip-blobs-bigger-than 50M--invert-paths的意思是“排除这个路径”也就是把匹配到的文件从所有历史提交中移除。--strip-blobs-bigger-than更粗暴直接把超过阈值的大对象全部剔除适合那种“我也不知道是哪个文件反正就是太大”的场景。有两个细节必须提醒。第一filter-repo出于安全考虑执行后会自动移除所有 remote 配置你需要重新git remote add origin 地址这不是 bug是防止误推。第二历史重写之后所有提交哈希都变了如果仓库已经有人克隆过他们必须重新克隆不能简单pull否则会把旧历史又带回来前功尽弃。所以这一步一定要在推送之前、团队通知之后做。4.3 什么样的项目该直接上 LFS如果你的项目本身就有大文件需求——设计稿、模型权重、音视频素材、二进制依赖那清理历史只是治标真正该做的是从源头换存储方式。Git LFS 的思路是把大文件存到独立的存储上仓库里只保留一个体积很小的指针文件克隆时按需拉取。配置起来不复杂git lfs install git lfs track *.psd git lfs track *.zip git add .gitattributes git commit -m chore: 启用 LFS 管理大文件关键在于.gitattributes这个文件必须提交进仓库团队成员拉下来之后规则自动生效。这里有个顺序陷阱必须在文件第一次提交之前就配好 LFS。如果文件已经进了普通历史再git lfs track是没用的那些旧对象依然在。这种情况只能先filter-repo清理历史再重新用 LFS 提交一遍。另外要注意托管平台对 LFS 的配额限制免费额度和存储空间是分开算的超了会拒绝推送报错信息跟普通体积超限很像容易误判。判断方法是看报错里有没有提到lfs或配额相关的词。5. 把这类报错挡在提交之前的几个习惯5.1 建仓顺序别搞反绝大部分首次推送被拒都是因为“先建远程、后建本地、还让远程自动初始化”。把顺序调一下就能规避要么先在本地git init提交好再去平台上建一个完全空的仓库不勾任何初始化选项然后git remote add加地址推送要么反过来先建远程并初始化然后git clone下来把代码拷进去再提交。两种方式都行核心是别让两条独立历史同时存在。我现在固定用第二种因为clone下来会自动配好 remote 和上游跟踪后续git push连参数都不用带省心。5.2 忽略规则要分层写.gitignore别只写一份。我的习惯是分三层项目级的.gitignore管语言相关的产物node_modules、__pycache__、target全局的core.excludesfile管编辑器和个人习惯产生的文件.idea、.vscode、.DS_Store临时性的用.git/info/exclude管不进版本库。git config --global core.excludesfile ~/.gitignore_global配置完之后新建的任何仓库都会自动应用全局规则。这一个小设置能省掉无数次“哎呀这个文件不该提交”的返工。特别提醒一句.gitignore只对未跟踪的文件生效已经提交过的文件再加规则也没用必须git rm --cached先移出索引。5.3 用 pre-push 钩子做最后一道闸本地钩子是最被低估的一道防线。在.git/hooks/pre-push里放一段脚本推送前自动检查本次要推的提交里有没有超大文件有就直接拦下来#!/bin/sh limit$((50 * 1024 * 1024)) while read local_ref local_sha remote_ref remote_sha; do [ $local_sha 0000000000000000000000000000000000000000 ] continue git rev-list $remote_sha..$local_sha 2/dev/null | while read commit; do git ls-tree -r -l $commit | awk -v lim$limit \ $4 0 lim { printf 超大文件: %s (%.1f MB)\n, $5, $4 / 1048576 } done done exit 1最后那个exit 1要做成条件判断——只有检测到问题才返回非零值否则每次推送都会被拦。这段只是示意结构实际用的时候要把判断逻辑补完整。钩子文件记得给执行权限chmod x .git/hooks/pre-push。注意本地钩子不会随仓库分发团队成员需要各自配置或者通过项目里放一个脚本目录、配合git config core.hooksPath来统一下发。5.4 团队侧的约定比工具更管用最后说点偏管理的。分支保护规则该开就开主干分支禁止直推、必须走合并请求这本身就减少了误推的概率。提交信息的格式约定如果有服务端钩子校验一定要在项目文档里写清楚别让新人踩这个坑才知道有规矩。还有一个容易被忽略的点主干分支的命名统一。老项目叫master新仓库默认叫main同一台机器上来回切换很容易推错目标。团队内部定一个新仓库创建时就改好别拖。6. 我在处理这类问题时踩过的几个坑说几个印象比较深的翻车现场都是踩过之后才长记性的。第一个是凭据缓存。有次帮 A 同学排查他坚称换了新账号但推送一直失败。折腾了半小时才发现系统凭据管理器里存的还是上一个账号的令牌git根本不读他新配置的邮箱。删掉缓存条目后一次就通了。从那以后我排查这类问题前三步必看凭据缓存。第二个是强推之后才发现同事有未拉取的提交。那次是大家都以为那个仓库只有一个人用结果另一个同事前一天推了个修复还没合并。强推之后他的提交在远程消失了他本地还在下一次推送又引发一轮冲突。虽然不是不可恢复但白折腾了一个下午。这件事让我把--force-with-lease变成了肌肉记忆裸--force再也不碰。第三个是历史清理后忘了重新加 remote。filter-repo跑完一切正常git push却提示没有配置推送地址第一反应是工具出错了其实人家是故意的。重新git remote add就完事。第四个最隐蔽某次推送被拒报错里 Service 端只说“拒绝”加--verbose之后才看到是文件名里带了特殊字符触发了平台的路径校验规则。这种问题没法靠经验预判只能靠养成看完整输出的习惯。IDE 折叠日志这个设计在排错环节真的是负优化。回到最初那句话Push rejected本身不提供任何方向它只是一个结果。真正决定排查效率的是你能不能在两三分钟内把“空仓库与非空仓库”“体积是否超限”“权限是否正常”“分支名对不对”这几个维度过一遍。把这套动作固化成习惯之后这类报错基本不会再占用你超过五分钟。我现在遇到的第一反应已经不是重试而是直接开终端敲git fetch加git log --all --graph看一眼图就全明白了。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。