想了很久,还是写这么一篇文章来总结一下有关分支策略和DevOps的一些内容吧。其实,DevOps相关的内容并不是我的工作范围,不过对于敏捷开发、DevOps、项目管理等等这一系列的与开发过程相关的内容,我还是有些经验的,也就抽时间跟大家分享一下吧。 Git Flow应该是很多基于Git分布式版本控制系统的项目所实践的一种开发流程,当然,很多人对于Github非常熟悉,甚至平时工作就是基于Github的。在此我也就不多介绍Git以及Github的概念了,只需要知道Github是基于Git的一个服务供应商,目前已被微软收购。Git Flow就是由Vincent Driessen基于Git这一版本控制系统所总结出来的一套可行的、能够满足很多项目开发需求(注意:不是大多数项目)的敏捷开发流程,英语水平好的朋友可以参考阅读Vincent的原文:《 A successful Git branching model》。
Git Flow
Git Flow通常会用在有具体的release发布的概念的项目上,简言之就是类似于以前能够给客户提供安装包的项目,对于SaaS项目,Git Flow还是太重了,而且不同环境、多租户的部署策略也与Git Flow有一些差别。因此,团队还是应该根据自己的实际情况来选择合理的开发流程,这里所介绍的内容也是仅供参考。下图(来自《A successful Git branching model》一文)展示了Git Flow的整个流程,在这里,我会对这个流程做一个详细的介绍。
master分支
master分支始终保持了最近一次发布(release)的代码,通常情况下,通过tag的方式来标记每一次release。在Github中,每创建一次release,就可以基于某个分支(branch)创建一个tag。在tag被创建后,分支是可以删除的。因此,我觉得在上面的模型中,维护一个master分支并不是必需的。所以,有些团队为了直观,会将develop分支作为默认分支,甚至删除master分支。但我觉得删不删除也都无所谓,直接将master分支作为开发分支(也就是合并develop和master的职责)也是可以的。
develop分支与feature分支
develop分支(或者master分支),是项目开发的主要分支,它包含了项目的最新代码,开发和测试团队都会基于develop分支进行工作。每当有新的功能需要开发时,都会从develop分支分出一个feature分支。从实践的角度:
- 上图中分主体功能的feature分支(Major feature for next release)以及后续版本的feature分支(Feature for future release),实际上,出现后续版本的feature分支的可能性不会太大,由于采用敏捷开发,团队通常不会做三个月以上的项目计划,因为没有意义,没有人能够确保三个月之内不会有功能需求的变更、backlog的调整以及市场对于项目本身的影响,因此,相对而言,近期的版本发布目标应该是比较明确的,长远的计划反倒显得意义不大。不过,也有可能会让团队的部分成员提前进入下一版本的研发,但由于敏捷开发和微服务理念的引入,一般敏捷团队也不会特别庞大,所以,这样的活动也不太会频繁发生。当然,还是应该依据项目实际情况而定,如果真有部分成员提前进入下一版本开发的需要,那就按上图中所描述的流程进行即可,不会有什么大问题
- 多个feature并存的可能性还是很大的,团队的不同成员会工作在不同的feature分支上,当每个feature完成之后,都会合并到develop分支。合并过程可能会出现代码冲突
- 每个成员是直接将代码提交到feature分支,还是自己再基于feature分支创建自己的工作分支?这取决于团队的代码审核(Code Review)流程,如果团队有严格的代码审核流程,开发人员应该在进行开发工作之前,基于feature分支创建自己的工作分支。开发完成后,将代码push到远程,然后提交Pull Request(PR)并邀请团队进行Code Review,Review通过,再将代码合并到feature分支,代码合并之后,将自己的工作分支删掉即可。如果代码审核流程不严格,那么直接将代码push到feature分支也是可以的,这需要团队成员建立起互信机制,并且有高度自治(Self-organize)的能力。这里有两点可以多写几句,首先不要觉得重复创建、删除分支会带来很大压力,Git的分支是非常轻量的,需要的时候就创建,不需要就删除,非常方便;其次,很多持续集成系统,对于多分支的开发流程以及Pull Request/Code Review流程都有完美的支持,Azure DevOps的这部分功能使用起来也是非常方便,之后我会详细介绍
- 持续集成系统会从各feature分支上拉取代码进行编译,然后自动化部署到开发环境进行冒烟测试(Smoke Test),便于开发人员尽早验证已开发的功能。如果验证通过,开发团队可以将feature分支合并到develop分支,并删除feature分支,然后进行下一个feature的开发
- 持续集成系统会从develop分支上拉取代码进行编译,然后自动化部署到测试环境供测试团队(QA)进行测试,最后给Product Owner或者其他Stakeholders做演示的环境,也是从develop分支出来的
release分支
当所有计划好的feature都完成之后,团队会基于develop分支创建release分支,此时:
- 持续集成系统会基于release分支产生新的构建,构建结果称为Beta构建(Beta Builds)
- 测试团队(QA)基于Beta构建进行回归测试(Regression Testing)
- release分支不接受任何新的功能增加
- 开发团队在release分支上进行Bug修复,同样,修复Bug时是否需要创建自己的代码分支,取决于团队自己的决定
- 当质量达标,团队验收之后,进入release流程,此时,基于release分支创建release tag,同时将release分支合并回develop分支,以将release分支中的bug修复带入develop分支。在建完release tag并将代码合并回develop分支后,可将release分支删除。上图中没有体现这一步
- 上图中展示的是将release分支合并到master分支,然后在master分支上建release tag,这样做是不妥的,因为合并到master分支的代码并没有经过测试,所以release tag不能建立在master分支上
hotfix分支
在某个版本发布之后,有可能会得到用户的反馈,有些是功能性的,有些则是影响用户使用的问题。对于功能性的反馈,可以与用户商量,或者团队自己决定,是准备在下一个版本中加入,还是基于用户现在的版本为用户专门定制。如果是后续版本再支持,则遵照上述feature分支的策略即可,但如果是基于用户现在使用的版本进行定制,则需要走Hotfix的流程
- Hotfix分支从已经release的tag创建,找到已有的tag,然后基于tag新建Hotfix分支即可
- Hotfix分支的工作流程可以参考develop分支
- Hotfix开发完成并检验通过之后,基于Hotfix分支创建tag,标记为某一个Hotfix
- 根据当前所修复的问题是针对该特定用户的(比如在界面上增加用户公司的名称与logo),还是针对所有用户的(比如某个bug的修复),以此决定是否需要将改动合并到develop分支
- 删除Hotfix分支
从上面的流程可以看出,除了develop分支(或者master分支)为长期存在的分支之外,其它的分支都为辅助分支,在工作完成之后可以立即删除。为了满足代码评审(Code Review)而创建的开发人员自己的分支,一般都是即用即删。 以上就是Git Flow的整个流程,应该是已经介绍的比较细致了。在平时的工作和自学中,我都是以Github作为代码托管平台的,因此,本文的内容仍然是以Github为基础,包括接下来有关Azure DevOps Pipeline的介绍,也是与Github进行集成的。
Azure DevOps中Git Flow相关功能介绍
在上面的介绍中,不止一次提到持续集成系统,比如,当有代码签入feature分支或者develop分支时,持续集成(Continuous Integration,CI)系统会自动进行编译,然后触发持续发布(Continuous Delivery,CD)系统完成自动化部署。这些CI/CD系统所执行的自动化任务,需要人为地反复定义并触发吗?其实并不需要。现在流行的持续集成系统基本都已成熟,比如Jenkins,它的Pipeline功能直接支持Multi-Branch,每当有新的分支创建,或者代码签入,都会触发Jenkins创建新的Job,并触发相应的CI任务。Azure DevOps在这部分也做得非常好,而且它是云端服务,简单易用,对于开源项目是完全免费的。 首先,当你在Github中新建了一个repo,并且在Azure DevOps中配置了一个Project,使其从Github拉取代码时,你会发现,Github repo的Webhook里已经自动为你加上Azure DevOps所需的Webhooks: 不用管它,我们设想两个场景:
- 当开发人员准备向feature分支合并代码时
- 当feature分支出现代码签入时
下面看看,如何在Azure DevOps中进行配置,以实现上述两个场景。在这个实验中,我的Github Repo有三个分支:
- master:暂时没用到
- dev:开发主线,保持了最新代码
- features/a1:功能代号为a1的开发分支
目前团队都在集中开发features/a1功能,由于团队选择较为严格的Code Review流程,因此,开发人员在进行自己工作之前,都是从features/a1创建自己的工作分支,比如就叫features/a1-daxnet吧,以便在代码签入features/a1分支的时候,能够产生Pull Request并申请代码审查。工作分支的名称可以自己决定,但团队需要达成共识,因为这个名称之后会被Azure DevOps用到。 现在,进入Github Repo的Settings页,在Branches设置页面中,在Branch protection rules设置下,新建一个规则(Rule),在Branch name pattern中,输入features/a1(也可以使用规则来定义一组分支),然后,勾选Require status check to pass before merging: 注:如需Code Review,则还需要勾选Require pull request reviews before merging,在这里就不演示了。然后,回到Azure DevOps,针对Github Repo创建CI,在CI的Trigger选项中,启用Continuous integration,以保证每当有代码签入指定的分支,就会触发持续集成。在Branch filters中,添加features/*分支,表示以features作为开头字符的所有分支,都需要进行持续集成,确保编译通过: 启用Pull request validation,在Branch filters中,添加features/a1,表示当有Pull Request需要合并到features/a1分支时,也需要进行持续集成,以确保编译通过。注意,目前我们不涉及Repo被Fork的情况,所以我们不勾选Forks选项: OK,基本设置就是这样,非常简单。接下来,我们开始开发新的功能。首先,在Github页面基于features/a1,创建分支:features/a1-daxnet: 刚刚创建完新的分支,Azure DevOps就已经开始工作了,可以看到,Build Pipeline已经完成了新分支的编译: 下面,我要开始修改代码了,在代码修改完成本地编译通过之后,我将代码提交到features/a1-daxnet分支: 再次查看Azure DevOps的Build Pipeline,可以看到,CI已经开始帮我们编译新的代码提交了: OK,再次不管它,现在,我希望将代码合并到features/a1分支,于是,我到Github上创建Pull Request,此时Github会提示,代码校验还在进行中,并等待Azure DevOps的编译结果: 回到Azure DevOps,可以看到,Build Pipeline正在进行Pull Request编译: 等待编译成功后,开发者就可以将代码合并到features/a1分支了。当然,这里我没有强制要求必须要等代码编译成功才能合并分支,因此,即使是编译没有完成,上图中的Merge pull request的按钮也是可用的。
总结
本文首先介绍了Git Flow分支策略,并结合了实际应用介绍了Git Flow的详细步骤,然后基于Azure DevOps介绍了在实际应用中,Azure DevOps Build Pipeline对Git Flow的支持。本文所介绍的内容并不能完全适用于所有的软件项目,尤其是SaaS项目,所以在开发过程中,团队还是应该根据自己的实际需求来定制自己的开发流程,在保证软件质量的前提下,提高开发体验和团队生产力,探索寻求适合自己的实践流程。