本文参考文章:A successful Git branching model » nvie.com,作者是Vincent Driessen
本文在原文的基础上做了本地化以及一部分现代化改动,例如将文章中的主分支名
master
改为现在更为常用的main
等,同时配合Fork做一些实操介绍。
阅读本文前,建议先阅读文章“【保姆级教程】Git客户端Fork的基础使用 | 青江的个人站”以了解Git与Fork的基础用法。
1. 为什么要使用标准Git分支模型
使用标准Git分支模型的核心作用是为代码的集成、测试和发布建立清晰可控的工作流程,避免非标准分支导致的开发混乱、出现问题时难以排查或回溯的情况。
通过定义如长期稳定的main
主干分支、临时性的功能开发分支(feature
)、预发布测试分支(release
)以及线上问题紧急修复分支(hotfix
)等角色,该模型将不同开发阶段和目的的工作有效隔离,避免相互干扰;这使得团队成员能够安全地并行推进多个任务,同时始终保证主干分支的代码处于可随时发布的稳定状态,从而显著降低集成冲突风险、提升软件交付质量与效率,并为版本追踪、问题排查及协作沟通提供了结构化的历史记录基础,最终实现开发过程的有序性和可靠性。
下面是一个完整的标准Git分支模型示意图:
2. 去中心化但集中
对于同一个项目,所有本地的分仓库都会指向同一个远程仓库,这个仓库被认为是中央仓库(事实上,由于Git是一个分布式版本控制系统,因此在技术层面上并不存在中央仓库)。我们将这个仓库称为origin
,因为这个名称对所有Git用户都很熟悉。
每个开发者都会从origin
拉取和推送。
但除了集中的推拉关系外,每个开发者还可能从其他同伴那里拉取变更,从而形成子团队。例如,这可能在将进行中的工作推送到origin
之前,与两个或更多开发者一起合作开发一个大型新功能时很有用。在上面的图中,有Alice和Bob的子团队,Alice和David的子团队,以及Clair和David的子团队。
从技术上讲,这不过是Alice定义了一个名为bob
的Git远程仓库,指向Bob的仓库,反之亦然。
3. 主分支(main branches)
在核心上,开发模型深受现有模型的影响。中央仓库包含两个具有无限生命周期的主分支:
- main
- develop
main
分支在origin
中应该对每个Git用户都很熟悉。与main
分支平行,还存在另一个名为develop
的分支,分支示意图如下:
当我们认为origin/main
是主分支时,其中HEAD
的源代码始终反映项目的就绪状态。
当我们认为origin/develop
是主分支时,其中HEAD
的源代码始终反映最新交付的开发变更状态,为下一个发布做准备。有些人会称其为“集成分支(integration branch)”。这是自动夜间构建(automatic nightly builds)的构建来源。
当develop
分支中的源代码达到稳定点并准备好发布时,所有变更应该以某种方式合并回main
,然后标记上发布编号。具体如何操作将在后续讨论。
因此,每次将变更合并回main
时,根据定义这就是一次新的生产发布。在这方面通常非常严格,因此理论上,我们可以在main
上每次提交时使用Git钩子脚本自动构建并部署我们的软件到生产服务器。
对于Fork,可以直接点击新建分支按钮,新建一个名为develop
的分支:
勾选“Check out after create”时,创建分支后会自动切换到新分支,也可以创建后双击切换分支。
此时可以在新的develop
分支上做一些新功能的开发,并在合适的时间将某一版本发布到main
分支。
此时模拟本地在develop
分支上开发了一个新功能:
点击Push,勾选“Create tracking reference”设置本地的develop
分支跟踪远程的develop
分支,最后点击Push推送本地分支和改动:
Push成功后可以发现远程分支中新增了一个develop
分支:
将鼠标悬停在本地的develop
分支上,可以看到这个分支已经跟踪到了远程的origin/develop
分支。
4. 支持分支(Supporting branches)
在主分支master
和develop
旁边,标准Git分支模型使用多种辅助分支来帮助团队成员并行开发、简化功能跟踪、为生产发布做准备,并协助快速解决生产环境中的问题。与主分支不同,这些分支始终有有限的生命周期,因为它们最终会被删除。
我们可能使用的分支类型有:
- 特性分支(Feature branches)
- 发布分支(Release branches)
- 热修复分支(Hotfix branches)
每个分支都有特定的用途,并且严格规定哪些分支可以是它们的源分支,哪些分支必须是它们的合并目标。稍后会逐一介绍。
从技术角度来看,这些分支绝非“特殊”。分支类型的划分取决于我们的使用方式。它们当然只是普通的 Git 分支。
注意:本文分支模型中的支持分支命名已过时,实际上,现代更常用的支持分支应该用
/
代替-
来命名,例如:
- 特性分支(Feature branches):
feature/*
- 发布分支(Release branches):
release/*
- 热修复分支(Hotfix branches):
hotfix/*
这样命名更为直观,且在Fork中,使用
/
命名可以自动创建文件夹来将同一类型的分支进行归类。
4.1 特性分支(Feature branches)
此分支可以从develop
分支的基础上产生,并必须合并回develop
分支。
分支的命名可以是除了main
、develop
、release-*
或者是hotfix-*
之外的所有内容。
特性分支(Feature branches)(有时也称为主题分支(topic branches))用于开发即将到来或遥远未来的新功能。在开始开发一个特性时,这个特性将被整合的目标发布版本在那个时刻可能还未知。
特性分支的本质在于它存在于功能开发期间,但最终会合并回develop
(以将新功能明确添加到即将发布的版本)或被丢弃(如果实验结果令人失望)。
特性分支通常只存在于开发者仓库中,而不是在origin
中,也就是通常不应该将特性分支推送到远端。
下面是使用Fork创建并合并一个特性分支的完整示例。
4.1.1 创建特性分支
开始开发新特性时,从develop
分支分出新分支。
选中最新的develop
分支,点击分支创建按钮创建新分支feature_2
。
4.1.2 开发新特性
此时模拟一个功能的开发过程,在新的feature_2
分支上创建一些commit。
4.1.3 将完成的特性合并到develop
分支
完成的特性可以合并到develop
分支,以正式添加到即将发布的版本中。
4.1.3.1 双击切换到想要合并到的分支
例如此时想要将feature_2
分支合并到develop
分支,就双击切换到develop
分支。
4.1.3.2 合并分支
右键需要合并的分支,选择“Merge into ‘develop’”选项。
在弹出的Merge页面将“Merge Option”更改为“No Fast-Forward”,也就是为Merge添加--no-ff
标志。
Merge操作出现冲突时,需要先解决一下代码的冲突再进行Merge。
--no-ff
标志会导致合并始终创建一个新的提交对象,即使合并可以通过变基来完成。这避免了丢失关于特性分支历史存在的信息,并将所有共同添加了特性的提交归集在一起。比较:
在后者的情况下,从Git历史中无法看出哪些提交对象共同实现了某个功能——你不得不手动阅读所有日志消息。在这种情况下,撤销整个功能(即一组提交),会变得非常头疼,而如果使用了--no-ff
标志,则很容易完成。
虽然添加这个选项会创建几个更多的(空的)提交对象,但收益远大于成本。
4.1.3.3 删除特性分支
当合并完成后,特性分支的任务已经完成,此时即可将特性分支删除。
右键特性分支,选择“Delete ‘xxx’”,也可选中后直接按“Delete”键删除。
注意:你无法删除当前处在的分支,因此删除一个分支之前,应该先切换到另一个分支。
4.1.4 将现有更改推送到远程
此时一个新特性的完整开发流程已经完成。
4.2 发布分支(Release branches)
此分支可以从develop
分支的基础上产生,并必须合并回develop
和main
分支。
分支的命名必须是release-*
的形式。
发布分支支持新版本的生产准备。它们允许在最后一刻完成细节工作。
此外,它们还允许进行小的错误修复和为发布做准备(版本号、构建日期等)。通过在发布分支上完成所有这些工作,develop
分支就准备好迎接下一重大版本的特性了。
从develop
分支出新的发布分支的关键时刻是当develop(几乎)反映了新发布的状态。至少所有计划用于即将构建的发布的特性都必须在这个时间点合并到 develop
。不过所有计划用于未来发布的特性可能不需要——它们必须等到发布分支分出之后才能进行。
只有在发布分支的起始处,即将发布的版本才会被分配版本号——绝不早于这个时间点。在那之前,develop
分支反映了“下一个发布”的变更,但直到发布分支开始,都不清楚那个“下一个发布”最终会成为 0.3 还是 1.0。
那个决定是在发布分支的起始处做出的,并且由项目的版本号递增规则来执行。
4.2.1 创建发布分支
发布分支是从develop
分支创建的。例如,假设当前的生产版本是1.1.5,项目即将有一个大版本发布。develop
的状态已经准备好进行“下一个发布”,此时决定这个新版本号为1.2(而不是1.1.6或2.0)。
此处假设第一次发布一个大版本,版本号为1.0
(假设前面的版本号为0.x或0.x.x)。
此时创建分支,并给发布分支命名为release-1.0
,以反映新的版本号。
4.2.2 修改代码中的版本号并提交
一般代码中会配置当前版本的版本号,创建新分支并切换到该分支后,需要更新版本号并创建一个新的Commit。
1 | ./bump-version.sh 1.0 |
这里的bump-version.sh
是一个虚构的 shell 脚本,它会修改工作副本中的某些文件以反映新版本。(当然,这也可以是手动修改——关键在于某些文件发生了变化。)然后,将更新后的版本号提交。
这个新分支可能会存在一段时间,直到发布可以最终确定。在这段时间里,这个分支上可能会做一些必要的错误修复(而不是在develop
分支上)。
在发布分支上添加大型新功能是严格禁止的。它们必须合并到develop
,因此,添加大型新功能必须等待下一次大版本发布。
4.2.3 完成一个发布分支
当发布分支的状态准备好成为正式发布时,需要执行一些操作。
首先,发布分支要合并到main
(因为main
上的每个提交都定义为一个新的发布,记住这一点)。
接下来,main
上的那个提交必须打上标签,以便将来轻松引用这个历史版本。
最后,发布分支上所做的更改需要合并回develop
,以便未来的发布也包含这些错误修复。
4.2.3.1 将发布分支合并到main
分支
与特性分支的合并相同,想要将其他分支合并到main
分支,应该先双击切换到main
分支,然后右键想要合并的分支,选择“Merge into ‘main’”选项。
在弹出的Merge页面同样将“Merge Option”更改为“No Fast-Forward”,也就是为Merge添加--no-ff
标志。
同样的,Merge操作出现冲突时,需要先解决一下代码的冲突再进行Merge。
4.2.3.2 为新的提交打上标签
为方便未来方便引用,需要为main
分支上Merge后的新的提交打上标签。
右键新的Commit,选择“New Tag…”。
Tag名称为此次准备提交的版本号。
此处的“Push”选项可以先不勾选,等到最后执行Push的时候可以将所有Tag一并推送。
4.2.3.3 将发布分支合并到develop
分支
为了保留在发布分支中做出的更改,我们需要将它合并回develop
。
先双击切换到develop
分支,然后右键想要合并的分支,选择“Merge into ‘develop’”选项。
在弹出的Merge页面同样将“Merge Option”更改为“No Fast-Forward”。
这一步很可能会引发合并冲突(因为我们已经更改了版本号)。如果出现了冲突,请手动修复它并提交。
4.2.3.4 删除发布分支
当所有合并工作全部完成时,发布分支的任务已经完成,此时不再需要它,可以将它删除。
4.2.4 将现有更改推送到远程
由于在合并发布分支的时候创建了新的Tag,因此Push时需要勾选“Push all tags”。
并且此时develop
分支和main
分支都做了改动,因此需要手动切换到main
分支再进行一次Push。
此时一个新版本的发布流程已经完成。
4.3 热修复分支(Hotfix branches)
此分支可以从main
分支的基础上产生,并必须合并回develop
和main
分支。
分支的命名必须是hotfix-*
的形式。
热修复分支与发布分支非常相似,它们同样是为了准备新的生产版本,尽管不在常规新版本发布的计划中。它们将在需要对新发布的版本做一些BUG修复时出现。
当生产版本中存在一个关键错误需要立即解决时,可以从标记生产版本的main
分支上的相应标签创建一个热修复分支。
分支示意图:
其核心在于,团队成员(在develop
分支上)可以继续工作,而另一个人则准备快速的生产修复。
4.3.1 创建热修复分支
热修复分支是从main
分支创建的。例如,假设版本1.0是当前运行在生产环境中的版本,但由于一个严重错误而出现问题。但develop
分支上的更改还不稳定。在这种情况下,我们可以创建一个热修复分支并开始解决问题。
例如,在最新main
分支(最新标记生产版本的Tag)的基础上创建一个新的热修复分支为hotfix-1.0.1
。
4.3.2 更新版本号并提交
由于hotfix的过程实际上会更新一个小版本号,这个例子中的版本号是由1.0
变为1.0.1
,因此也应该像发布分支一样更新版本号并创建一个新的Commit。
1 | ./bump-version.sh 1.2.1 |
提交更新完成的版本号。
4.3.3 进行错误修复并提交
更新版本号后,即可开始正式的错误修复,并将修复内容提交到一个或多个独立的提交中。
4.4.4 完成一个热修复分支
完成热修复后,BUG修复完成的代码需要合并回main
,同时也需要合并回develop
,以确保BUG不会包含在下一个版本中。这完全类似于发布分支的完成方式。
4.4.4.1 将热修复分支合并到main
分支
先双击切换到main
分支,然后右键想要合并的分支,选择“Merge into ‘main’”选项。
在弹出的Merge页面同样将“Merge Option”更改为“No Fast-Forward”,也就是为Merge添加--no-ff
标志。
4.4.4.2 为新的提交打上标签
为方便未来方便引用,需要为main
分支上Merge后的新的提交打上标签。
右键新的Commit,选择“New Tag…”。
Tag名设置为新版本的版本号,此处为1.0.1
。
4.4.4.3 将热修复分支合并到develop
分支
为了保留在热修复分支中做出的更改,我们需要将它合并回develop
。
先双击切换到develop
分支,然后右键想要合并的分支,选择“Merge into ‘develop’”选项。
在弹出的Merge页面同样将“Merge Option”更改为“No Fast-Forward”,也就是为Merge添加--no-ff
标志。
这里的唯一例外是,当当前存在一个发布分支时,热修复分支的变更需要合并到那个发布分支,而不是develop
。因为如果这样,当发布分支完成时,错误修复也会被合并到develop
。(如果develop
中的工作立即需要这个错误修复且不能等待发布分支完成,你也可以现在就安全地将错误修复合并到develop
。)
4.4.4.4 删除热修复分支
当热修复流程结束时,热修复分支的任务已经完成,此时不再需要它,可以将它删除。
4.4.5 将现有更改推送到远程
此时develop
分支和main
分支都做了改动,因此需要分别对两个分支都进行一次Push操作。
由于在合并发布分支的时候创建了新的Tag,因此Push时需要勾选“Push all tags”。
此时一个热修复的完整流程已经完成。
总结
虽然这个分支模型并没有什么真正令人震惊的新内容,但本文开头提到的“大局观”图示在很多项目中却证明是非常有用的。
它形成了一个优雅的思维模型,易于理解,并使团队成员能够对分支和发布流程形成共同的理解。
现在回过头去看一开始的完整的标准Git分支模型示意图,同时,对比Fork中给出的提交图,应该会更为清晰:
值得一提的是,经过很多提交之后,Fork中的分支图也会变得比较复杂,此时可以使用Fork中的折叠分支的功能来简化分支结构。
鼠标悬浮到某一个节点,可以查看这个节点的提交结构。
点击每一个分支节点的减号(“-”)即可对这个节点的连线进行折叠。
如果对任何一个节点点击右键,选择“Collapse All”即可折叠所有无关分支。
此时的分支结构就如同之前所说,包含两个具有无限生命周期的主分支,它们两个分支互相平行。
对节点右键,选择“Expand All”即可回到原本的状态。
至此,标准Git分支模型的全部介绍已完成,请尽情探索Git与Fork的更多有趣用法,希望本文可以在项目中帮到你。
本文链接: https://hanqingjiang.com/2025/08/21/20250821_gitBranchingModelWithFork/
版权声明: 本作品采用 CC BY-NC-SA 4.0 进行许可。转载请注明出处!
