写在前面:这一章要解决什么
你写完代码git push却被拒:! [rejected] main -> main (non-fast-forward)。本章帮你理解推送被拒的原因,学会安全处理,避免覆盖他人代码。
学完后,你应能:
- 解释「非快进」
- 区分
fetch和pull - 知道何时用
git pull --rebase - 理解
--force的危险,以及--force-with-lease的安全 - 理解远程跟踪分支
- 配置多远程,走通三角工作流
读者设定:大一同学,已学过基本远程协作,想搞懂推送被拒与强制推送。
1. 定位:为什么要单开一章讲远程进阶
1.1 一句话先记住
推送被拒 = 你想往信箱塞信,但信箱里已经有别人的信了。你的提交的前驱是 A,但远程 A 后面已有 B,Git 拒绝覆盖。
1.2 没有这些知识会怎样
| 场景 | 不知道怎么办 | 知道以后 |
|---|---|---|
| push 被拒 | 慌乱,乱敲命令 | 先拉再推 |
| 想强推覆盖 | --force会丢失同事代码 | --force-with-lease安全兜底 |
| fetch 和 pull 分不清 | 每次 pull 产生合并提交 | 按需选择,历史更干净 |
| 多人改同一分支 | 互相覆盖,越推越乱 | 理解远程跟踪分支,有条有理 |
1.3 生活里的类比
| 你已经会的 | 远程进阶对应 |
|---|---|
| 往信箱塞信 | push |
| 信箱里已有别人的信,塞不进去 | 非快进 |
| 先取出信箱里的信,再塞自己的 | fetch + merge / pull |
| 强行把别人的信换成自己的 | force push(危险!) |
| 换之前先确认信箱里还是你上次看到的那几封 | force-with-lease |
1.4 本章内容目标
| 目标 | 你能做到 |
|---|---|
| 理解非快进 | 说清「远程有新提交,你本地没有」 |
| 区分命令 | fetch 只下载,pull = fetch + 整合 |
| 安全强推 | 用--force-with-lease代替--force |
| 看懂跟踪分支 | 解释origin/main是什么 |
| 多远程协作 | 配置 upstream,走通三角工作流 |
2. 本质:推送被拒到底发生了什么
2.1 一张图先建立感觉
图:本地与远程之间的同步——fetch 拉取,push 推送。
图:分布式版本控制——每个人都有完整副本。
2.2 用白话拆开
- 远程仓库是共享的「中心信箱」。
- 远程跟踪分支(如
origin/main)是你本地对远程分支的缓存快照,由fetch更新。 - 推送是把本地提交上传到远程。
- 非快进是远程有你不包含的新提交,Git 拒绝推送。
2.3 非快进到底是什么
你上次看信箱:信 A(origin/main指向 A)。现在信箱:A → B(别人推了 B)。你要塞:A → C(你的本地提交 C 基于 A)。C 未包含 B,若硬塞,B 丢失。Git 拒绝,这就是非快进。
2.4 fetch vs pull:关键区别
| 命令 | 干什么 | 动了哪些指针 |
|---|---|---|
git fetch | 只下载远程提交和对象 | 只更新远程跟踪分支 |
git pull | fetch + 自动合并 | 更新远程跟踪分支 + 合并到本地分支 |
白话:fetch是「先看看信箱里有啥新信」,pull是「看完后自动归档到你的文件夹」。多数时候先fetch再决定如何整合。
2.5 几条硬规矩
| 规矩 | 白话 |
|---|---|
| 远程跟踪分支不可手动提交 | origin/main只能由 fetch 更新 |
| push 前先 fetch | 看看远程是否有新东西 |
永远不要对共享分支用--force | 会覆盖别人的提交 |
--force-with-lease只用于自己的功能分支 | 保险绳,不是通行证 |
| push 被拒是 Git 在保护你 | 别慌,先拉再推 |
2.6 新手最常踩的坑
| 坑 | 表现 | 办法 |
|---|---|---|
push 被拒就--force | 别人提交丢失 | 先fetch+ 整合,实在要强推用--force-with-lease |
| 分不清 fetch 和 pull | 产生大量合并提交 | 想看再用fetch,想一步到位用pull(建议配--rebase) |
不知道origin/main是什么 | 以为是远程分支,实际是本地快照 | 记住:由 fetch 更新的记忆 |
| 改了已推送的提交又推 | 被拒,历史变了 | 变基后--force-with-lease |
| 多人推同一分支 | 互相覆盖 | 各开功能分支,走 PR/MR |
3. 建议学习顺序
理解非快进 → fetch 和 pull 的区别 → pull --rebase → 远程跟踪分支 → force-with-lease → 多远程 → 三角工作流4. 动手准备
要求 Git 2.43.0,使用本地模拟远程仓库,无需联网。
# 建假远程mkdir-p/tmp/fake-remote.git&&cd/tmp/fake-remote.git&&gitinit--bare-bmain# 建本地工作区mkdir-p/tmp/my-work&&cd/tmp/my-work&&gitinit-bmaingitconfig user.name"Ada Example"&&gitconfig user.email"ada@example.com"gitremoteaddorigin /tmp/fake-remote.git# 初始提交并推送printf'v1\n'>file.txt&&gitaddfile.txt&&gitcommit-m"init: 第一个版本"gitpush-uorigin main输出显示远程分支已建立,本地main跟踪origin/main。
5. 跟着做:一次完整远程进阶实验
5.1 制造非快进:模拟别人先推了代码
创建另一个工作区模拟 Bob:
mkdir-p/tmp/other-work&&cd/tmp/other-work&&gitclone /tmp/fake-remote.git.gitconfig user.name"Bob Example"&&gitconfig user.email"bob@example.com"printf'v2 from Bob\n'>file.txt&&gitaddfile.txt&&gitcommit-m"feat: Bob 的修改"gitpush回到你的工作区,在不知情下提交并推送:
cd/tmp/my-workprintf'v2 from Ada\n'>file.txt&&gitaddfile.txt&&gitcommit-m"feat: Ada 的修改"gitpush推送被拒,提示non-fast-forward。这是非快进。
5.2 先 fetch 看看远程有什么
gitfetch输出显示origin/main更新到 Bob 的提交,但本地main未变。
git log --oneline --graph --all显示分叉:远程origin/main指向 Bob,本地main指向 Ada。
5.3 用 pull 整合(merge 方式)
gitpull产生合并提交。若遇到冲突,解决后 push 成功。
5.4 用pull --rebase代替 pull
gitfetchgitpull--rebase把你的提交移到远程最新之后,历史成一条直线。设置默认:
gitconfig pull.rebasetrue5.5 远程跟踪分支是什么
git branch -r列出origin/main;git branch -vv显示本地分支与远程跟踪的关联。远程跟踪分支是只读的,由fetch更新。
5.6--force为什么危险
变基后推送被拒,git push --force会覆盖远程,可能丢失他人提交。
5.7--force-with-lease:保险绳
--force-with-lease要求远程与你本地origin/main一致,否则拒绝。若别人在你 fetch 后推送了,再--force-with-lease也会被拒,保护了他人代码。
5.8 远程管理:查看、添加、删除
git remote -v查看远程git remote rename重命名git remote remove删除git remote add添加
5.9 多远程与三角工作流
你 fork 了老师的仓库为origin,老师的仓库为upstream。从upstream拉取,往origin推送,通过 PR 贡献。
gitremoteaddorigin https://github.com/你的用户名/课件作业.gitgitremoteaddupstream https://github.com/老师用户名/课件作业.gitgitfetch upstreamgitmerge upstream/maingitpush origin main设置本地main跟踪origin/main。
6. 命令按「用途」分组
6.1 拉取与整合
| 命令 | 干什么 |
|---|---|
git fetch | 下载远程更新,不动本地分支 |
git fetch --all | 下载所有远程的更新 |
git pull | fetch + merge |
git pull --rebase | fetch + rebase(推荐) |
git config pull.rebase true | 让 pull 默认用 rebase |
6.2 推送
| 命令 | 干什么 |
|---|---|
git push | 正常推送 |
git push -u origin main | 推送并设置上游跟踪 |
git push --force | 强制推送(危险) |
git push --force-with-lease | 安全强推(远程变了就拒绝) |
6.3 远程管理
| 命令 | 干什么 |
|---|---|
git remote -v | 列出所有远程 |
git remote add 名字 地址 | 添加远程 |
git remote remove 名字 | 删除远程 |
git remote rename 旧名 新名 | 重命名远程 |
6.4 查看跟踪关系
| 命令 | 干什么 |
|---|---|
git branch -r | 列出远程跟踪分支 |
git branch -vv | 列出本地分支及其跟踪关系 |
git branch --set-upstream-to=origin/main main | 设置上游跟踪 |
7. 对照表:降低记忆负担
7.1 push 被拒时怎么办
| 情况 | 做法 | 命令 |
|---|---|---|
| 远程有新提交,我落后了 | 先整合再推 | git pull --rebase→git push |
| 我改写了历史(自己的分支) | 安全强推 | git push --force-with-lease |
| 改写了历史(共享分支) | 别推 | 用revert或沟通 |
| 不关心历史 | 不推荐 | git push --force(极度危险) |
7.2 fetch vs pull vs pull --rebase
| 命令 | 下载了代码? | 合并了? | 历史形状 |
|---|---|---|---|
git fetch | 是 | 否 | 不变 |
git pull | 是 | 是(merge) | 多一个合并提交 |
git pull --rebase | 是 | 是(rebase) | 一条直线 |
7.3 --force vs --force-with-lease
| 命令 | 会覆盖别人的提交吗? | 安全吗? |
|---|---|---|
git push --force | 会,不检查 | 危险 |
git push --force-with-lease | 远程有新提交就拒绝 | 有保险绳 |
7.4 远程跟踪分支 vs 本地分支
| 概念 | 例子 | 谁更新它 | 你能直接提交到上面吗 |
|---|---|---|---|
| 远程跟踪分支 | origin/main | git fetch | 不能 |
| 本地分支 | main | 你自己 | 能 |
8. 安全习惯
8.1 建议这样做
| 习惯 | 原因 |
|---|---|
push 前先git fetch | 查看远程是否有新东西 |
设置pull.rebase true | 避免无意义合并提交 |
用--force-with-lease代替--force | 多一层保护 |
--force-with-lease只用于自己的功能分支 | 共享分支不要强推 |
强推前先fetch确保origin/main最新 | 保险绳才有效 |
push 后用git log确认 | 确保推了你想推的 |
8.2 绝对不要做的事
| 操作 | 后果 |
|---|---|
对共享分支--force | 别人提交丢失 |
不fetch就--force-with-lease | 保险绳可能失效 |
| 推送包含密钥的提交 | 密钥泄露,历史难清 |
8.3 推荐工作流(团队协作)
1. 开始干活前:git fetch 2. 整合远程:git pull --rebase 3. 开分支改代码、提交 4. 推之前再 fetch 一次 5. 正常推送:git push 6. 如果改写了历史(自己的功能分支):git push --force-with-lease9. 真实场景
9.1 课程作业:和同学共用一个仓库
- 各自开功能分支,不直接推
main - 提 PR 让助教或组长审
- 若 push 被拒,
git pull --rebase再推
9.2 开源贡献:三角工作流
- fork 项目到自己的账号
git remote add upstream 原仓库地址git fetch upstream,git checkout -b fix/xx upstream/main- 改好提交,
git push -u origin fix/xx - 在 GitHub 上从你的 fork 发 PR 到 upstream
9.3 推送被拒但急着想推
gitfetchgitlog--onelineorigin/main..HEAD# 查看本地独有提交gitpull--rebasegitpush9.4 变基后要强推
在自己的功能分支上 rebase 后,推送被拒,使用git fetch && git push --force-with-lease origin feature/xxx。
9.5 误推了敏感信息
删除文件并提交,但历史仍存在;紧急情况需重写历史(见后续章节),立即通知团队不要拉取。
10. 稍微多懂一点点(可选)
- 远程跟踪分支保存在
.git/refs/remotes/origin/main,由 fetch 更新。 --force-with-lease比较本地origin/main与远程最新哈希,不一致则拒绝;故需先 fetch。- 可通过
git remote set-url --push origin 禁止推送的地址防止误推。 git push --force-if-includes(Git 2.30+)更严格,要求本地已整合远程提交。- 即使被强推覆盖,远程 reflog 可能找回提交。
11. 小实验(请一定动手)
实验甲:制造并解决非快进
用假远程和两工作区,A 推,B 推被拒,Bfetch后pull --rebase再推成功。
实验乙:force-with-lease 拦截
B 不 fetch 直接--force-with-lease被拒,fetch 后如有 A 新提交,仍会被拒,需整合再推。
实验丙:三角工作流
一个仓库当 upstream,一个当 fork,从 upstream 拉,推到 origin,模拟 PR。
通过标准:能独立处理推送被拒,知道何时用--force-with-lease,能说出它比--force安全在哪里。
12. 常见问题
问 1:推送被拒是不是我做错了?
不是,远程有你不包含的新提交,先 fetch 再整合。
问 2:fetch 和 pull 到底用哪个?
想看再决定用fetch,想一步到位用pull --rebase。建议配置pull.rebase true。
问 3:--force-with-lease一定能保护我吗?
在 fetch 后、别人又推了这种时间差内,可能拦不住,但比--force安全得多。
问 4:能不能对main用--force-with-lease?
技术上能,但共享分支不应强推,用revert更安全。
问 5:pull 出了合并提交怎么办?
设置pull.rebase true,以后不会出现。
问 6:origin/main和main有什么区别?origin/main是本地对远程 main 的快照,main是你本地分支。
问 7:删了 remote 会删代码吗?
只删关联关系,不删本地代码和远程仓库。
问 8:三角工作流和普通推送有什么区别?
普通推送直推主仓库,三角工作流推自己的 fork,再通过 PR 合并。
13. 总结、学习路线与思维升华
13.1 这一章请记住的
| 点 | 记住什么 |
|---|---|
| 非快进 | 远程有你不包含的提交,推送被拒是 Git 在保护你 |
| fetch vs pull | fetch 只下载,pull = fetch + 整合 |
| pull --rebase | 避免合并提交,保持历史一条线 |
| –force | 危险,直接覆盖别人的提交 |
| –force-with-lease | 安全强推,远程变了就拒绝 |
| 远程跟踪分支 | origin/main是本地快照,由 fetch 更新 |
| 多远程 | origin + upstream,三角工作流 |
| 下一章 | 额外命令速查(21 章) |
13.2 在整个系列中的位置
01 认识 Git 02 三区模型 03 基本工作流 04 安全地撤销 05 分支与合并 06 远程协作 …… 19 暂存与清理 20 远程进阶 ← 当前 21 额外命令速查 22 冲突策略 23 安全实践 ……13.3 思维升华
保险绳不是鼓励你冒险,而是当你必须强推时多一道确认。
推送被拒是 Git 在说「远程有新东西你还没看」,不是在跟你作对。
先 fetch 看清楚,再决定怎么整合。能不强推就别强推,必须强推就用--force-with-lease。
信箱类比:塞信前先看看信箱里还是不是你记得的样子——这就是 force-with-lease 的全部意义。
13.4 参考资料
- Pro Git 中文版 — 远程分支
- Pro Git 中文版 — 与远程协作
- 官方文档:git push / fetch / pull / remote
- 命令输出样例验证环境:Git 2.43.0;演示作者信息:
Ada Example <ada@example.com>
13.5 本章检查清单
- 能说清「非快进」是什么,为什么推送会被拒
- 能区分
fetch和pull - 会用
git pull --rebase保持历史干净 - 知道
--force-with-lease比--force安全在哪里 - 能解释
origin/main是什么 - 会添加和删除远程仓库
- 能说出三角工作流的三个步骤
- 推送被拒时知道该怎么处理
按回车前先预判状态——这一章的保险绳,是你对远程仓库的清醒认识。