← 返回 Git内部原理图解 目录

🌿 Git分支与合并图解

branch指针 + fast-forward/3-way merge + rebase vs merge + 冲突解决 · SVG可视化

① 分支的本质

Git的分支就是平行宇宙——你可以在不同的宇宙线里做不同的事,互不干扰。但分支的本质超级简单:它就是一个40字节的文本文件,存着某个commit的SHA-1。创建分支=新建一个文件,移动分支=修改文件内容,删除分支=删掉文件。
分支 = 指向commit的指针 C1 C2 C3 C4 main HEAD→main feature C5 C6 main feature
分支只是指针,main和feature指向不同的commit。创建分支=新建指针,不复制代码
terminal — 分支操作
📁 .git/refs/heads/
main
feature
# 创建分支(只新建一个40字节的文件) $ git branch feature $ cat .git/refs/heads/feature 3f8e9a2b... # ← 和main指向同一个commit! # 切换分支(修改HEAD指向) $ git switch feature $ cat .git/HEAD ref: refs/heads/feature # 在feature上commit(移动feature指针,main不动) $ git commit -m "feat: 新功能" $ cat .git/refs/heads/feature c5a1b2c3... # ← feature前进了 $ cat .git/refs/heads/main 3f8e9a2b... # ← main没动 # 删除分支(删文件) $ git branch -d feature # 删除 .git/refs/heads/feature 文件

② Fast-forward合并

Fast-forward合并就像把日历翻到最新一页——main指针原本落后于feature,直接把main指针"快进"到feature的位置。没有新commit,只是移动了指针。条件:main没有新的commit,feature是main的直接后继。
Fast-forward 合并过程 合并前 C1 C2 C3 main→C2 feature→C3 合并后 C1 C2 C3 main→C3 ✅ feature→C3 Fast-forward特点 ✅ 没有新commit(只是移动main指针) ✅ 线性历史,干净 ⚠️ 看不出"这里做过合并" ⚠️ 条件:main必须没有新commit
Fast-forward:main指针直接"快进"到feature的位置,没有新commit

③ 三方合并

三方合并就像两个平行宇宙碰撞合并——main和feature各有新commit,无法快进。Git找到它们的共同祖先(C2),然后对3个点(C2, main的C4, feature的C3)做三方对比,自动合并不同的改动。最后生成一个merge commit(有2个parent)。
三方合并 (3-way Merge) 过程 合并前:main和feature各有新commit C1 C2 共同祖先 C4 main→ C3 feature→ 三方对比 (3-way diff) C2(基线): README="v1" C4(main): README="v2", +App.java C3(feat): README="v3", +Util.java → 改了不同文件=自动合并 / 改了同一文件同一行=冲突 M merge commit main→M parent1=C4, parent2=C3 🔑 merge commit有2个parent!这就是和普通commit的区别,也是"分叉再合并"的记录 git log --graph 会显示分叉线:* | \ / *
三方合并:找共同祖先→三方对比→自动合并不同改动→生成merge commit(2个parent)

④ Rebase vs Merge

merge = 两张照片拼贴在一起,保留原始时间线,但会多一张拼贴照片。
rebase = 把照片重新排列,让feature的照片看起来是紧接着main拍的——历史变成一条直线,但照片内容其实变了(新的SHA-1)。
Merge vs Rebase 效果对比 git merge feature C1 C2 C4 C3 M ✅ 保留完整历史 ✅ 不改commit SHA ❌ 历史有分叉 ❌ 多一个merge commit git rebase main C1 C2 C4 C3' C3被"移植"到C4之后,变成C3' SHA-1变了!(新的parent→新的commit) ✅ 线性历史,干净 ✅ 没有merge commit ❌ 改了commit SHA ❌ 丢失原始时间线 🤔 怎么选? • 自己的本地分支 → rebase(保持线性,方便rebase -i整理commit) • 已push到远程的共享分支 → merge(不改历史,不影响他人) • 团队规范优先!有些团队只允许merge,有些允许rebase ⚠️ 黄金法则:永远不要rebase已push的commit!
merge保留分叉历史,rebase改写历史变线性。核心区别:是否保留原始commit SHA

⑤ 冲突解决

