对于项目版本管理,你是否存在这样的痛点:项目分支多而杂不好管理,git log界面commit信息错乱复杂无规范,版本回退不知道选择什么版本合适……。
项目版本管理的最佳实践系列,笔者将以两篇文章的形式展开介绍(即基础篇与进阶篇)。本文为gitflow版本管理的最佳实践-基础篇。基础篇主要介绍git应用于生产的基本流程与怎么使用gitflow管理你的项目版本线(适用于敏捷迭代的项目管理场景下)。进阶篇 将着重介绍gitflow+jenkins+docker+DevOps+敏捷Scrum 完成项目持续构建与持续交付(CI/CD)。阅读本文需要有一定git基础,基础知识则不在本文展开,善用网上冲浪工具便可学习到许多Git的基础知识。实际上,本文介绍的并不是纯粹的gitflow,而是结合实际生产对gitflow的改造与最佳实践。
导航
项目版本管理的最佳实践:飞流Flow(阿里AoneFlow)篇
项目版本管理的最佳实践:gitflow基础篇
目录
Git的基本术语与简写
术语 | 解释 |
PR | 即pull request,拉取请求。请求git代码管理员将你的代码合并到仓库的分支中。一般的PR由标题部分,描述部分与代码部分组成。 |
code review | 在PR过程中代码管理员对你提交的代码进行代码审查,即你的代码是否符合规范、是否存在风格问题、安全问题等,对你代码进行cr的同学并不一定是代码管理员,成熟的敏捷团队,每一个成员都是code owner,都可以对pr进行审阅。 |
squash | PR过程中,会将你的所有commit合并(榨取)成一个commit并提交到目标分支中,目的是减少冗余提交、规范主分支提交信息(实际上是rebase的一种操作)。 |
LGTM | look good to me(看起来很吊),一般存在于PR评论中,即对pr的内容没有问题,同意合并到版本库。 |
一、分支规约
在我们的最佳实践中,远程版本库永远只存在三条长期且相互独立的分支,他们分别为develop、release与master,三条分支对应三个环境,分别为开发环境(集成开发环境)、测试环境(预发环境)与生产环境,三个分支分别都上权限,不可直接对其进行push与commit操作,即所有的修改均通过PR进行,以保证分支对应环境的安全与稳定。本地环境对应的远程分支均会在PR通过之后,自动进行删除,以保证版本线的简单。
环境 | 分支名 |
开发环境 | origin/develop |
测试环境(预发环境) | origin/release |
生产环境 | origin/master |
本地环境功能分支 | develop_xxx (xxx为具体的开发成员或具体的功能描述, origin/develop_xxx,即feature分支下沉到本地,生命周期短,只存在于pr过程)。 |
二、版本号规约
在正式介绍gitflow之前,我们需要对版本号进行规范,方便接下来的行文展开。
在生产中,我们常用的版本号为三位数版本号(偶尔带四位热修复号),其构成如下:
V主版本号.次版本号.功能号(.${热修复版本号}).环境
eg:V1.0.0.1.RELEASE、V1.1.0.DEVELOP、V1.0.0。(版本号并不以十进制,而是按照迭代规划推送)
2.1 主版本号(首位版本号)
主版本号,也叫首位版本号、顶位版本号,即V后第一个版本号。主版本号一般代表项目的期数与产品方向。除非项目合同改变、大规模api不兼容、产品方向改变、底层架构升级等情况外不轻易更新。
另外,项目未正式发布、未正式孵化、未正式上线,则首位版本号为0,一期发布,则为V1。
2.2 次版本号(迭代号)
次版本号,也叫迭代号,一般代表某个迭代发布的功能集合(一个迭代发布会包含若干个功能更新)。
如V1.1.0:第一期项目第一迭代发布版本、V1.2.0:第一期第二迭代发布版本。
2.3 功能号(PR号)
一般来说,提交到项目分支内的代码均需要经过PR,而为了保证单个PR的简洁性与纯粹性,建议一个PR描述一个功能。因此第三位数的版本号也叫做PR号或功能号,用来描述单个提交到主分支内的功能或代码修改。
如V0.0.1:第一迭代的第一个提交、V0.0.98:第一迭代的第98个PR。
2.4 热修复号
四位数版本号是可选版本号,为热修复版本号(也叫老爷保号hh),常规迭代与develop分支下并不会出现,而常出现在测试环境对应的release分支与生产环境对应的master分支(develop分支对应的开发环境出现bug直接提交pr修复并在原来的版本号上+1便可)。这个版本号常用于线上热修复,测试环境(预发环境)的热修复。
值得注意的是,四位数版本号经过线上热修复之后,要同步到本地develop环境的情况下,应当在develop分支下的三位数版本号上加一。
如:master的热修复号为V1.0.0.4,develop分支当前版本为V1.1.8.DEVELOP,那么这个修复要同步回develop分支保证bug不重现,那么在develop上面的版本则为V1.1.9.DEVELOP
2.5 环境号
因为在git中的tag名称是唯一的,那么在develop分支下出现了V1.0.0的tag,那么在release和master下便不可以再打一个tag叫V1.0.0。因此出现环境号来对分支版本进行区分(生产环境不加环境号)。
环境 | 环境名 | 版本号(示例) |
开发环境 | DEVELOP | V1.0.0.DEVELOP |
测试环境(预发环境) | RELEASE | V1.0.0.RELEASE |
生产环境 | MASTER | V1.0.0 |
三、Gitflow的最佳实践 (基础篇)
3.1 总体流程图
3.2 弓行同学与阿康同学的最佳实践
这里要搬出两位同学进行接下来的讲解,他们是【弓行】同学与【阿康】同学。
3.2.1 远程主干分支创建
版本的最开始(指V0.0.0),代码管理员会初始化远程仓库,并基于master的初始版本创建三条分支,他们是
origin/master(对应生产),origin/release(对应测试环境),origin/develop(对应开发环境) 并为这三条分支设置保护策略,三条分支均不允许直接的commit与push修改。
代码管理员将三个初始版本打上相应的TAG:(V0.0.0.DEVELOP、V0.0.0.RELEASE与V0.0.0)。
3.2.2 本地分支创建
完成迭代计划会议(迭代版本号为V0.1.0)之后,弓行与阿康他们分别认领了两个任务:【开发功能】弓行,【开发功能2】阿康。
此时,弓行与阿康会将远程仓库克隆下来,并基于origin/develop 创建本地develop_gx分支与develop_kang分支。
3.2.3 创建PR
两人认领任务后进行同步开发,一段时间后,弓行率先完成【开发功能1】的工作,因此他需要将当前开发版本提交到开发环境中进行自测与前后端联调。但此时【origin/develop】是被保护的状态无法被直接提交。因此,弓行需要对当前的开发的版本进行PR申请,即创建拉取请求,请求代码管理员对代码进行code review,通过后进行合并。
此处涉及的步骤大致如下:
1、push当前本地分支到origin,得到origin/develop_gx。
2、创建PR:即:origin/develop_gx 合并到 origin/develop 的拉取请求
3、等待代码管理员(或小组内同学)进行code review,若需要修改,则直接在pr中提出注释,作者修改后直接push到远程分支中,继续等待代码管理员进行code review。
4、通过后,将当前commit list以squash的形式合并到origin/develop中,得到V0.0.1.DEVELOP 的commit
5、最后选择删除origin/develop_gx的远程分支
此时,弓行同学完成了第一个功能的开发,并在【origin/develop】分支上对自己的pr commit 进行tag操作:将此commit记录为【V0.0.1.DEVELOP】
3.2.4 合并冲突提交版本
不久后,阿康同学也完成了【开发功能2】的开发,他也需要将代码提交到origin/develop分支进行测试与联调。但此时,origin/develop已经与他的基版本不一样了(基版本为V0.0.0.DEVELOP,远程版本为V0.0.1.DEVELOP,领先一个版本)如果直接创建PR,可能因为代码冲突的问题无法完成版本合并,如下图。
此时阿康需要将origin/develop版本拉取到本地,并执行以下操作(推荐直接使用ide自带的git工具,会方便不少)
//检查远程仓库是否有新版本
git fetch origin
//发现新版本,需要拉取到本地解决冲突后进行代码合并
//暂存本地修改
git stash save “stash changes”
//拉取远程版本
git pull origin/develop
//取出本地修改
git stash apply
//手工解决冲突(推荐直接使用idea)
//提交修改
git commit -m'1、解决冲突合并版本'
使用ide自带的冲突解决工具则如下图
提交修改后(注意一定要和冲突代码的作者商量代码的变更),便可以创建PR,等待团队内同学进行code review。团队成员通过之后,阿康的修改便可以成功被合并到origin/develop中进行联调与测试了。阿康此时需要将改commit打上tag【V0.0.2.DEVELOP】,如下图。
至此,V0.1.0所规划的开发工作全部完成。
3.2.5 测试环境版本发布
完成V0.1.0版本开发工作后,弓行同学认领了一个新任务:【V0.1.0版本提测】。正在其他进行其他功能开发工作的弓行同学此时需要将本地代码stash起来,并将origin/develop分支的代码与本地代码进行合并(即git pull origin develop操作),并进行代码冲突的解决工作。
因为要将代码发布到origin/release分支进行版本提测,所以弓行同学需要同时将origin/release上的代码与本地代码进行合并操作(即git pull origin release操作) 并进行代码冲突的解决工作。
完成git pull origin develop 与git pull origin release 之后,本地会形成一个新的commit版本。弓行同学需要将此commit版本通过pr的方式合并到 origin/release 上,方可完成release分支的测试版本发布工作。因此弓行同学需要重复 3.2.3 步骤的PR创建过程,并通过release分支的分支管理员审批后,方可将版本发布到测试环境。
3.2.6 版本标记
将commit通过pr的形式提交到release后,接下来就是对版本进行标记的过程,因为此release已经完成了版本的开发工作,因此,当前版本在release分支上会被标记为【V0.1.0.RELEASE】。又因为在develop分支上,V0.0.2.DEVELOP版本对应着release的V0.1.0.RELEASE版本,针对origin/develop的分支上的该commit,会被打上第二个tag:【V0.1.0.DEVELOP】。
而后,对于develop分支的tag处理,将会直接从V0.1.0.DEVELOP继续往下走(如V0.1.1.DEVELOP等)
3.2.7 热修复
origin/release分支对应着测试环境,对于某些情况而言,测试环境相当于项目的beta版本,有可能直接面对客户。
那么版本提测之后,测试同学针对该【V0.1.0.RELEASE】版本进行各种测试后发现当前版本存在BUG,那么开发的同学就要针对改bug进行热修复。
假设现在在测试环境出现一个BUG,该BUG的修复工作依旧由弓行同学认领解决,那么此时弓行同学就需要将手头上的开发工作暂停(git stash),然后拉取最新版本的origin/release分支到本地,然后进行bug修复工作。完成修复后,提交本地代码到 origin/release_hotfix_gx 分支,对该分支进行PR操作,由release管理员进行code review并合并到release中,并将该修复版本记录为【V.0.1.0.1.RELEASE】。
当然了,因为分支commit存在映射关系,出现在V0.1.0.RELEASE上的BUG,也一定会出现在V0.1.0.DEVELOP。那么此时修复了测试环境的版本仍不够,弓行需要将该修复合并到origin/develop上。因此弓行同学需要将新发的版本【V0.1.0.1.RELEASE】拉取到本地,然后对origin/develop进行版本提交工作,形成【V.0.1.1.DEVELOP】
至此完成热修复的过程(master的热修复也是同理,不过是将修复版本根据实际情况合并到release和develop上的不同罢了)。
3.2.8 生产发布
完成release版本的提测工作、BUG修复工作后,弓行同学需要将release分支的版本发布到master上,完成生产环境版本的发布,实际上这个过程也与 3.2.5 并无太大差异。同学们可以结合自己实际情况,在这一步增加团队code review、checklist检查,发布风险控制等操作,对生产发布进行安全保障。
在完成origin/master的发布工作后,将master的tag更新到 V0.1.0 便完成了整个迭代的发布工作。
细心的同学读到这里可能已经发现了,origin/develop、origin/release、origin/master 这三条分支在整个过程中都互相独立,互不影响,因此本工作流程属于三独立分支模式的gitflow,同学们若为减少流程,release分支可优化掉,直接在develop分支上进行测试,(也符合测试驱动开发)
四、 双周迭代制与gitflow
4.1 敏捷的双周迭代制
试想一个场景:产品没多少时间规划特性;项目草草规划冲刺backlog;开发在迭代最后一天完成开发工作,测试只有最后2小时进行测试;上线后一堆BUG便令人十分抓狂,因此双周迭代制应运而生。
以下图为例,一个敏捷团队中有三种垂直的职能角色(暂不考虑产品):开发、测试、与项目(Scrum master),我们假定当前迭代为N,下个迭代为N+1,上个迭代为N-1。
双周迭代制,即一个冲刺迭代设置为两周(或若干周),在这两周中的第一周,这几位垂直职能角色可以如下分工:
- 开发:进行N迭代 (当前迭代) 的开发与N-1版本 (上个版本) 的hotfix工作,并在每周五进行统一提测;
- 测试:进行N迭代 (当前迭代) 的develop环境测试与N-1迭代 (上个迭代) 的 release 环境测试,在每周五前完成N-1版本的测试工作;
- 项目:进行N+1 (下个迭代) 的迭代规划工作与上两个迭代(N-2)的迭代发布工作
如此一来,N-1版本,N版本,N+1版本便可实现交错进行,有条不紊(需求源源不断地来hhh)当然了,迭代开发时间与测试时间可以适当变动(如 开发:测试 = 6:4 或7:3)。
采用双周迭代的好处在于:
- 开发同学有充足和弹性的时间进行迭代的开发工作与bug修复工作与需求理解
- 测试同学有充足的时间进行测试工作以保证项目质量 (在develop环境上一个功能测一个功能,并在release环境可以完成充分的功能测试)
- 项目有更多时间去规划项目的迭代与分解具体需求做更完善的设计(疯狂规划迭代)。
4.2 双周迭代结合gitflow的最佳实践
基于双周迭代制的gitflow版本管理,即在迭代中:
- 开发在origin/develop上进行开发,进行 3.2.4步骤的开发工作,与上个release版本的hotfix工作;
- 测试紧跟开发进行develop分支的测试与上个release版本的测试;
- 第一周结束,统一发版本到origin/release,测试在第二周开始当前版本release环境的功能测试;
- 第三周的周一,项目进行版本发版工作(即发布到origin/master)
五、FAQ
Q1 : 微服务架构下,每个项目独立一个版本库怎么做到版本号统一,是每个微服务单独编制版本号还是全局统一版本号?
A1 : 对于微服务架构下(或者分布式架构项目)的每个微服务独立版本库的情况,建议全局编制版本号,即同一个发布窗口,对所有的目标发布分支与的二位数版本号进行编制(即全局统一迭代号或者产品号)。对于没有更新的微服务,可直接在原release的commit上进行Tag发布。
Q2 : 三分支版本线相对独立,对于版本合并比较痛苦。
A2 : 这个问题是切实存在的,建议固定发布人,在本地分支保留release分支、master分支与develop分支的合并记录,防止冲突过多。
Q3 : 对于目标发布的功能,若功能在发布前存在风险,则无法下有风险的分支。
A3 : 问题切实存在,可以配合开关配置与双周迭代做发布。
Q4 : 项目处于快速开发阶段,大家一直往develop分支上面提PR,但是没有人做code review。
A4 : 可以尝试PR标题带上tag信息作为commit title,即:V0.0.1 xxx功能开发 V0.0.2 XXX功能开发,这样一来,就相当于做了资源锁,大家都想往develop上面提pr,但是上一个人把三位数版本号占用了,那么就需要有人把这个pr处理掉,自己才能使用下一个版本号,直到团队code review习惯成熟,如下图(develop的版本线是不是很清晰)。
Q5 : 允许origin/develop、origin/release与origin/master三条分支之间互相合并吗?
A5 : 不允许,只能通过PR形式进行分支版本合并
Q6 : 使用此种gitflow后,三个分支的版本线会是什么样子的?
A6 : 如下图,无论是develop分支还是release分支与master分支,分支永远只有一条直线,不会有分支之间进行合并的情况,所以显得版本线十分干净整洁。
Q7 : 好像整个过程都没有看到feature分支
A7 : 是的,在此种版本管理中,feature分支其实已经下沉到了每个人的本地版本库中,不直接在origin库中体现。
Q8 : 远程feature分支可以不删除吗?
A8 : 为了保证git log干净,建议个人分支合并到develop分支后便执行删除,但不删除远程个人分支也是可以的,可以尝试完成develop的pr后使用同一个个人分支合并到release_${version}中,然后执行release_${version}->release的PR完成release的发布。
以上便是项目版本管理的最佳实践:gitflow基础篇的所有内容,欢迎在评论区讨论与提出改进意见!