20 --- 远程进阶:强制推送的保险绳------force-with-lease
20 --- 远程进阶:强制推送的保险绳------force-with-lease
文章摘要: 本文深入讲解 Git 远程协作中的高级场景,重点解决推送被拒(non-fast-forward)问题。通过信箱类比解释非快进本质,区分 fetch 与 pull 的差异,介绍 pull --rebase 保持历史干净的方法。核心讲解强制推送的安全实践:对比 --force 的危险性与 --force-with-lease 的保险机制,详解远程跟踪分支原理。涵盖多远程配置、三角工作流、命令分组对照、安全习惯及真实场景应对。最后提供动手实验和常见问题解答,帮助读者建立安全的远程协作工作流。
写在前面:这一章要解决什么
你兴冲冲写完代码,git push 一敲------
css
! [rejected] main -> main (non-fast-forward)
推送被拒了。心里一慌:别人的代码是不是被我覆盖了?还是我落后了?要不要 --force 强推?强推会不会出事?
这一章就是帮你搞明白:推送为什么会失败,失败了该怎么做,以及怎么在必须强推时给自己拴一根保险绳。
学完后,你应该能:
- 用大白话解释「非快进」是什么意思
- 说清
fetch和pull的区别 - 知道什么时候该
git pull --rebase - 理解
--force为什么危险、--force-with-lease为什么更安全 - 看懂「远程跟踪分支」在干什么
- 配置多个远程仓库,走通「三角工作流」
读者设定: 大一同学,已经学过基本远程协作(06 章),想搞懂推送被拒和强制推送那些事。
1. 定位:为什么要单开一章讲远程进阶
1.1 一句话先记住
推送被拒 = 你想往信箱塞信,但信箱里已经有别人的信了。
你出门前看了一眼信箱是空的,结果塞信的时候发现别人已经塞了一封进去。你的信和别人的信冲突了,邮局不帮你硬塞------这就是「非快进」。
1.2 没有这些知识会怎样
| 场景 | 不知道怎么办 | 知道以后 |
|---|---|---|
| push 被拒 | 懵了,乱敲命令 | 知道先拉再推 |
| 想强推覆盖 | --force 一敲,同事代码没了 |
--force-with-lease 安全兜底 |
| fetch 和 pull 分不清 | 每次都 pull,经常出合并提交 | 按需选择,历史更干净 |
| 多人改同一分支 | 互相覆盖,越推越乱 | 理解远程跟踪分支,有条有理 |
1.3 生活里的类比
| 你已经会的 | 远程进阶对应 |
|---|---|
| 往信箱塞信 | push |
| 信箱里已经有别人的信,塞不进去 | 非快进(non-fast-forward) |
| 先把信箱里的信取出来看看,再塞自己的 | 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 非快进到底是什么
用信箱类比:
css
你上次看信箱:信 A(你的本地记录 origin/main 指向 A)
现在信箱里:信 A → 信 B(别人在你不知道的时候推了 B)
你要塞:信 A → 信 C(你的本地 main 指向 C,C 的前驱是 A)
问题:信 C 的前驱是 A,但信箱里 A 后面已经有了 B。
你的信 C 没有包含 B 的内容,如果硬塞,B 就丢了。
Git 拒绝了,这就是「非快进」(non-fast-forward)。它是在保护远程上别人的代码不被你的推送覆盖。
2.4 fetch vs pull:关键区别
| 命令 | 干什么 | 动了哪些指针 |
|---|---|---|
git fetch |
只下载远程的提交和对象 | 只更新 origin/main 等「远程跟踪分支」 |
git pull |
fetch + 自动合并 | 更新远程跟踪分支 + 合并到你的本地分支 |
白话: fetch 是「先看看信箱里有啥新信」,pull 是「看完了还自动归档到你的文件夹里」。
大多数时候你想先 fetch 看看情况,再决定怎么整合。pull 一步到位,但可能在你没准备好的时候制造合并提交。
2.5 几条硬规矩
| 规矩 | 白话 |
|---|---|
| 远程跟踪分支你改不了 | origin/main 是 fetch 更新的,不能手动提交到上面 |
| push 前先 fetch | 看看远程有没有新东西 |
永远不要对共享分支用 --force |
别人的提交会被你覆盖 |
--force-with-lease 只用于自己的功能分支 |
它是保险绳,不是通行证 |
| push 被拒是 Git 在保护你 | 别慌,先拉再推 |
2.6 新手最常踩的坑
| 坑 | 表现 | 办法 |
|---|---|---|
push 被拒就 --force |
别人的提交没了 | 先 fetch + merge/rebase,实在要强推用 --force-with-lease |
| 分不清 fetch 和 pull | pull 出一堆合并提交 | 想先看看就用 fetch,想一步到位再用 pull |
不知道 origin/main 是什么 |
以为是远程上的分支,其实是本地缓存的快照 | 记住:origin/main 是本地对远程的「记忆」 |
| 改了已推送的提交又推 | 被拒,因为历史变了 | 变基后用 --force-with-lease |
| 多人推同一分支 | 互相覆盖 | 各开功能分支,走 PR/MR 流程 |
3. 建议学习顺序
text
理解非快进 → fetch 和 pull 的区别 → pull --rebase → 远程跟踪分支 → force-with-lease → 多远程 → 三角工作流
4. 动手准备
bash
git --version
text
git version 2.43.0
准备一个可以随便折腾的目录。演示身份:Ada Example ada@example.com。
下面我们在本地模拟一个「远程」,这样不用真连网络也能练。
bash
# 1. 建一个「假远程」仓库
mkdir -p /tmp/fake-remote.git
cd /tmp/fake-remote.git
git init --bare -b main
# 2. 建你的工作仓库
mkdir -p /tmp/my-work
cd /tmp/my-work
git init -b main
git config user.name "Ada Example"
git config user.email "ada@example.com"
# 3. 加个远程指向假远程
git remote add origin /tmp/fake-remote.git
# 4. 提交一个初始版本
printf 'v1\n' > file.txt
git add file.txt
git commit -m "init: 第一个版本"
# 5. 推上去
git push -u origin main
text
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 221 bytes | 221.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To /tmp/fake-remote.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
白话翻译: Git 把 3 个对象推到了远程,远程上有了 main 分支,本地 main 也设置跟踪 origin/main 了。
5. 跟着做:一次完整远程进阶实验
5.1 制造非快进:模拟别人先推了代码
在另一个目录模拟「别人的提交」:
bash
# 建一个「别人的」工作区
mkdir -p /tmp/other-work
cd /tmp/other-work
git clone /tmp/fake-remote.git .
git config user.name "Bob Example"
git config user.email "bob@example.com"
printf 'v2 from Bob\n' > file.txt
git add file.txt
git commit -m "feat: Bob 的修改"
git push
text
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 240 bytes | 240.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To /tmp/fake-remote.git
a1b2c3d..e4f5g6h main -> main
白话翻译: Bob 的提交已经推到远程了,远程 main 现在指向 Bob 的提交。
回到你的工作区,在不知情的情况下也做提交并推送:
bash
cd /tmp/my-work
printf 'v2 from Ada\n' > file.txt
git add file.txt
git commit -m "feat: Ada 的修改"
git push
text
To /tmp/fake-remote.git
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to '/tmp/fake-remote.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
白话翻译: 推送被拒了!因为你本地的 main 落后于远程了------远程有 Bob 的提交,你本地没有。Git 让你先整合远程的改动。
这就是非快进。你遇到了。
5.2 先 fetch 看看远程有什么
bash
git fetch
text
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From /tmp/fake-remote
a1b2c3d..e4f5g6h main -> origin/main
白话翻译: fetch 把远程的提交下载到了本地,更新了 origin/main,但没有动你的工作区和 main 分支。
看看现在的状态:
bash
git log --oneline --graph --all
text
* e4f5g6h (origin/main) feat: Bob 的修改
| * 7a8b9c0 (HEAD -> main) feat: Ada 的修改
|/
* a1b2c3d init: 第一个版本
怎么读: 从共同起点 a1b2c3d 分叉了------远程 origin/main 指向 Bob 的提交,你本地 main 指向你的提交。
5.3 用 pull 整合(merge 方式)
bash
git pull
text
Merge made by the 'ort' strategy.
file.txt | 1 +-
1 file changed, 1 insertion(+), 1 deletion(-)
白话翻译: git pull = git fetch + git merge origin/main。它自动把远程的改动合并进来,产生了一个合并提交。
看图:
bash
git log --oneline --graph --all
text
* 1a2b3c4 (HEAD -> main) Merge remote-tracking branch 'origin/main'
|\
| * e4f5g6h (origin/main) feat: Bob 的修改
* | 7a8b9c0 feat: Ada 的修改
|/
* a1b2c3d init: 第一个版本
注意: 出了合并提交。如果你的团队偏好干净历史,可以考虑用 --rebase。
这次因为两边改了同一个文件同一行,pull 时会出现冲突。解决方式和之前分支合并一模一样------编辑文件、删标记、add、提交。
冲突解决后推送:
bash
git push
text
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 350 bytes | 350.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To /tmp/fake-remote.git
e4f5g6h..1a2b3c4 main -> main
白话翻译: 推上去了。现在远程也有你的提交了。
5.4 用 pull --rebase 代替 pull
如果你想避免合并提交,让历史保持一条直线,用 --rebase:
先把场景重置------假设你又遇到非快进,这次用 rebase 方式:
bash
git fetch
git pull --rebase
text
Rebasing 1 commit onto e4f5g6h
Successfully rebasing and applied 1 commit.
白话翻译: Git 把你的提交「搬」到了远程最新提交的后面,而不是合一个合并提交。历史是一条直线。
bash
git log --oneline --graph --all
text
* 5d6e7f8 (HEAD -> main) feat: Ada 的修改
* e4f5g6h (origin/main) feat: Bob 的修改
* a1b2c3d init: 第一个版本
对比:
| 方式 | 历史形状 | 命令 | |
|---|---|---|---|
git pull |
出现分叉 ` | ` 和合并提交 | git pull |
git pull --rebase |
一条直线 | git pull --rebase |
很多团队约定用 --rebase,这样 git log 更干净。你可以设置默认行为:
bash
# 让 pull 默认用 rebase
git config pull.rebase true
text
(无输出,设置已保存。)
白话翻译: 以后 git pull 就自动用 rebase 模式了,不用每次手动加 --rebase。
5.5 远程跟踪分支是什么
每次 git fetch 后,Git 会在本地记录「远程每个分支当前指向哪」。这些记录就是「远程跟踪分支」。
bash
git branch -r
text
origin/main
白话翻译: -r 列出所有远程跟踪分支。origin/main 就是你本地对「origin 上的 main 分支」的最新记忆。
注意:远程跟踪分支是 只读的 ------你不能直接提交到 origin/main 上,它只在 git fetch 时被动更新。
bash
git branch -vv
text
* main 5d6e7f8 [origin/main] feat: Ada 的修改
白话翻译: -vv 显示本地分支和它跟踪的远程分支的对应关系。你的 main 跟踪 origin/main,目前是同步的。
5.6 --force 为什么危险
假设你做了一个变基(rebase),改变了已经推送过的提交的历史:
bash
printf 'rewritten\n' > file.txt
git add file.txt
git commit --amend -m "feat: 重写提交信息"
现在推送:
bash
git push
text
To /tmp/fake-remote.git
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to '/tmp/fake-remote.git'
被拒了------因为你改写了历史,远程那边的提交和你本下的提交不一样了。
如果你用 --force:
bash
git push --force
text
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To /tmp/fake-remote.git
+ e4f5g6h...9h0i1j2 main -> main (forced update)
成功了------但看看那个 + 号。 它表示远程的提交被你覆盖了。如果 Bob 在这段时间又推了新提交,那些提交就被你抹掉了,找都找不回来(除非 Bob 本地还有)。
白话翻译: --force 就是不管信箱里有什么,全扔掉换成你的。如果别人在信箱里放了信,那些信就没了。
5.7 --force-with-lease:保险绳
--force-with-lease 的逻辑是:
我知道我要强推,但 前提是远程还是我看到的那样子。如果远程有我不知道的新提交,就拒绝。
继续上面的场景,先 fetch 一下确认状态:
bash
git fetch
git push --force-with-lease
text
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To /tmp/fake-remote.git
+ e4f5g6h...9h0i1j2 main -> main (forced update)
推成功了。因为远程确实是你 fetch 时的状态,没有别人的新提交。
现在来模拟危险情况------Bob 又推了新提交:
bash
cd /tmp/other-work
printf 'v3 from Bob\n' > file.txt
git add file.txt
git commit -m "feat: Bob 又推了"
git pull --rebase
git push
回到你的仓库,不 fetch 直接强推:
bash
cd /tmp/my-work
git push --force-with-lease
text
To /tmp/fake-remote.git
! [rejected] main -> main (stale info)
error: failed to push some refs to '/tmp/fake-remote.git'
被拒了------这正是你想要的!
白话翻译: Git 发现远程已经变了(有 Bob 的新提交),但你本地的「记忆」(origin/main)还是旧的,所以拒绝推送。这就是保险绳------它挡住了你覆盖别人代码的操作。
正确的做法:先 fetch,处理完再推。
5.8 远程管理:查看、添加、删除
bash
# 查看所有远程
git remote -v
text
origin /tmp/fake-remote.git (fetch)
origin /tmp/fake-remote.git (push)
白话翻译: -v 显示远程的名字和地址。每个远程有 fetch 和 push 两个地址(大多数时候是一样的)。
bash
# 重命名远程
git remote rename origin upstream
# 再改回来
git remote rename upstream origin
text
(无输出,重命名成功。)
bash
# 删除远程
git remote remove origin
text
(无输出,删除成功。本地不再关联这个远程了。)
bash
# 加回来
git remote add origin /tmp/fake-remote.git
text
(无输出,添加成功。)
5.9 多远程与三角工作流
在开源贡献和课程协作中,常见的工作流是「三角工作流」:
- 你 fork 了老师的仓库到自己的账号(叫
origin) - 老师的仓库叫
upstream - 你从
upstream拉更新,往origin推代码 - 从
origin发 PR 给upstream
bash
# 添加你自己的 fork 为 origin
git remote add origin https://github.com/你的用户名/课件作业.git
# 添加老师的仓库为 upstream
git remote add upstream https://github.com/老师用户名/课件作业.git
# 确认一下
git remote -v
text
origin https://github.com/你的用户名/课件作业.git (fetch)
origin https://github.com/你的用户名/课件作业.git (push)
upstream https://github.com/老师用户名/课件作业.git (fetch)
upstream https://github.com/老师用户名/课件作业.git (push)
白话翻译: 现在你有两个远程。origin 是你自己的,有推送权限;upstream 是老师的,只能拉不能推。
bash
# 从 upstream 拉最新内容
git fetch upstream
# 把 upstream/main 合并到你的本地
git merge upstream/main
# 推到你的 origin
git push origin main
text
(正常推送输出,略。)
这就是三角工作流的日常:从 upstream 取,往 origin 推。
如果你想省点打字,可以设置上游分支:
bash
# 让本地 main 跟踪 origin/main(而不是 upstream/main)
git branch --set-upstream-to=origin/main main
text
branch 'main' set up to track '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 推荐工作流(团队协作)
text
1. 开始干活前:git fetch
2. 整合远程:git pull --rebase
3. 开分支改代码、提交
4. 推之前再 fetch 一次:git fetch
5. 正常推送:git push
6. 如果改写了历史(自己的功能分支):git push --force-with-lease
9. 真实场景
9.1 课程作业:和同学共用一个仓库
- 各自开功能分支,不直接推
main - 提 PR 让助教或组长审
- 审过了再合并
- 如果
push被拒,git pull --rebase再推
9.2 开源贡献:三角工作流
- fork 项目到自己的账号
git remote add upstream 原仓库地址git fetch upstream,git checkout -b fix/拼写错误 upstream/main- 改好提交,
git push -u origin fix/拼写错误 - 在 GitHub 上从你的 fork 发 PR 到 upstream
9.3 推送被拒但急着想推
bash
# 第一步:别慌,先看看远程有什么
git fetch
git log --oneline origin/main..HEAD
text
7a8b9c0 feat: 我的提交
白话翻译: 这条命令显示「你本地有但远程没有」的提交。确认都是你的提交后:
bash
git pull --rebase
git push
9.4 变基后要强推
你在自己的功能分支上做了 git rebase,推送被拒了:
bash
git fetch
git push --force-with-lease origin feature/my-feature
只对你的功能分支用 --force-with-lease,不要对 main 用。
9.5 误推了敏感信息
bash
# 立刻删掉文件并提交
git rm 敏感文件.txt
git commit -m "chore: 删除误推的敏感文件"
# 但它还在历史里!需要彻底重写才行,那是 16 章的内容
# 紧急情况下:让所有人别拉,重写历史后 --force-with-lease 推上去
10. 稍微多懂一点点(可选)
- 远程跟踪分支存在哪:
.git/refs/remotes/origin/main,存的就是一个提交哈希,和本地分支(.git/refs/heads/main)一样的格式,只不过由fetch更新。 --force-with-lease的原理: 它在推的时候把本地的origin/main哈希和远程实际最新的哈希做比较,不一样就拒绝。所以你必须在--force-with-lease之前fetch,保证本地记忆是最新的。- 多个远程的推送策略:
git remote set-url --push origin 禁止推送的地址可以让你git push时不会误推到 upstream。 git push --force-if-includes: Git 2.30 新增的参数,比--force-with-lease更严格------要求你本地已经整合了远程的提交才能强推。两个参数可以一起用。- reflog 保命: 即使被强推覆盖了,远程管理员可能还能从 reflog 找回提交(但前提是还没被垃圾回收)。
11. 小实验(请一定动手)
实验甲:制造并解决非快进
- 用本章第 4 节的方法建一个假远程和两个工作区。
- A 提交并推送,B 也提交并推送(被拒)。
- B 用
fetch看远程状态,再用pull --rebase整合。 - B 推送成功。
实验乙:force-with-lease 拦截
- A 推送一个提交。
- B 也推送(被拒)。
- 不 fetch ,B 直接
git push --force-with-lease------也会被拒! - B
fetch后发现远程有 A 的新提交,整合后再推。 - 验证:如果在 fetch 之后 A 又推了新提交,你的
--force-with-lease仍然会拦截。
实验丙:三角工作流
- 一个仓库当作 upstream,一个仓库当作你的 fork(origin)。
- 从 upstream 拉代码,改完推到 origin。
- 从 origin 发 PR(可以只用命令模拟,不用真上 GitHub)。
通过标准: 能独立处理一次推送被拒,知道什么时候用 --force-with-lease,说得出它比 --force 安全在哪里。
12. 常见问题
问 1:推送被拒是不是我做错了?
不是,是远程有你不知道的新提交。先 fetch 再整合就行。
问 2:fetch 和 pull 到底用哪个?
想先看看远程有什么用 fetch,想一步到位用 pull --rebase。建议设 pull.rebase true 后放心用 pull。
问 3:--force-with-lease 一定能保护我吗?
在你 fetch 之后、别人又推了新的提交这种时间差内,--force-with-lease 可能拦不住 (因为你的本地记忆还是旧的)。但它比 --force 安全得多,绝大多数情况够用了。
问 4:能不能对 main 用 --force-with-lease?
技术上能,但 不应该 。共享分支不应该强推。如果必须回退,用 git revert 更安全。
问 5:pull 出了合并提交怎么办?
下次用 git pull --rebase。设了 pull.rebase true 就不会出了。已有的合并提交如果很烦,可以在下次 rebase 时被压扁。
问 6:origin/main 和 main 有什么区别?
origin/main 是你本地对远程 main 的「记忆」(fetch 时更新),main 是你自己本地的分支。两者不一定指向同一次提交。
问 7:删了 remote 会删代码吗?
只删关联关系(名字和地址),不删本地分支和提交,也不删远程仓库。
问 8:三角工作流和普通推送有什么区别?
普通推送你往 origin 推,origin 就是主仓库。三角工作流你往 origin(你的 fork)推,然后通过 PR 合并到 upstream(主仓库)。你在 upstream 上没有推送权限。
13. 总结、学习路线与思维升华
13.1 这一章请记住的
| 点 | 记住什么 |
|---|---|
| 非快进 | 远程有你没有的提交,推送被拒是 Git 在保护你 |
| fetch vs pull | fetch 只下载,pull = fetch + 整合 |
| pull --rebase | 避免合并提交,保持历史一条线 |
| --force | 危险,直接覆盖别人的提交 |
| --force-with-lease | 安全强推,远程变了就拒绝 |
| 远程跟踪分支 | origin/main 是本地对远程的快照,由 fetch 更新 |
| 多远程 | origin + upstream,三角工作流 |
| 下一章 | 额外命令速查(21 章) |
13.2 在整个系列中的位置
text
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 说明
- git fetch 说明
- git pull 说明
- git remote 说明
- 术语表
- 图示署名:
assets/diagrams/ATTRIBUTION.md
命令输出样例验证环境:Git 2.43.0 ;演示作者信息为虚构:Ada Example <ada@example.com>。
13.5 本章检查清单
- 能说清「非快进」是什么,为什么推送会被拒
- 能区分
fetch和pull - 会用
git pull --rebase保持历史干净 - 知道
--force-with-lease比--force安全在哪里 - 能解释
origin/main是什么 - 会添加和删除远程仓库
- 能说出三角工作流的三个步骤
- 推送被拒时知道该怎么处理
按回车前先预判状态------这一章的保险绳,是你对远程仓库的清醒认识。