简述
前阵子,掌上生活
iOS客户端代码进行了Git迁移。踩了很多坑之后,终于在Git上成功的发布了一个版本,而且还进行了一次紧急发版。所以,趁着下雨的周末,来讲讲踩完坑之后现在的客户端Git分支模型。我不会讲任何项目的细节,比如为什么模块化开发,仅讨论分支策略和版本管理的相关内容。
新『拿来主义』
业界流行的五分支模型
图如下:

五分支模型
对于只管理单个Git仓库的项目来说,分支非常清晰,版本井井有条。
如果项目被模块化后,每一个模块需要被创建Git仓库,那么,一个项目就会管理多个Git仓库;
每次项目发版,有些模块添加了新的功能,这些模块按照五分支模型
需要创建release
分支进行发布前的测试;但有些模块没有变更,则不需要创建release
分支。
这个时候就出现了一个版本中,有些模块是5分支,有些模块却是4分支。
一个项目版本中存在2种分支模型肯定是不允许的,否则版本管理就混乱了。
在项目发版本的时候,如何统一所有模块仓库的分支模型呢?
再见release分支
release分支的定义是为新版本的发布做准备的;
它允许我们在最后时刻做一些细小的修改,允许小bug的修复。
release分支是从达到发布理想状态的develop分支创建出来的;在修复完bug之后,release要合并到master上,同时还要合并到develop上进行代码同步。
假设,master分支把达到发布理想状态的develop分支直接合并,创造出预发布版本;那么,release分支所担当的职责可以由hotfix分支来完成。
按照这个思路,最终确定项目中多Git仓库下的分支模型。如下图:

关键分支

在整个项目的周期中,原始库(origin)随着开发的进行一直保持着2个关键分支:
- master分支
- develop分支
每一个开发人员都要了解原始的master分支(origin/master)。
与master分支并行的另一个分支,我们称为develop分支。
develop分支跟踪着源码的最新开发进度,开发人员会不断的向develop分支合并最新代码。
当develop分支的源码到达了一个稳定状态待发布时,所有的代码变更需要以某种方式合并到master分支,然后标记一个版本信息。
master分支记录了所有发布版本的信息,还记录了每个版本的各个阶段,如alpha、beta、rc等。
辅助分支
在这个分支模型中,使用了各种辅助性分支。这些分支与关键分支(master和develop)一起,用来支持团队成员们并行开发,使开发的内容易于追踪,快速修复内测Bug和线上版本问题。
与关键分支不同,这些分支总是有一个有限的生命周期,因为他们最终会被移除。
项目中用到的分支类型包括:
- 特性分支
- 热修复分支
每一种分支有其特定的目的,并且有严格的创建合并规则,比如:可以用哪些分支作为源分支,哪些分支能作为合并目标。当然,从技术角度来看,这些分支绝不是特殊分支。分支的类型基于我们使用的方法来进行分类。它们理所当然是普通的Git分支。
特性分支(feature)

特性分支通常为即将发布或未来发布版本的新功能特性。其命名规则为feature-*
。它包含了如下几种情况:
- 每天开始开发一部分新功能时,需要创建一个特性分支,在完成当天开发任务后,需要合并到develop分支。这种特性分支生命周期比较短,一般不超过一天。
- 在开发的过程中发现一种新的实现方式,为了测试可行性可以创建一个特性分支;最终这个特性分支会根据测试结果合并到develop或取消。
- 有些新功能还无法确定发布时间时,开发这些新功能的特性分支会一直存在下去,直到确定发布时合并到develop分支。
特性分支的实质是只要这个功能处于开发状态它就会存在,但是最终会合并到develop分支或取消(比如一次令人失望的测试)。
特性分支通常存在于开发者本地的代码库中,而不是在源代码库中。
当feature分支合并到develop分支上时,我们需要添加一个--no-ff
参数,它会在合并过程中强制创建一个空的commit对象,即使该合并操作可以fast-forward
。这样避免了在合并过程中丢失特性分支的历史信息,将该分支的所有提交组合在一起。
为了详细说明这种情况,下面有一个比较:

后一种情况,你不可能从Git历史中一目了然的看出哪些提交一起实现了一个功能,必须手工阅读全部的日志信息。假如我们需要对整个功能进行回退,后一种方式会是一个非常头痛的问题,而使用--no-ff
参数的情况则非常容易。
热修复分支(hotfix)

热修复分支通常为正在内测阶段或刚发出的版本修复bug。它的命名规则为hotfix-*
。
hotfix分支是从master上分出来的;当完成bug修复后,hotfix分支需要合并到master和develop分支上去,这样才能保证修复的bug也包含在下一个版本中。
客户端版本管理
客户端项目模块化后,虽然一个项目包含了多个Git仓库,但是统一了每个仓库的分支模型后,版本管理就简单多了。每次发布版本,模块仓库根据主仓库同步打tag,这样只需要遍历所有Git仓库,就可以通过tag拿到任意版本下的所有客户端代码。
总结
虽然这个分支模型没有任何新颖的地方,但是它很好的解决了项目多仓库情况下版本管理的问题。它让团队成员能更多的把精力放在开发上,而不会因仓库分支不同而不断在仓库间同步版本。