
1. 从一条报错读起git push origin master 在做什么git push origin master 报错这件事几乎每个第一次往团队仓库推代码的人都会撞上一次。我最早遇到的那回是对着满屏英文来回翻了半个多小时最后发现本地分支压根没和远程建立关联加一个-u就完事了。后来带过几批新人发现问题高度集中在同一点不是不会敲命令而是不知道该看报错的哪一行也不知道每一行在说什么于是只能把搜到的命令挨个试一遍运气好能过运气不好就把远程历史搞乱。所以这篇按我平时排查的顺序来写先把这条命令拆开讲清楚再把最常见的几类报错按“症状—原因—修法—验证”逐一过一遍最后给一份按使用场景整理的高频 git 命令清单。刚装好 git 的同学能直接照着做写了几年代码但一直靠编辑器点按钮的同学也能把底层那点逻辑补上。1.1 一条 push 命令其实被拆成了三段git push origin master承载的是三段信息动作是 push目标仓库叫 origin目标分支叫 master。很多人报错以后乱试是因为把这三段混在一起看改了一处不动又改另一处最后连自己改过什么都记不清。push 是单向的它只负责把本地的提交送到远程不会顺手把远程的新提交拉下来也不会替你合并。后面所有rejected类的报错根源都在这一点上。origin 只是远程仓库在本地的别名真正的地址写在.git/config里。你可以把它叫 anything、upstream、companyorigin 只是 init 和 clone 时的默认值本身没有任何魔法。master 是分支名。现在不少仓库初始化后默认分支叫 main如果你的远程默认分支是 main却执意推 master仓库上会凭空多出一个分支而托管页面默认展示的仍是 main你推上去的代码在首页看不到人就会以为“推失败了”其实早就成功了。这三段里最容易出岔子的是第二段。地址错了、别名重了、换了认证方式报出来的错却都是“连不上”长得还挺像。所以排查的第一步永远是确认目标仓库是谁。1.2 上游分支报错里出现频率最高的一个词-u或--set-upstream做的事是往.git/config里写两行配置[branch master] remote origin merge refs/heads/master有了这两行git push和git pull不带参数时才知道该去哪儿。用git branch -vv能看到分支后面跟着[origin/master]那就是关联成功的标志。很多人的困惑是明明远程仓库就在那儿为什么 git 非要我手动关联一次不能自动猜原因在于本地分支名和远程分支名允许不一样。你可以把本地的dev推到远程的feature/dev也可以用一个本地分支跟踪完全另一个名字的远程分支。如果 git 自动替你猜就有推错分支的风险——把实验代码推到主干上去这个后果比多敲几个字符严重得多。所以这个设计取舍其实偏向保守理解了就不会觉得它麻烦。1.3 看到关键词就知道往哪一类查报错信息长但真正有用的往往就那么几个词。先定位到类别再动手比一条条试命令快得多报错里的关键词问题属于哪一类跳到no upstream branch / set-upstream本地分支没有关联远程2.1does not appear to be a git repository远程别名或地址有问题2.2rejected / fetch first / non-fast-forward远程有本地没有的提交2.3remote rejected / pre-receive hook declined服务端规则或权限拒绝2.4Authentication failed / password authentication was removed身份认证没过2.5refusing to merge unrelated histories两条历史没有共同起点2.6习惯了这套分类之后你会发现自己看报错的速度明显变快因为大多数时候只需要扫最后一段和倒数第一行。2. 六类 push 报错的定位与修复2.1 fatal: the current branch master has no upstream branch先看它长什么样$ git push fatal: The current branch master has no upstream branch. To push the current branch and set the remote as upstream, use git push --set-upstream origin master这类报错有个好处它直接把解药写在提示里了。老实说我早期一直忽略提示文字非要去搜纯属浪费时间。原因是本地这个分支没有对应的远程追踪分支而当前push.default的默认值是simple这个模式要求本地分支必须已经有上游才允许不带参数地推。修法就是提示里的那条git push -u origin master跑一次之后往后git push就够了。验证方式是git branch -vv看到分支后面跟着[origin/master]就对了。顺带说一个我很喜欢的配置。push.default有五个取值其中两个值得认识取值行为nothing不带参数的 push 直接报错逼你说清楚推哪儿current推当前分支到远程同名分支没有就创建upstream推到当前分支的上游分支分支名可以不同simple默认值同名分支才推且要求上游已存在matching把所有同名分支一起推上去matching是 git 2.0 之前的默认值一次推所有同名分支容易误伤早就被换掉了。我个人全局设的是currentgit config --global push.default current设完之后git push会自动推同名分支并在需要时建上游省掉-u。代价是丢掉了一层“确认目标”的保护所以我会在推送前用git log --oneline origin/main..HEAD看一眼要推什么这个习惯后面细说。2.2 fatal: origin does not appear to be a git repository完整报错通常是这样$ git push origin master fatal: origin does not appear to be a git repository fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.关键词是does not appear to be a git repository说明 git 在它认识的远程列表里找不到 origin 这个名字。按这个顺序排查git remote -v如果没有任何输出说明仓库里一个远程都没配。加上git remote add origin 仓库地址如果输出里有一行但名字不叫 origin比如叫 upstream那就用回正确的名字或者给它改个名git remote rename upstream origin如果名字对但地址错了直接换地址不用删了重加git remote set-url origin 新的仓库地址 git remote -v # 再确认一次有个细节常被误会git remote -v输出两行是正常的一行标 fetch、一行标 push不是重复添加了远程。想看更详细的信息可以用git remote show origin它会连远程有哪些分支、当前分支跟踪的是谁一起列出来。地址写错有几种经典形态从浏览器地址栏直接复制带着/tree/main之类的浏览路径粘进来私有仓库用的是没有访问权的账号内网仓库开了 SSH 但填的是 HTTP 地址。最后一种最常见判断方法是看地址前缀git开头是 SSHhttps://开头是 HTTP两者认证方式完全不同SSH 走密钥HTTP 走账号或令牌。如果内网仓库用的是自签证书可能会报证书无法验证。这种场景需要把公司自己的 CA 证书路径告诉 gitgit config --global http.sslCAInfo /路径/company-ca.crt提示改 remote 地址或别名不会动本地任何提交改错了再改回来就行不用怕。2.3 ! [rejected] master - master (fetch first)这是最常见的推送被拒完整输出一般是$ git push origin master To gitexample.com:team/demo.git ! [rejected] master - master (fetch first) error: failed to push some refs to gitexample.com:team/demo.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.含义很直白远程有你本地没有的提交git 拒绝用你的历史覆盖它。它不敢自动合并因为一旦有冲突它没法替决定留谁的代码。正确修法是先拉再推git pull --rebase origin master git push origin masterpull不带参数时默认是 merge会生成一个合并提交。加--rebase是把你本地的提交“挪”到远程最新提交之后再放上去历史是一条直线看起来干净。选哪种取决于团队规范个人特性分支、希望历史线性用git pull --rebase团队共用的主干或长期分支用普通 merge别改写已经被别人拉走的历史懒得每次打参数全局设一次git config --global pull.rebase true。rebase 过程中如果冲突了把文件改好后要git add然后跑git rebase --continue继续。想中途放弃就用git rebase --abort它会把你带回 rebase 开始前的状态这一点很让人安心——任何走到一半觉得不对劲的时候abort 永远是退路。我见过有人冲突之后顺手敲了git commit结果把整个 rebase 过程搅成一团最后只能硬着头皮reset重来。同一类问题还有个措辞变体是(non-fast-forward)。它和 fetch first 说的是同一件事你的分支不是远程分支的直系后代只是 git 版本或用词不同。含义都是“历史分叉了”。另外有一种容易误判的情况本地和远程的文件内容其实一模一样但历史上没有共同祖先比如远程新建仓库时自动生成了 README你本地是另一个 commit。这种走 2.6 的办法。这里必须强调一句千万不要顺手git push -f origin master。这会把你不知道的那些提交从远程直接抹掉属于能上事故通报的操作。真要强制推自己的个人分支用--force-with-lease理由见 5.1。2.4 ! [remote rejected] master - master (pre-receive hook declined)这类报错的措辞各家服务端不一样但关键词一定是remote rejected加pre-receive hook declinedremote: 该分支受保护不允许直接推送 ! [remote rejected] master - master (pre-receive hook declined) error: failed to push some refs to ...pre-receive hook是服务端在接收这次推送之前执行的脚本。只要它返回非零整次推送就被整体拒绝你本地什么都没变。它失败通常有这么几种原因分支保护。主干分支被设置成“只能通过合并请求合入”个人账号没有直推权限。这是现在最主流的一种。提交信息不合规。很多团队用钩子校验提交信息格式比如必须带工单号、首行不能超过多少字、不允许出现“update”这类没有信息量的描述。提交者身份没被识别。git config user.email里配的邮箱和服务端账号绑定的邮箱对不上服务端会判断这个提交来自一个“不认识的人”直接拦下。仓库被设为只读、已归档或者账号的写权限被回收。排查顺序建议是从上往下先读服务端返回的提示钩子通常会打印具体原因提示里没写清楚就查自己的身份配置git config user.email git config user.name第三种原因特别隐蔽因为报错里不会直说邮箱的事。我自己就被它卡过一次明明有写权限却怎么都推不上去最后发现是全局邮箱配的是个人邮箱而仓库在公司的组织下。改法git config --global user.email youcompany.com git config --global user.name 你的名字需要留意的是改配置只影响之后的提交历史里已有的提交记的仍是旧邮箱。如果服务端是按提交里的邮箱判权限那老提交得用git rebase -i配合--exec git commit --amend --reset-author --no-edit去改写或者干脆新开一个分支重新提交都挺折腾。所以我给所有人的第一条建议就是装完 git 第一件事把 name 和 email 配好。如果是分支保护正规做法是推到自己命名的分支再去托管页面发起合并git switch -c feature/xxx git push -u origin feature/xxx分支名带上自己的名字或工单号是团队里很省事的惯例评审的人一眼就知道找谁问。2.5 Authentication failed 与 password authentication was removedremote: Support for password authentication was removed. fatal: Authentication failed for https://example.com/team/demo.git/含义是用 HTTP 地址访问时多数托管服务已经不再接受登录密码做 git 认证必须换成访问令牌或 SSH 密钥。见过不少人第一反应是“我密码没错啊”反复输几遍其实密码早就不是合法凭证了。三种处理方式按推荐顺序排第一改用 SSH。一次性配好之后不用再认证git remote set-url origin gitexample.com:team/demo.git生成密钥ssh-keygen -t ed25519 -C youcompany.com # 一路回车默认生成 ~/.ssh/id_ed25519 和 id_ed25519.pub cat ~/.ssh/id_ed25519.pub把.pub文件里的内容整段贴到服务端的 SSH 公钥设置里。这里有个必须记住的点贴的是公钥带.pub后缀的那个私钥不带后缀任何情况下都不要发给别人包括同事和任何自称客服的人。验证ssh -T gitexample.com返回一句欢迎类的文本就算通了。如果返回Permission denied (publickey)从这几处找原因公钥没贴全末尾漏了一截是最高频的贴成了私钥内容~/.ssh目录权限太开放macOS 和 Linux 上目录要 700、文件要 600或者服务端根本没添加这把公钥。第二继续用 HTTP 但改用访问令牌。在账号设置里生成一个有仓库读写权限的令牌推送弹窗里用户名填账号名密码位置填令牌。令牌泄漏等于账号被人拿走建议设置有效期并只给最小必要权限别图省事勾上全部权限。第三清掉旧凭证。macOS 的钥匙串、Windows 的凭据管理器里可能缓存着已经失效的旧密码导致你输新的也没用。macOS 打开“钥匙串访问”搜仓库域名删掉对应条目Windows 在凭据管理器里找 git 相关条目删除。也可以让 git 彻底忘记git config --global --unset credential.helper注意一台机器上要给多个账号配多把密钥时用~/.ssh/config按 Host 区分不同密钥文件否则 git 只会拿默认那把去试第二个账号永远认证失败而且报错和“没有权限”一模一样容易查错方向。2.6 fatal: refusing to merge unrelated histories典型场景本地git init之后写了几个提交远程仓库是新建时勾选了初始化 README两边没有共同祖先于是 git 拒绝合并。$ git pull origin master fatal: refusing to merge unrelated histories解法是显式告诉它“我知道这两条历史没关系照合”git pull origin master --allow-unrelated-histories # 解决完冲突后 git push -u origin master但我更推荐另一种做法别让这个局面出现。远程仓库已经有内容了就直接克隆下来再往里放代码git clone gitexample.com:team/demo.git cd demo # 把原来那份代码拷进来add、commit、push原因有两个--allow-unrelated-histories这个参数不好记每次都得现查而且强合出来的历史会有两条并行起点看git log --graph的时候非常别扭后来的人完全看不懂这个仓库是怎么长成这样的。能用 clone 解决的场景别用合并解决。3. 让工作区安静下来忽略规则、中文乱码与换行符3.1 .gitignore 写了却挡不住文件“我.gitignore里明明写了 node_modules为什么git status还是一堆文件”——这是高频疑问之一。原因在于.gitignore只对“未被跟踪”的文件生效。文件一旦被 add 过就已经进了索引之后写什么忽略规则都拦不住它。处理方式git rm -r --cached node_modules git commit -m chore: 移除已跟踪的 node_modules--cached表示只从索引里删本地文件原封不动。不加这个参数会把本地文件也一起删掉我见过有人直接git rm -r node_modules然后整个依赖目录没了只能重新装。规则写法上也有些细节值得记不带斜杠的log.txt匹配任意目录下的同名文件写成/log.txt只匹配根目录下的那个。目录结尾加斜杠build/只匹配目录不匹配同名文件。!用来取反从忽略里捞出个别文件。但要注意父目录如果已经被忽略子文件的取反是不生效的这是最容易翻车的地方。**匹配任意层级a/**/b能匹配a/x/b和a/x/y/b。还要分清三个放规则的地方.gitignore会被提交对所有人生效.git/info/exclude只对本地生效、不提交适合放自己编辑器产生的垃圾全局忽略文件对所有仓库生效git config --global core.excludesFile ~/.gitignore_global提示把配置文件、.env这类东西加进忽略之前先想想团队成员是不是也需要这个文件。常见做法是提交一份.env.example当模板真实值靠本地填既不泄漏又能让新人跑起来。3.2 中文文件名显示成一串八进制git status里中文名显示成\344\270\255\346\226\207.txt不是仓库坏了是 git 默认对非 ASCII 字符做转义。关掉就行git config --global core.quotepath false顺手把几个显示相关的配置一起配了省得以后一个个查配置项取值作用core.quotepathfalse中文等非 ASCII 文件名原样显示i18n.commitEncodingutf-8提交信息按 utf-8 存储i18n.logOutputEncodingutf-8log 按 utf-8 输出避免乱码core.autocrlfWindows 用 truemacOS/Linux 用 input换行符自动转换core.ignorecase跟随系统默认别乱改文件名大小写是否视为相同换行符这个问题值得单独说。Windows 用 CRLFmacOS 和 Linux 用 LF同一份代码在系统之间来回走很容易出现“整个文件全变了”的 diff实际只改了一行评审的人看着一片红绿很崩溃。给 Windows 用户的标准配置是git config --global core.autocrlf truemacOS 和 Linux 上设成input。更彻底的办法是在仓库根目录放一个.gitattributes让换行符规范跟着仓库走而不是跟着每个人的机器走* textauto *.sh text eollf *.bat text eolcrlf.gitattributes会提交到仓库团队所有人的行为就统一了比每个人各自配autocrlf可靠得多。只要团队里同时有 Windows 和 macOS 用户我就建议加上这个文件一次投入长期受益。还有一种烦人情况某个文件的权限从 100644 变成 100755diff 里显示old mode / new mode内容一行没改。常见于从 Windows 拷到 Linux 的场景。确认执行位确实不需要之后chmod 644 文件再提交即可。3.3 装完 git 先花五分钟把这些配上下面这段可以直接整段抄# 身份必须配否则提交没有作者信息很多服务端直接拒绝推送 git config --global user.name Your Name git config --global user.email youcompany.com # 默认分支名避免每次 init 出来都是 master git config --global init.defaultBranch main # push 不带参数时推同名分支 git config --global push.default current # pull 用 rebase历史更干净 git config --global pull.rebase true # 显示相关 git config --global core.quotepath false git config --global color.ui auto # 常用别名 git config --global alias.st status -sb git config --global alias.lg log --oneline --graph --decorate --all配置不生效的时候第一时间用这条命令看它到底来自哪个文件git config --list --show-origin--show-origin这个参数我强烈建议记住。配置有系统级、用户级、仓库级三层优先级从低到高仓库级会覆盖全局。一堆人排查半天“为什么我改的配置没生效”最后发现是仓库目录里还有一份.git/config写了别的值。--show-origin一眼就能看出每条配置从哪儿读来的省掉大量猜测。4. 高频命令按使用场景整理4.1 三种起点clone、init、改默认分支已有远程仓库、直接参与开发用 clonegit clone 地址 git clone --depth 1 地址 # 只拉最新一次提交--depth 1在仓库特别大时能省下大量时间和磁盘代价是没有历史切不到旧提交也做不了基于历史的排查。只在“我马上就要跑起来”的场景用它。本地已有代码、要推到新远程仓库顺序是git init git add . git commit -m chore: 初始化项目 git remote add origin 地址 git push -u origin main想把默认分支从 master 改成 main本地先改名git branch -m main推上去再去托管页面把默认分支切到 main最后删掉旧的远程分支git push origin --delete master。顺序别弄反先删后建会让别人的本地跟踪分支失联一下。4.2 提交这条线status、add、commitgit status -sb比默认输出短得多-s是短格式-b带上分支信息。每天要敲几十遍的命令短一点是值得的。git add有三个常用姿势。git add 文件名精确添加git add -A添加所有改动包括删除git add -p分块挑选。重点说第三个一个文件里改了五处但只有两处属于这次提交-p会逐块问你收不收让提交只包含相关内容。这个习惯在代码评审时能省很多口舌因为评审的人不需要从一堆无关格式调整里找真正的改动。git commit -m fix: 修正分页参数越界 git commit --amend --no-edit # 把改动补进上一次提交不改提交信息 git commit --amend # 顺便改提交信息--amend只能改最近一次提交而且改了之后提交哈希会变。已经推到远程的分支再用--amend推会被拒绝需要--force-with-lease。个人分支上随便用公共分支上别用。4.3 看历史log 的几种实用姿势git log --oneline --graph --decorate --all这条我配成别名长期在用一行一个提交、画出分支走向、显示分支指针和标签、把没合进来的分支也画出来。排查“这段代码什么时候变成这样”的时候图比列表有用得多。git log -p 文件名 # 看某个文件的每次改动内容 git log -S 某个函数名 -p # 找出哪次提交引入或删掉了这段字符串 git log --author张三 --since2 weeks ago git log --grep工单号-S值得单独记一下它搜的是“改动内容里涉及这个字符串的提交”和--grep搜提交信息、git grep搜工作区是三个层次的东西。想找“某行代码是谁哪次加进去的”还有git blame 文件名它会逐行标注作者和提交。blame 出来的结果先看提交信息很多时候看一眼说明就知道为什么这么写不用去打扰原作者。4.4 分支建、切、合新版本 git 提供了语义更清楚的两个命令建议用它们代替checkoutgit switch main # 切换分支 git switch -c feature/a # 新建并切换 git restore 文件名 # 丢弃工作区改动switch只管切分支restore只管恢复文件比一个checkout既要切分支又要恢复文件清楚得多。老教程里全是checkout新手看多了很容易把两个场景记混然后在一个分支上误删文件。合并的两种方式git merge feature/a # 能快进就快进历史是直线 git merge --no-ff feature/a # 强制生成合并提交团队主干上我倾向--no-ff。好处是分支的边界在历史图上清晰可见事后想整体回滚一个特性只要 revert 那一个合并提交就行。快进合并会把分支上的提交“抹平”进主干过一段时间谁都分不清哪些提交属于同一件事。冲突处理不难要点在心态冲突不是错误只是同一行被两边改过。git status会列出both modified的文件打开找、、标记保留需要的部分、删掉标记git add之后git commitrebase 场景则是git rebase --continue。真拿不准就用git merge --abort退回去先看两边的 log 想清楚再重来。4.5 撤销的几种力度别选错这块最容易出事故做成表更清楚命令影响范围提交历史典型用途git restore 文件工作区不变改坏了丢弃还没 add 的改动git reset --soft HEAD~1撤销提交回退改动留在暂存区提交信息写错、想拆成两个提交git reset --mixed HEAD~1撤销提交回退改动留在工作区提交粒度需要重排git reset --hard HEAD~1撤销提交回退改动彻底丢弃确认这段改动完全不要了git revert 哈希新增一个反向提交前进已经推送到远程的提交需要撤销--hard是唯一会真的丢掉工作区改动的操作。用之前确认两件事这段改动没有别处备份没有别的分支还指着那个提交。万一真丢了git reflog是最后的救命稻草git reflog git reset --hard HEAD{3}reflog记录本地 HEAD 的移动历史包括被 reset 掉的提交默认保留 90 天而且只在本地、不会被推送。我见过同事误操作之后硬着头皮重写了一下午代码其实一条 reflog 就能找回来。养成习惯动reset --hard之前先看一眼 reflog 当前位置心里有底。已经推送过的提交要用revert而不是reset。reset 改写历史别人本地已经有那份历史再 pull 就会打架revert 是往前加一个反向补丁对所有人都是安全的。4.6 临时存一下与只挑一个stash 与 cherry-pick改到一半被叫去处理线上问题先存一下git stash push -m 首页改到一半 # 处理完、提交、推送 git stash list git stash pop # 取回并删除这条 stash git stash apply # 取回但保留这条 stashpop和apply的区别就在取回后留不留记录。需要把同一份改动应用到多个分支时用apply。git stash push -u会把未跟踪的新文件也一起存进去默认是不会的——这个坑很常见stash 完git status是干净的你以为万事大吉切分支回来发现新写的文件还在原地或者反过来切分支时因为未跟踪文件冲突被拦住。cherry-pick是把另一个分支上的某一个提交复制到当前分支git cherry-pick 提交哈希典型场景是从发布分支挑一个紧急修复回主干。它复制的是改动本身会生成一个哈希不同的新提交所以同一个改动在两条分支上会有两个提交之后合并时可能还会碰到冲突。这点要心里有数别以为它们会自动同步。4.7 别名把长命令压成两个字母git config --global alias.st status -sb git config --global alias.co switch git config --global alias.br branch -vv git config --global alias.lg log --oneline --graph --decorate --all git config --global alias.last log -1 --stat git config --global alias.undo reset --soft HEAD~1别名的本质就是往~/.gitconfig的[alias]段写键值对想删就用git config --global --unset alias.undo。有个细节别名里如果要写带引号的复杂参数配置会变得很绕这种情况不如写成一个 shell 脚本放进 PATH比硬塞进别名清楚。5. 几条文档里不会写的经验5.1 --force 与 --force-with-leasegit push -f会无视远程当前状态强行覆盖。危险在于如果在你 rebase 的这段时间里同事也推了提交-f会把那些提交一并抹掉而同事本地还以为推成功了两个人要过很久才会发现代码不见了。--force-with-lease多了一道检查只有当远程分支仍停留在你上次获取到的那个位置时才允许覆盖如果远程动过推送直接被拒你就得重新获取、看看人家改了什么。git push --force-with-lease origin feature/a我早几年就把-f从自己的命令习惯里删掉了。个人分支需要改写历史的时候用--force-with-lease它只是多花你三秒钟换来的是不会误删别人的提交。5.2 推送前的三分钟自检按顺序过一遍能挡掉大部分返工git status # 有没有忘加的文件、有没有调试代码 git diff --cached # 逐条确认这次要提交的内容 git log --oneline origin/main..HEAD # 这次要推哪几个提交第三条特别有用。origin/main..HEAD的含义是“远程没有、本地有的提交”推送前看一眼能发现不该推的东西比如 pull --rebase 之后意外带上的别人的提交或者误合进来的实验分支。我见过有人一口气推了十几个不属于自己的提交上去就是因为从来没看过这条命令。另外两个细节提交信息别写“update”“fix bug”这类过三个月自己都看不懂提交前把调试用的打印语句、注释掉的整块代码清一遍这些在 diff 里最扎眼评审的人一眼就看到。5.3 几个流传很广但并不准确的说法说法实际情况必须先 pull 再 push只有远程有本地没有的提交时才必须。可以先 fetch 看一眼需要时再 rebasereset --hard 之后代码就没了已经提交过的内容还能从 reflog 找回真正丢的是从未 add 过的工作区改动加了 .gitignore 就能忽略任何文件已跟踪文件不受影响得先 git rm --cachedmaster 是固定的主分支名它只是 init 的默认值可配置、可重命名现在多数新仓库用 mainrebase 一定比 merge 高级两者解决的是不同问题。公共分支上 rebase 会改写别人已经拉走的历史是有害操作这些说法之所以流传广多半是因为省略了前提条件。把前提补上很多“玄学问题”就不玄了。最后说一个我自己的习惯。每次遇到没见过的新报错我会先把第一行的英文关键词原样复制下来去搜然后回到仓库里用三条命令确认现状git status看本地工作区git log --oneline -5看最近提交git remote -v看目标仓库。绝大多数看起来“诡异”的问题这三条命令就能定位到底是本地状态、远程状态还是身份权限的问题。搜到的解法不一定对得上你的场景但把现场看清楚之后误差会小很多。git 的学习曲线其实陡在概念不在命令origin、upstream、HEAD、索引这几个词真正弄明白了报错提示写得都挺直白——它一直在告诉你下一步该做什么只是以前没耐心读而已。