git push 失败时,最常见的反应是去网上搜那串报错原文,然后照着别人帖子里的命令一条条敲,最后把 .git 删了重新 clone。其实大多数 push 报错都能归到三类:远端地址变了、凭据失效了、分支没对上。这篇按「花多少时间、多大概率解决」排序,先做 30 秒就能排除的,再往深了查。
最容易踩的是「仓库地址和你想的不一样」。很多人配置了多个远端,或者换过托管平台,push 时一直推到旧地址,报错却是「Authentication failed」或「Could not resolve hostname」。
先看当前远端配的是什么(实测,Windows 和 macOS 的命令完全一样):
git remote -v
输出形如:
origin https://github.com/sky/blog.git (fetch)
origin https://github.com/sky/blog.git (push)
如果这里的地址和你现在要推的仓库对不上,改掉它:
git remote set-url origin https://github.com/你的用户名/仓库名.git
这条命令 5 秒就能跑完,却是「折腾一小时最后发现推到旧仓库」这类事故的解药。改完再 git push,很多时候直接就好了。
GitHub 从 2021 年 8 月起就不再接受账号密码做 HTTPS 推送(来源:GitHub 官方 blog 2021-08),必须用 Personal Access Token。很多报错的真实原因就是:仓库还是老的,但凭据过期了,或者当初存的就是已经作废的 token。
判断是不是凭据问题的两个信号:
Authentication failed for 'https://github.com/...'remote: Invalid username or password、403macOS 和 Windows 会把凭据存在系统钥匙串/凭据管理器里,失效后要主动清掉才能重新弹输入框。以 Windows 为例,Git 默认用系统凭据管理器存储(实测):
git credential-manager erase
或者到「控制面板 → 凭据管理器 → Windows 凭据」里,找到 git:https://github.com 那条删掉,下次 push 就会重新要求输入用户名和 token。
一个关键点:填 token 时,用户名填你的 GitHub 用户名,密码框填 token,而不是把 token 当用户名填。这一步填错的人非常多。
如果你用的是 SSH 而不是 HTTPS,那要查的是密钥对不对得上:
ssh -T git@github.com
返回 Hi 用户名! You've successfully authenticated 就说明 SSH 通了;返回 Permission denied (publickey) 就是公钥没加进 GitHub 或本地密钥路径不对。
前两类都排除了,再看分支。常见的报错是:
fatal: The current branch main has no upstream branch.
或者 push 时提示远端有本地没有的提交,被拒绝(non-fast-forward)。这通常发生在:你 clone 下来之后别人也往同一个分支推了,或者你本地分支是从别的分支切出来的,跟远端不是一条线。
先同步远端再推(实测,这是处理「远端有新提交」最稳的顺序,先 pull 再 push 比强行 --force 安全得多):
git pull --rebase origin main
git push origin main
--rebase 会把你的本地提交「垫」在远端新提交之上,避免产生一次多余的 merge 提交。如果确实想覆盖远端(比如你本地是对的、远端那几笔是废的),才考虑强制推,但要明确知道代价:
git push --force-with-lease origin main
--force-with-lease 比裸 --force 安全(来源:Git 官方文档),它会在覆盖前检查远端是不是你上次看到的状态;如果别人在你看不到的时候推了新提交,它会拒绝执行,避免把别人的活冲掉。除非百分百确定远端那几笔提交是废的,否则不要用裸 --force。
改完 remote 还是报同样的错。 因为凭据也被缓存了,地址改了但 token 还是旧的。改地址和清凭据要一起做。
公司内网用 HTTPS push 到自建 GitLab,一直超时。 这多半不是 Git 的锅,是代理或防火墙。Git 会读 http.proxy 配置,内网地址被代理劫持了。临时关掉再试(实测):
git config --global --unset http.proxy
git config --global --unset https.proxy
报错 RPC failed; HTTP 413,大文件推不上去。 这是仓库里的文件超过了托管平台单文件大小限制。GitHub 是 100 MB(来源:GitHub 官方文档),超过会拒绝。这个坑比前两个少见,但一遇到就很耽误时间。
解决思路两条路:大文件如果确实要版本化,用 Git LFS 管;如果是不小心 add 进来的误提交,从历史里清掉。这里重点说第一种,因为用 LFS 的人不多、文档又散:
# 安装 LFS 后,初始化
git lfs install
# 把某种大文件类型交给 LFS 管
git lfs track "*.zip"
git add .gitattributes
# 之后正常 add/commit/push,大文件会走 LFS 通道
LFS 的核心(实测):它不在 Git 历史里存大文件本体,只存一个指针,真正的文件单独传。所以用 LFS 之前历史里已经误传的大文件,track 之后也救不回来,得用 filter-repo 或 filter-branch 重写历史——这一步属于进阶操作,动手前务必先备份 .git。
SSH 换了端口还是连不上。 有些公司 Git 服务器改过 SSH 端口,不是默认 22。要在 ~/.ssh/config 里显式指定,git remote 的地址里也得写成 ssh:// 形式带端口。
举例,如果服务器 SSH 端口是 2222,~/.ssh/config 里加:
Host git.company.com
Port 2222
然后 remote 地址用 ssh://git@git.company.com:2222/group/repo.git 这种带端口的写法(实测,端口在 ssh:// 里要显式写出来,否则 config 里的 Port 不一定被读到的情形也有)。
push 成功但 CI 里拉不到。 这是「分支名大小写」或「没推对分支」导致的。确认本地分支名和远端分支名完全一致,包括大小写。
同时干几件事,用 worktree 会清爽很多,但也带来新的 push 坑。 如果你习惯同时开几个分支开发,git worktree 能在同一仓库下开多个工作目录,不用反复 stash/checkout。但它有个坑:worktree 里改的东西,主仓库的 git status 看不到,很多人以为丢了、或者从主仓库 git push 时发现「没东西可推」。
排查要点:先在主仓库跑 git worktree list,看有哪些 worktree 在工作(实测):
git worktree list
输出里除了主路径,还有你开的每个 worktree 的路径和所在分支。搞清楚改动在哪个 worktree 里,再决定从哪 push。另外 worktree 里的分支不能同时被两个地方 checkout,否则会报 fatal: 'xxx' is already checked out。
很多人 push 一次输一次密码,烦得不行。这不是平台故意刁难,是凭据没有被正确缓存。不同系统的机制不一样,搞清楚自己用的是哪套,才能一劳永逸。
想确认当前用的是哪种,看一条配置(实测):
git config --global credential.helper
manager 或 manager-core:走 Git Credential Manager(Windows 常见)osxkeychain:走 macOS 钥匙串如果输出为空,说明你每次 push 都要重新输,那就可以手动指定一个 helper。Windows 用 manager,macOS 用 osxkeychain:
git config --global credential.helper manager
配好之后,下次 push 登录一次,之后就不用了。这一步做完,凭据相关的报错会少一大半。
为了让你不用通读全文,我把最常见的报错原文和它的归属列成一张表。你在终端里看到哪句,就对号入座:
| 报错原文(关键词) | 归属 | 直接跳 |
|---|---|---|
Authentication failed for 'https://...' | 凭据失效 | 第二节 |
remote: Invalid username or password | 凭据失效 | 第二节 |
remote: Permission to xxx.git denied | 凭据/权限 | 第二节 |
fatal: unable to access ... Could not resolve host | 地址/网络 | 第一节 + 第四节坑2 |
fatal: The current branch xxx has no upstream branch | 分支没关联 | 第三节 |
! [rejected] ... (non-fast-forward) | 远端有新提交 | 第三节 |
! [rejected] ... (fetch first) | 远端有新提交 | 第三节 |
remote: Support for password authentication was removed | 用了密码没换 token | 第二节 |
error: RPC failed; HTTP 413 | 大文件超限 | 第四节坑3 |
Permission denied (publickey) | SSH 公钥没配 | 第二节 |
这张表本身就是一个最快的分流器:先看报错原文,再决定从哪一节看起,而不是从头读。
push 报错往往不是孤立的,它和两个问题经常一起出现,一并说清楚,省得你查完 push 又去查别的。
第一个:本地改了东西,但 git status 说干净。 这通常是改了却被 .gitignore 忽略了,或者改在了错误的目录。看某个文件到底被没被忽略:
git check-ignore -v 你怀疑的文件名
如果有输出,说明它命中了 .gitignore 的某条规则;没输出就是没被忽略。
第二个:clone 一个很大的仓库卡住不动。 这和我之前写过的「AI 写的 SQL 导致数据库死锁」是同一类「看着像卡住、其实要分层定位」的问题,思路都是先判断卡在哪一段再动手。大仓库可以先做浅克隆,只拉最近一次提交,速度快很多(实测):
git clone --depth 1 https://github.com/某仓库.git
等确定要完整历史了,再 git fetch --unshallow 拉全。
别急着重装 Git。 上面三类问题(地址、凭据、分支)没有一个靠重装能解决,重装只会把配置清掉,反而更难查。按这个顺序走,99% 的 push 报错在第二步就能定位:
git remote -v 看地址对不对(30 秒)git pull --rebase 再 push(1 分钟)适合重装 Git 的只有一种情况:Git 本身坏了,比如所有命令都报 fatal: not a git repository 但目录里明明有 .git,或者 Git 命令直接崩溃、版本混乱。这种情况极少,而且重装前也要先把 .git 目录备份好。
如果你是按网帖把 .git 删了重新 clone 才解决,那说明你其实丢掉了本地没 push 的提交——正确做法是先 git log 确认哪些没推上去,git stash 或切个分支把改动保住,再 clone。丢了工作区是最亏的,比 push 报错本身严重得多。
说到丢提交,我在本地部署和编译开源项目时也栽过类似的坑,比如当初折腾 DeepSeek Harness 的源码编译打包 时,就因为没先把本地改动分叉出来,白白返工了一轮。Git 的每一个「回退」「覆盖」操作,动手前都值得先想一句:这步会不会丢东西。
写完这一篇,我想强调一个更根本的思路,它比上面任何一条具体命令都值钱。
Git 的报错几乎从来不是「Git 坏了」,而是「某一层的状态和你的预期不一致」。 地址是一层、凭据是一层、分支关系是一层、网络又是一层。绝大多数人查 push 报错的失败方式,是直接搜那串报错原文,然后照着帖子里的命令一条条试,试到哪条碰巧好了为止——这是「撞大运式排查」,运气好三分钟,运气不好一下午。
正确的方式是先判断自己卡在哪一层,再动手。判断分层最快的手段,其实就三条命令,各管一层:
git remote -v # 看「地址层」对不对
git config --list # 看「配置层」(含 credential.helper、http.proxy)对不对
git status && git log --oneline -5 # 看「分支层」:本地在哪、领先远端几笔
这三条跑完,绝大多数 push 报错该看哪一层、该改什么,心里就有数了,剩下的只是执行。
这个「先分层、再定位」的思路,跟我写过的其它排查类文章是一脉相承的——不管是排查 AI 写的 SQL 导致数据库死锁,还是给本地程序定位卡点,都是先画一条「卡在哪一段」的线,而不是上来就重装。工具会变、报错会变,但这个排查的框架是长期有用的。
所以这篇的最终结论可以再浓缩成一句:git push 报错,先跑 git remote -v 和 git config --list,再决定动不动手;别删 .git,别急着重装。
补充两个容易被忽略、但很省事的小习惯(实测):
git status 一眼的习惯,确认自己在哪个分支、有哪些改动待提交。很多人 push 失败,归根到底是没意识到自己当前不在以为的那个分支上——这个问题 5 秒就能在上游避免,比事后排查省一百倍的时间。