通常在合并分支时,一般情况下Git会采用 Fast forward
模式。还记得如果我们采用 Fast forward
模式之后,形成的合并结果是什么呢?
在这种 Fast forward
模式下,删除分支后,查看分支历史时,会丢掉分支信息,看不出来最新提交到底是 merge 进来的还是正常提交的。
但在合并冲突部分,我们也看到通过解决冲突问题,会再进行一次新的提交,得到的最终状态为
那么这就不是 Fast forward
模式了,这样的好处是,从分支历史上就可以看出分支信息。
dev1
分支,但依旧能看到 master 其实是由其他分支合并得到,如下:lighthouse@VM-8-10-ubuntu:gitcode$ git log --graph --pretty=oneline --abbrev-commit
* cb7ce27 (HEAD -> master) merge book
|\
| * da3c3b1 modify book
* | fc26892 modify book
|/
Git 支持我们强制禁用 Fast forward
模式,那么就会在 merge 时生成一个新的 commit
,这样从分支历史上就可以看出分支信息。
下面我们实战一下 --no-ff
方式的 git merge
。首先,创建新的分支 dev2
,并切换至新的分支
lighthouse@VM-8-10-ubuntu:gitcode$ git checkout -b dev2
Switched to a new branch 'dev2'
修改 book
文件,并且提交一个新的 commit
lighthouse@VM-8-10-ubuntu:gitcode$ cat book
Hello Island1314
Hello World
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d
lighthouse@VM-8-10-ubuntu:gitcode$ git add .
lighthouse@VM-8-10-ubuntu:gitcode$ git commit -m "modify Readme"
[dev2 2bd7b8b] modify Readme
1 file changed, 1 insertion(+)
切换 master
分支,进行合并
lighthouse@VM-8-10-ubuntu:gitcode$ git checkout master
Switched to branch 'master'
lighthouse@VM-8-10-ubuntu:gitcode$ git merge --no-ff -m "merge with no-ff" dev2
Merge made by the 'ort' strategy.
book | 1 +
1 file changed, 1 insertion(+)
lighthouse@VM-8-10-ubuntu:gitcode$ cat book
Hello Island1314
Hello World
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d
请注意
--no-ff
参数,表示禁用Fast forward
模式。禁用Fast forward
模式后合并会创建一个新的 commit,所以加上-m 参数,把描述写进去。
合并后,查看分支历史:
lighthouse@VM-8-10-ubuntu:gitcode$ git log --graph --pretty=oneline --abbrev-commit
* ac37cfb (HEAD -> master) merge with no-ff
|\
| * 2bd7b8b (dev2) modify Readme
|/
* cb7ce27 merge book
|\
| * da3c3b1 modify book
* | fc26892 modify book
|/
* 33a5368 modify book
可以看到,不使用 Fast forward
模式,merge 就像如下:
所以在合并分支时,加上 --no-ff
参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而 fast forward
合并就看不出来曾经做过合并。
在实际开发中,我们应该按照几个基本原则进行分支管理:
master
分支发布1.0版本;你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了
所以,团队合作的分支看起来就像如下这样:
命令示例:
示例1:
git merge --ff-only dev # 强制仅快进合并
示例2:
# 强制创建合并提交(即使可以快进)
git merge --no-ff feature/login
# 查看合并后的线性历史
git log --graph --oneline --decorate
示例3:错误演示
# 错误:在已提交的主分支使用快进合并导致历史混乱
git checkout main
git merge --ff-only hotfix # 若main有新提交则会失败
选项参数 | 作用描述 | 适用场景 |
---|---|---|
-X patience | 改进差异比对算法 | 复杂代码重构合并 |
-X ignore-space | 忽略空格变化 | 跨平台开发合并 |
-X renormalize | 统一行尾风格 | 混合Windows/Linux环境 |
-X diff-algorithm=histogram | 更准确的历史分析 | 大型Java项目合并 |
命令示例:
git merge -s recursive -X patience dev # 使用特定差异算法
命令示例:
示例1:使用规范
# 正确流程示例(架构迁移场景):
git checkout new-arch
git merge -s ours old-arch # 保留新架构
git checkout old-arch
git merge new-arch # 同步代码但保留旧架构
示例2:危险操作告警
# 错误示例:直接覆盖生产分支
git checkout production
git merge -X theirs staging # 可能导致生产数据丢失!
触发条件:同时合并多个分支
命令示例:
git merge dev feat hotfix # 一次性合并三个分支
图示:
Subtree | Submodule | |
---|---|---|
存储方式 | 代码直接入库 | 引用外部仓库 |
历史追踪 | 完整保留 | 仅记录引用点 |
修改流程 | 直接修改+推送 | 进入子模块操作 |
适用场景 | 频繁修改的组件 | 稳定的第三方依赖 |
触发条件:合并外部仓库到子目录
命令示例:
git merge -s subtree --allow-unrelated-histories external-repo
图示:
触发条件:简单分叉合并(Git 早期版本)
命令示例:
git merge -s resolve legacy-branch
图示:
策略 | 合并类型 | 冲突处理 | 历史记录 | 适用场景 | 风险等级 |
---|---|---|---|---|---|
Fast Forward | 线性移动 | 无冲突 | 完全线性 | 短期特性分支合并 | ⭐ |
Recursive | 三方合并 | 自动+手动 | 合并提交 | 标准分支合并 | ⭐⭐ |
Ours/Theirs | 选择性覆盖 | 强制解决 | 隐藏分叉 | 保留分支结构 | ⭐⭐⭐⭐ |
Octopus | 多分支合并 | 必须无冲突 | 单点聚合 | 批量合并已验证分支 | ⭐⭐ |
Subtree | 目录级合并 | 路径自动处理 | 独立子历史 | 第三方库集成 | ⭐⭐⭐ |
Resolve | 简单三方合并 | 手动解决 | 合并提交 | 简单分叉合并(旧版本兼容) | ⭐⭐ |
Recursive
(默认) + --no-ff
保留合并痕迹Fast Forward
快速上线Ours
策略保留新架构Octopus
一次合并多个已验证分支Subtree
管理子仓库Resolve
处理简单合并# 查看可用的合并策略
git merge -s help
# 显示合并细节(调试用)
git merge --verbose --stat -m "合并日志"
# 分析合并基准
git merge-base --all master dev
# 可视化合并过程
git mergetool -t meld
分支治理规范:
--no-ff
合并合并策略决策树
🔥实际开发中需要根据项目需求选择最合适的合并方式。建议在团队中制定《合并策略规范》,例如长期分支强制使用 --no-ff
,第三方库必须用 subtree
等,以保持代码库的健康度。