简介
学习完了分支之后,合并又是什么呢?
只要你执行:
git merge <远程\或者本地的某个分支>
Git 就会尝试:
把“指定的提交历史”整合进当前分支。
说白了,merge就是两条不同的历史,进行整合,merge 是发生在“两条不同的提交历史线之间”
merge 本质总结(非常关键)
merge 的本质不是:
合并分支
而是:
合并两个 commit 节点的历史
分支只是:
指向 commit 的标签
一个更准确的理解方式:
merge 发生的真正条件是:
当前 HEAD 指向的 commit
和你要 merge 的 commit
不在同一条线性历史上
只要不是直线关系,就需要三方合并。
1.merge做了什么?
看图:
A --- B --- C (master)
\
D --- E (dev)
现在你在 master 上执行:
git merge dev
Git 会:
- 找到两条线的共同祖先(B)
- 计算差异
- 生成一个新的 commit
结果:
A --- B --- C -------- F (master)
\ /
D --- E ---- (dev)
这个 F 就叫:
merge commit
它有两个 parent:
- 一个指向 C
- 一个指向 E
这就是真正意义上的“合并”。
Git merge 的核心步骤是:
1️⃣ 找共同祖先(merge base)
2️⃣ 比较三份内容:
- 祖先版本(才能计算差异)
- 当前分支版本
- 要合并分支版本
3️⃣ 自动合并
4️⃣ 如果冲突 → 让你手动解决
这叫:
三方合并(three-way merge)
2.为什么 merge 叫“合并历史”?
关键点:
merge commit 会保留两条历史线。
看图:
D --- E
/ \
A --- B --- C ----- F
从 F 可以看到:
- 一条 parent 指向 C
- 一条 parent 指向 E
历史是完整保留的。
3.三种merge场景
1)场景 1:不同分支之间(最常见)
A --- B --- C (master)
\
D --- E (dev)
你在 master 上:
git merge dev
这是典型的“两个分支合并”。并产生一个merge commit
必须满足:
1️⃣ 两条分支都各自有新的 commit
2️⃣ 当前分支不是对方(目标分支)的祖先
一定会产生 merge commit。
2)场景 2:合并远程分支
git merge origin/master
这里:
- 当前分支master
- origin/master
虽然 origin/master 不是“本地分支”,
但本质仍然是两条提交线。
只要对应的远程分支上,有新的提交,就和场景1是一样的
3)场景 3:merge 一个具体 commit
1)git merge commit-hash
例如:
git merge a1b2c3d
意思是:
把这个 commit(以及它的历史)合并到当前分支。
本质理解(非常关键)
Git merge 的本质是:
把“某个提交节点”整合进当前 HEAD。
它不是“只拿这个 commit 的修改”。
它是:
以这个 commit 为末端的那条历史线。
举例说明
假设现在仓库是:
A --- B --- C --- D (master)
\
E --- F --- G (feature)
你当前在:
master
现在你执行:
git merge F (注意:F 是 feature 分支上的一个 commit(不是分支名))
找到当前 HEAD(D)
找到目标 commit(F)
找到它们的共同祖先(B)
G 是在 F 之后的提交,
merge F 不会包含 G。
因为你 merge 的是“截止到 F 的历史”。
结果:
A --- B --- C ---D--- H (master)
\ /
E -------- F --- G
H 的第二个 parent 指向 F,
但 G 不会被合入。
有可能是到F是测试好的,稳定的功能版本,之后的还没测试,就属于这类场景的合并
2)cherry-pick
如果你只想要:
某个 commit 的改动
而不想引入整条历史
应该用:
git cherry-pick <commit>
一句话定义
git cherry-pick = 把某一个 commit 的“改动内容”复制到当前分支,生成一个新的 commit。
注意:
✔ 是“复制改动”
❌ 不是“合并历史”
例子
当前仓库结构:
A --- B --- C (master)
\
D --- E --- F (feature)
你现在在:master
你只想要:
feature 上的 E 这个改动
但不想合并整个 feature 分支。
你执行:
git cherry-pick E
结果变成:
A --- B --- C --- E' (master)
\
D --- E --- F (feature)
注意:
E' 是一个 新的 commit。
相当于你在 master 上创建了一个新提交,
它的 parent 是 C,
不是 D。
所以 hash 必然不同。
内部流程是:
1️⃣ 取出 commit E
2️⃣ 计算它相对 parent 的 diff
3️⃣ 把这个 diff 应用到当前 HEAD
4️⃣ 生成新的 commit
本质是:
应用补丁
热修复回移:线上有 bug。
你在 master 上修复:然后你需要把这个修复带回 develop,就需要用到这种场景
一句话总结:
merge 是“合并历史”
cherry-pick 是“复制改动”
一个是结构操作,
一个是补丁操作。
4.一种不产生merge commit的合并
如果当前分支是目标分支的祖先:
A --- B --- C (master)
\
D --- E (dev)
在 master 上:
git merge dev
会变成:
A --- B --- C --- D --- E
这种就叫 fast-forward
本质不是创建 merge commit,只是移动指针。所以这种合并不会产生 merge commit
这种合并,后续在rebase一篇中,再继续探讨