← 返回 Git内部原理图解 目录
🌐 Git远程协作图解
remote tracking + fetch/pull/push流程 + PR工作流 + 多人协作避坑 · SVG可视化
🔴 真实事件:git push --force 覆盖了同事3天的代码
周五下午,小王在本地rebase了feature分支后,习惯性地执行了 git push --force。
17:30 小王push --force,远程feature分支被覆盖
17:45 小李git pull报错:"Your branch and 'origin/feature' have diverged"
18:00 小李发现自己昨天push的3个commit在远程消失了!本地还有但远程没了
18:30 只能靠小李本地的git reflog找回commit hash,手动cherry-pick恢复
💡 教训:git push --force = 覆盖远程历史,共享分支绝对不要force push!如果必须rebase已推送的分支,用 git push --force-with-lease(至少会检查远程有没有别人的新commit)
① 远程引用与追踪
远程仓库就像云端相册——你的本地照片馆(本地仓库)和云端相册(远程仓库)是两个独立的地方。远程追踪分支(origin/main)是云端相册在你本地的"缓存副本"——它告诉你"上次同步时,云端的main指向哪里",但你不能直接改它,只有fetch/pull才能更新它。
origin/main是远程main在本地的一个"只读镜像",只有fetch能更新它
3种分支的关系
| 分支类型 | 位置 | 能改吗 | 类比 |
| 本地分支 main | .git/refs/heads/main | ✅ 随便改 | 你自己的相册 |
| 远程追踪 origin/main | .git/refs/remotes/origin/main | ❌ 只读(fetch更新) | 云端相册的本地缓存 |
| 远程分支(真正的) | GitHub/GitLab服务器 | 通过push改 | 云端相册本身 |
terminal — 远程引用
# 查看远程仓库
$ git remote -v
origin https://github.com/user/project.git (fetch)
origin https://github.com/user/project.git (push)
# 查看远程追踪分支
$ git branch -r
origin/main
origin/feature
# 查看追踪关系
$ git branch -vv
* main 3f8e9a2 [origin/main] feat: 添加功能
feature abcd123 [origin/feature] feat: 新模块
# 本地main和远程origin/main的追踪关系
# 存在 .git/config 的 [branch "main"] 节
# remote = origin, merge = refs/heads/main
✅ 已了解:本地分支/远程追踪分支/远程分支三者的关系,origin/main是只读缓存
➡️ 接下来看:fetch和pull的区别——什么时候用哪个
② fetch vs pull
fetch = 只下载云端的最新目录,看看别人加了什么照片,但不往你的相册里放。
pull = fetch + merge,先下载最新目录,再自动合并到你的相册里。
fetch=只下载不合并(安全),pull=fetch+merge(方便但可能冲突)
推荐工作流:先fetch再merge
# 方式1:分步(推荐,更安全)
$ git fetch origin # 下载远程更新
$ git log --oneline origin/main # 看看别人改了什么
$ git merge origin/main # 确认后再合并
# 方式2:一行搞定(方便但粗暴)
$ git pull origin main # = fetch + merge,可能直接冲突
# 方式3:pull with rebase(更干净)
$ git pull --rebase origin main # = fetch + rebase,线性历史
fetch vs pull的选择策略:
• 团队协作:先git fetch再git log --oneline origin/main看看别人的改动,确认没问题再git merge——永远比直接pull安全
• 个人分支:直接git pull --rebase就行,反正只有你自己
• 口诀:共享分支先fetch看一眼,个人分支直接pull --rebase
✅ 已了解:fetch只下载不合并(安全) vs pull=fetch+merge(方便但可能冲突)
➡️ 接下来看:push的内部4步流程和被拒绝怎么办
③ push内部流程
git push就像把你的新照片上传到云端——把本地没有的commit对象发给远程,然后让远程的main指针前进到你本地的位置。
push = 协商差异→传输对象→更新远程ref→同步本地追踪分支
✅ 已了解:push内部4步流程,push被拒时先pull再push
➡️ 接下来看:PR/MR工作流——团队协作的标准姿势
④ PR/MR工作流
PR(Pull Request)就像往公司公告栏投稿——你写了一篇文章(代码改动),提交给编辑(审核者)审核。编辑觉得OK就帮你贴上公告栏(合并到main)。这样保证main上的内容都经过审核,不会有人直接乱贴。
PR工作流:feature→push→PR→审核→合并→清理,保证main始终经过审核
✅ 已了解:PR工作流7步流程 + 3种合并策略(Squash推荐)
➡️ 接下来看:远程协作最容易踩的三大陷阱 ❌✅对比
⑤ 远程协作三大陷阱 ❌✅
远程协作最大的坑不是"不会用命令",而是用错了场景——force push覆盖别人代码、merge冲突处理不当、feature分支管理混乱。下面三个陷阱,每个都是血泪教训!
陷阱1: 共享分支force push
❌ bad-rebase-push.sh — 共享分支rebase后force push
# ❌ 陷阱:在共享feature分支上rebase然后force push
$ git checkout feature/login
$ git rebase main # rebase后commit hash变了
$ git push origin feature/login
To github.com:user/project.git
! [rejected] feature/login -> feature/login (non-fast-forward)
$ git push --force origin feature/login # ← 💀 覆盖远程!
# 小李昨天push的3个commit在远程消失了
# 小李下次pull会发现自己本地有远程没有的commit
# 团队一片混乱
✅ good-rebase-push.sh — 用force-with-lease或避免rebase共享分支
# ✅ 方案1:共享分支用merge而不是rebase
$ git checkout feature/login
$ git merge main # merge保留原始commit hash
$ git push origin feature/login # fast-forward,不会覆盖别人
# ✅ 方案2:如果必须rebase,用 --force-with-lease
$ git rebase main
$ git push --force-with-lease origin feature/login
# --force-with-lease 会检查远程有没有别人的新commit
# 如果有人在你rebase期间push了新commit → 会被拒绝 → 安全!
# ✅ 方案3:黄金法则
# 个人分支:随便rebase + force-with-lease
# 共享分支:永远用merge,不rebase
陷阱2: 合并冲突解决不当
❌ bad-conflict.sh — 冲突时直接用一方的代码覆盖
# ❌ 陷阱:遇到冲突直接用--ours或--theirs
$ git pull origin main
CONFLICT (content): Merge conflict in src/UserService.java
# 错误做法1:全部用我的
$ git checkout --ours src/UserService.java
$ git add .
$ git commit -m "fix: 解决冲突"
# ← 别人的代码全丢了!你根本没看冲突内容!
# 错误做法2:全部用别人的
$ git checkout --theirs src/UserService.java
# ← 你自己的代码全丢了!
✅ good-conflict.sh — 逐个文件审查,手动合并双方改动
# ✅ 正确:先看冲突文件,逐个解决
$ git pull origin main
CONFLICT (content): Merge conflict in src/UserService.java
# 第1步:看哪些文件冲突
$ git status # 列出unmerged paths
# 第2步:用IDE或编辑器打开冲突文件
# 文件里会有:
# <<<<<<< HEAD
# 你的代码(当前分支)
# =======
# 别人的代码(远程分支)
# >>>>>>> origin/main
# 第3步:手动合并两边改动,保留正确逻辑
$ code src/UserService.java # 用VS Code打开,有冲突高亮
# 第4步:标记已解决
$ git add src/UserService.java
$ git commit -m "fix: 合并远程main,保留双方改动"
# 口诀:冲突不是"选一边",而是"合并两边"
陷阱3: feature分支太久不pull,合并时冲突爆炸
❌ bad-workflow.sh — feature分支开发2周不pull
# ❌ 陷阱:feature分支2周不合并main的更新
$ git checkout -b feature/big-module # 周一创建
# ... 开发了2周,50个commit ...
# 期间main分支前进了40个commit,改了100个文件
$ git checkout main
$ git merge feature/big-module
CONFLICT in 30 files!!!
# 30个文件冲突,根本不知道怎么合并
# 每个冲突都要看双方改动,工作量巨大
# 可能1天都解决不完
✅ good-workflow.sh — 每天rebase/merge main,小步快跑
# ✅ 正确:每天从main合并最新改动
$ git checkout feature/big-module
# 每天早上开发前,先同步main
$ git fetch origin
$ git merge origin/main # 或 git rebase origin/main
# 如果有冲突,每次只有2-3个文件,5分钟搞定
# 继续开发
$ git add . && git commit -m "feat: xxx"
# 开发完成后,PR合并时冲突很少
# 因为每天已经同步了main的更新
# 口诀:feature分支要"勤同步",不要"攒大冲突"
# 每天merge一次 = 每次冲突5分钟
# 2周不merge = 一次冲突1天+
📋 远程协作三大陷阱速查表
| 陷阱 | ❌ 错误做法 | ✅ 正确做法 | 一句话口诀 |
| 共享分支force push | git push --force覆盖别人代码 | 共享分支用merge,个人分支用--force-with-lease | 共享不force,force不共享! |
| 冲突解决粗暴 | git checkout --ours/--theirs 一刀切 | 打开冲突文件,逐个审查合并双方改动 | 冲突不是选一边,是合并两边! |
| feature分支太久不同步 | 2周不merge main,最后冲突爆炸 | 每天merge origin/main,小步同步 | 勤同步,不攒冲突! |
远程协作黄金法则:
1. main分支:永远通过PR合并,不直接push
2. 共享feature分支:用merge不同rebase,避免覆盖别人的commit
3. 个人feature分支:每天rebase origin/main保持同步
4. force push:个人分支用--force-with-lease,共享分支绝对不用
✅ 已了解:远程协作三大陷阱 ❌✅对比 + force push/冲突/同步的避坑策略
➡️ 最后:面试中Git远程协作的高频问题速答
⑥ 面试速答
git fetch和git pull的区别?
git fetch:只下载远程的新commit和对象,更新远程追踪分支(origin/main),不修改本地分支。安全,可以先审查再合并。
git pull:= git fetch + git merge。下载后自动合并到当前本地分支。方便但可能产生冲突。
推荐用法:git fetch → 审查 → git merge(或 git rebase)
git push被拒绝了怎么办?
push被拒绝是因为远程有你的本地没有的新commit(别人push了)。解决:
1. git pull --rebase origin main — 拉取远程更新并rebase你的commit
2. 解决可能的冲突
3. git push origin main — 再推一次
⚠️ 不要用 --force,除非你确定要覆盖远程历史(如rebase后已push的分支)。
origin/main和main有什么区别?
main:本地分支,你可以直接修改(commit/merge/rebase)。
origin/main:远程追踪分支,只读!它记录的是"上次fetch/pull时远程main的位置"。只有fetch能更新它。
当 origin/main 和 main 指向不同的commit时,说明你的本地和远程不同步,需要 pull/push。
什么是git push -u?
-u(--set-upstream)设置上游追踪关系。首次push一个新分支时需要用:
git push -u origin feature
这会做两件事:1.push代码 2.在本地feature和远程origin/feature之间建立追踪关系。
之后直接 git push 就行,不需要再写全 git push origin feature。
Squash Merge是什么?和普通merge有什么区别?
Squash Merge把feature分支的多个commit压成一个commit再合并到main。
优点:main历史干净,一个功能一个commit
缺点:丢失了详细的开发过程
普通merge:保留所有commit历史,但main上会有很多"fix typo"之类的琐碎commit。
团队实践:feature分支随便写commit,PR时用Squash Merge合并到main——兼顾灵活性和干净历史。
多人协作的最佳实践?
1. 永远在feature分支开发,不直接push main
2. 频繁fetch,保持本地和远程同步
3. 小步PR,一个功能一个PR,不要攒一大堆
4. Code Review,所有合并必须经过审核
5. rebase本地分支保持线性,merge共享分支保留历史
6. 写好commit message,遵循约定式提交(Conventional Commits)
git push --force和--force-with-lease有什么区别?
--force:无条件覆盖远程分支,不管远程有没有别人的新commit。危险!
--force-with-lease:先检查远程分支是否还是你上次看到的那个状态。如果有人在你之前push了新commit,会被拒绝。安全!
类比:--force = 不看就闯红灯,--force-with-lease = 先看一眼有没有车再过。
使用场景:rebase后的个人分支push用--force-with-lease,共享分支永远不用force。
PR合并时3种策略怎么选?
Create a merge commit(--no-ff):保留完整历史和分支结构。适合需要追溯每个功能开发过程的场景。
Squash and merge:多个commit压成1个。适合feature分支commit较杂("fix typo"等)的场景。团队最常用✅
Rebase and merge:线性历史,保留原commit但重写hash。适合想要干净线性历史且保留开发细节的场景。
实际选择:大多数团队用Squash Merge,因为feature分支的commit不需要保留在main上——一个功能一个commit最干净。
🎯 Git远程协作速记三原则
- 先fetch再merge:共享分支先看一眼别人的改动再合并,别直接pull
- 共享不force:共享分支用merge不同rebase,force push只用于个人分支且用--force-with-lease
- 勤同步不攒冲突:feature分支每天merge origin/main,不要2周不同步最后冲突爆炸