经典分支模型(Git Flow)
由 Vincent Driessen 提出的 Git Flow 模型,是管理 main
(或 master)和 dev
分支的经典方案:
main
用于生产发布,保持稳定;dev
用于日常开发,合并功能分支(feature/*);- 功能开发在 feature 分支进行,完成后合并回
dev
; - 预发布分支(release/*)用于测试,测试完成合并到
main
和dev
; -
紧急修复分支(hotfix/*)从
main
拉出,修复后合并回main
和dev
。该模型清晰划分职责,减少冲突,适合中大型项目。
-
GitHub Flow
适合快速迭代和持续部署的项目,只有一个主分支(
main
),所有功能分支直接从main
拉出,完成后通过 Pull Request 合并回main
,保持main
始终可发布状态。适合小团队或需要快速上线的项目。
分支 | 作用 |
---|---|
main |
生产环境分支(已上线) |
dev |
开发主线(功能集成、测试) |
feature/* |
功能开发分支,从 dev 拉 |
release/* |
发布准备分支,从 dev 拉 |
hotfix/* |
紧急修复分支,从 main 拉 |
但是有一部分公司是这样的:【个人觉得超级坑】
项目场景 | Git Flow 方式 | 你现在的方式 | 是否合理 |
---|---|---|---|
正常开发 | 从 dev 拉 feature |
❌ 你从 main 拉 feature |
⚠️ 容易冲突 |
集成测试 | feature 合并到 dev |
✅ 一样 | ✅ 合理 |
正式上线 | dev 合并回 main |
✅ 或 feature → main |
✅ 合理 |
热修复生产问题 | 从 main 拉 hotfix 分支 |
❌ 当前无此逻辑 | 可补充 |
🚨 从慢分支切出一个 feature 分支,再合并到快分支,Git 是如何判断“要不要合并”“能不能合并” 的?是看提交指针,还是 diff?
🔥 Git 在合并时,不是简单对比提交指针,也不是简单对比 diff,而是会寻找“共同祖先(common ancestor)”,然后基于它做一个三方合并(three-way merge)。
🔍 详细说明
假设:
main
(慢分支):只有 A → B 两个提交dev
(快分支):从main
分出,但已有 A → B → C → D → E- 你从
main
切了feat
分支,提交了 F → G
现在你要从 feat
合 PR 到 dev
。
🧠 Git 是怎么做的?
A---B (main)
\
F---G (feat)
/
C---D---E (dev)
- Git 会找到这三个分支的 最近公共祖先(common ancestor):这里是
B
- 然后进行三方合并:
B
(祖先)、E
(当前目标dev
)、G
(你的分支 HEAD) - Git 会计算:
dev
从B
→E
做了什么改动feat
从B
→G
做了什么改动- 把这两个方向的改动 合并 到一起
✅ 所以 Git 合并看什么?
判断项 | 说明 |
---|---|
✅ 共同祖先 | 找出你分支和目标分支的分叉点 |
✅ 各自的 diff | 从分叉点开始的差异 |
❌ 提交指针不直接比较 | 两个分支的 HEAD 不一定要线性关系 |
✅ 是否有冲突 | 如果两边都改了同一行,就冲突 |
假设:
B
中某函数返回"123"
dev
改成了"dev-456"
- 你
feat
改成了"feat-789"
那合并时,就会冲突,因为从共同祖先 B 开始,两边都对这行做了不同的修改。
✅ 为什么你能合进去?
因为:
- 你改的地方
dev
没碰过 → Git 三方合并自动通过 - 哪怕是老分支,只要你改的代码段和
dev
没有冲突,Git 会智能合并 - 不依赖时间先后,也不依赖谁快谁慢,而是看从共同祖先以来谁改了什么
✅ Git 合并靠的是三方合并:找共同祖先 + 比较两边 diff 合并内容,而不是简单看提交指针或谁快谁慢!
✅ 即使你的 feature 分支来自更“慢”的 main 分支,只要你和 dev 的改动没有在相同代码段重叠,Git 都能自动合并。
问题2:
- feat1 和 feat2 都是从 main 分出来的,然后都分别合入 dev;
- 合并后
dev
有了两条变更历史; - feat1 发现 bug,要修复,要怎么做合并?Git 是怎么看这段合并链路的?
A---B (main)
|\ \
| C1(feat1)\ C3 (bugfix)
| \ \ /
| \ \ /
| \ D (dev)
| \ /
| C2(feat2)
|
|
common ancestor = B
初始状态:
- C1:feat1 的改动
- C2:feat2 的改动
- D:dev 合并了 C1 和 C2
🔧 现在 feat1 要修复 bug:
✅ 方案一:继续在 feat1
分支上修
git checkout feat1
- 写 bugfix(提交为 C3)
git merge feat1
→ dev
Git 的处理方式:
- 会做一次三方合并:
- 祖先 =
dev
上一次合并feat1
的版本(即 C1) - 当前 dev 最新 = D
- 你的 feat1 最新 = C3
- 祖先 =
Git 能识别出:
“哦你只是继续往 feat1 分支上加了一个提交,那我只需要合并 C3 的 diff 就行。”
✅ 合并时不会乱套,也不会重复合并旧的提交。
✅ 方案二:新建 hotfix 分支(推荐)
适用于多人协作不希望回到原 feature 分支的情况
git checkout -b hotfix/feat1-bugfix D # 从 dev 最新建出修复分支
# 修复代码,提交 C3
git push origin hotfix/feat1-bugfix
# 提 PR 合并到 dev
这个方式的好处是:
- 不污染原始的
feat1
分支(它已被归档、合并了) - 修复逻辑清晰,一条 commit 路就搞定了
✅ Git 是否会自动判断祖先指针?
是的,Git 合并永远使用 “共同祖先” + “目标分支 HEAD” + “当前分支 HEAD” 做三方合并。
所以:
- Git 会保留链路(commit 记录)
- Git 并不只看最后提交的指针,而是完整计算变更路径
- 合并记录也会清晰显示 “这个分支是从哪来的,做了哪些 diff”
✅ Git 是内容寻址 + 快照系统,不是完整复制代码
✅ 多个分支不会导致仓库体积线性增长
举例:
- 你从
main
分出 20 个分支,每个分支只改了 3 个文件; - Git 仓库里最终只会多出:
- 20 个“分支指针”
- 每个改动的 diff(也叫快照对象)
🧠 Git 使用的是 增量存储(snapshot)+ 内容去重(hash),所以很多内容其实只是复用引用。
操作 | 是否会大幅增加仓库大小 |
---|---|
创建分支 | ❌ 几乎不增加 |
提交小代码改动 | ✅ 增量增长,很小 |
改大文件、多图资源 | ✅ 会增长,需优化 |