冲突就像两个人同时改了同一张照片的同一个位置——Git不知道该用谁的版本,只好把两个版本都放出来让你选。冲突标记 <<<<<<< 是Git在说:"这里有两份不同的修改,你来定!"
README.md — 冲突状态
📁 project/
README.md
App.java
# Git 冲突标记格式: <<<<<<< HEAD # ← 你当前分支(main)的版本 版本号: v2.0 # ← main的修改 ======= # ← 分隔线 版本号: v3.0-beta # ← feature的修改 >>>>>>> feature # ← 合并进来的分支名 # 解决步骤: # 1. 手动编辑,选择保留哪个版本(或合并两个) # 2. 删除冲突标记(<<<<<<, =======, >>>>>>) # 3. 保存文件 # 4. git add README.md # 5. git commit (merge时自动完成)

冲突解决4步法

1️⃣ 打开冲突文件 2️⃣ 选择保留内容 3️⃣ 删除冲突标记 4️⃣ add + commit
# 查看哪些文件冲突了 $ git status Unmerged paths: both modified: README.md # ← 冲突文件 # 用IDE打开冲突文件,解决后: $ git add README.md $ git commit # 如果想放弃合并,回到合并前: $ git merge --abort
减少冲突的技巧:1. 频繁从main拉取更新 git fetch + git rebase origin/main;2. 小步提交,别攒一大堆;3. 不同人改不同文件/模块;4. 沟通!改动公共文件前先告知。

⑥ 面试速答

Git分支的本质是什么?

分支就是一个指向commit的指针,存储在 .git/refs/heads/ 下的文本文件中,内容只有40个字符(commit的SHA-1)。创建分支=新建一个文件,O(1)操作。删除分支=删文件。切换分支=修改HEAD指向。

Fast-forward合并和三方合并的区别?

Fast-forward:当前分支(如main)没有新commit,合并只是把main指针移动到feature的位置。不产生新commit,历史是线性的。
三方合并:两个分支都有新commit。Git找共同祖先,做3-way diff,自动合并不同改动,产生merge commit(有2个parent)。

rebase和merge的区别?什么时候用哪个?

merge:保留完整历史(含分叉),不改commit SHA,产生merge commit。适合已push的共享分支。
rebase:改写历史变线性,不产生merge commit,但commit SHA会变。适合本地未push的分支。
核心原则:不要rebase已push的commit(会让别人的历史混乱)。

为什么rebase后commit的SHA-1会变?

因为commit的SHA-1计算包含了parent字段。rebase把C3"移植"到C4之后,C3的parent从C2变成了C4。parent变了→SHA-1就变了→这是一个"新"commit。虽然代码改动一样,但Git认为它是不同的对象。

merge commit和普通commit的区别?

普通commit只有1个parent。merge commit有2个parent(分别指向两个被合并的分支的最新commit)。用 git cat-file -p <merge-commit> 可以看到两行parent。git log --graph 会显示分叉和合并线。

Git冲突是怎么产生的?怎么解决?

两个分支修改了同一个文件的同一行,且改动内容不同,Git无法自动决定用哪个版本。
解决步骤:1.打开冲突文件 2.选择保留哪个版本(或合并两者) 3.删除 <<<<<<</=======/>>>>>>> 标记 4.git add 5.git commit
想放弃合并:git merge --abort

git cherry-pick是什么?和merge/rebase有什么不同?

cherry-pick = 从别的分支"摘"一个commit过来,应用到当前分支。
和merge的区别:merge把整个分支合过来,cherry-pick只选一个commit。
和rebase的区别:rebase把整条分支"移植"过来,cherry-pick只挑一个。
典型场景:hotfix修了一个bug,需要同时应用到main和release分支。

什么是detached HEAD?怎么处理?

HEAD直接指向一个commit而不是分支名,就是detached HEAD状态。常见于 git checkout <SHA>git checkout v1.0
在这个状态下做的commit,切走后没有分支指着它,可能被GC清理掉。
处理:在当前位置创建新分支 git switch -c new-branch,或者如果只是查看代码,看完切回分支即可。

← 上一篇:工作流与暂存区 下一篇:Git远程协作 →
核心三原则速记

Git三原则:① 提交信息写清楚为什么改而不是改了什么 → ② 永远不在共享分支force push → ③ 合并前先rebase保持线性历史