← 返回 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)

🗺️ Git远程协作学习路线图 🧭 第一层:懂追踪 远程仓库(origin) vs 本地仓库 vs 远程追踪分支(origin/main) → 三者的关系和只读规则 → 不混淆 🏗️ 第二层:明流程 fetch(只下载) vs pull(下载+合并) vs push(上传) → 内部4步流程 → push被拒怎么办 ⚡ 第三层:会避坑 PR工作流规范 → 远程协作三大陷阱❌✅ → force push/冲突解决/分支管理 → 不翻车 基础概念 核心流程 实战避坑

① 远程引用与追踪

远程仓库就像云端相册——你的本地照片馆(本地仓库)和云端相册(远程仓库)是两个独立的地方。远程追踪分支(origin/main)是云端相册在你本地的"缓存副本"——它告诉你"上次同步时,云端的main指向哪里",但你不能直接改它,只有fetch/pull才能更新它。
远程仓库与追踪分支 ☁️ 远程仓库 (origin) C1 C2 C5 origin/main → C5 💻 本地仓库 C1 C2 C4 main → C4 远程追踪分支(只读) origin/main → C2 (上次fetch的位置) .git/refs/remotes/origin/main origin/main追踪 远程的main
origin/main是远程main在本地的一个"只读镜像",只有fetch能更新它

3种分支的关系

分支类型位置能改吗类比
本地分支 main.git/refs/heads/main✅ 随便改你自己的相册
远程追踪 origin/main.git/refs/remotes/origin/main❌ 只读(fetch更新)云端相册的本地缓存
远程分支(真正的)GitHub/GitLab服务器通过push改云端相册本身
terminal — 远程引用
📁 .git/refs/
📁 heads/
main
📁 remotes/
📁 origin/
main
# 查看远程仓库 $ 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 vs pull 对比 git fetch origin ① 下载远程所有新对象和引用 ② 更新 origin/main 指针 origin/main: C2 → C5 (更新了) 远程追踪分支被更新 main: 还在C4 (没动!) 你的本地分支不受影响 ✅ 安全:只看不改,先审查再合并 git pull origin main ① fetch (下载+更新origin/main) ② merge origin/main → main origin/main: C2 → C5 (更新了) 同fetch main: C4 → M (合并了C5!) 自动合并远程修改 ⚠️ 可能冲突!自动合并可能失败
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 fetchgit 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指针前进到你本地的位置。
git push 内部流程 ① 协商(ref negotiation) 本地告诉远程: "我有C3和C4" ② 传输对象(pack) 把远程缺少的 commit/tree/blob发送 ③ 更新远程ref 远程的main指针 前进到C4 ④ 更新本地origin/main 同步远程追踪 origin/main→C4 ⚠️ push被拒绝的情况 如果远程main已前进到C5,而你本地main还在C2→C4(C4不是C5的后继) 解决:先 git pull (fetch+merge),解决冲突后再 git push 强制push: git push --force(危险!会覆盖远程的C5)
push = 协商差异→传输对象→更新远程ref→同步本地追踪分支
✅ 已了解:push内部4步流程,push被拒时先pull再push
➡️ 接下来看:PR/MR工作流——团队协作的标准姿势

④ PR/MR工作流

PR(Pull Request)就像往公司公告栏投稿——你写了一篇文章(代码改动),提交给编辑(审核者)审核。编辑觉得OK就帮你贴上公告栏(合并到main)。这样保证main上的内容都经过审核,不会有人直接乱贴。
Pull Request 工作流 ① 创建feature分支 git switch -c feature 从main分出来 ② 开发+commit 写代码 git add + commit ③ push到远程 git push -u origin feature 推送到GitHub ④ 创建Pull Request 在GitHub上点按钮 填写描述+审核人 ⑤ Code Review 审核者看代码 提意见/要求修改 ✅ Approve / ❌ Request Changes ⑥ 合并到main 审核通过后 在GitHub上点Merge 或用Squash Merge ⑦ 清理 删除远程feature分支 删除本地feature分支 git fetch --prune PR合并的3种策略 Create a merge commit 保留所有commit历史 git merge --no-ff Squash and merge 多个commit压成1个 历史最干净 ✅推荐 Rebase and merge 线性历史,保留原commit git rebase + merge
PR工作流:feature→push→PR→审核→合并→清理,保证main始终经过审核
✅ 已了解:PR工作流7步流程 + 3种合并策略(Squash推荐)
➡️ 接下来看:远程协作最容易踩的三大陷阱 ❌✅对比

⑤ 远程协作三大陷阱 ❌✅

远程协作最大的坑不是"不会用命令",而是用错了场景——force push覆盖别人代码、merge冲突处理不当、feature分支管理混乱。下面三个陷阱,每个都是血泪教训!

陷阱1: 共享分支force push

❌ bad-rebase-push.sh — 共享分支rebase后force push
📁 project/
terminal
# ❌ 陷阱:在共享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共享分支
📁 project/
terminal
# ✅ 方案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 — 冲突时直接用一方的代码覆盖
📁 project/
terminal
# ❌ 陷阱:遇到冲突直接用--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 — 逐个文件审查,手动合并双方改动
📁 project/
terminal
# ✅ 正确:先看冲突文件,逐个解决 $ 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
📁 project/
terminal
# ❌ 陷阱: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,小步快跑
📁 project/
terminal
# ✅ 正确:每天从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 pushgit 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远程协作速记三原则
← 上一篇:分支与合并 返回目录 →