进入企业后,开发过程中为了实现多人同步协作,企业往往会有一套 git 分支规范,这和学习中遇到的分支大有不同。本文是对 git 成熟模型 A successful Git branching model 的理解和对公司约定的 git 分支规范的思考。

在实际生产开发的过程中,如果每个人都随意的创建分支,随意的提交commit,必将导致整个git仓库非常的混乱,不易于团队协作。Vincent Driessen 同学为了解决这个问题提出了 A successful Git branching model,最后形成了业内普遍采用的 git 工作流程,大家都在约定的流程内使用git,使得团队协作效率大大提高‌。

为什么使用Git?

Git 是一个分布式版本管理工具,在多人协作的条件下高效处理任何规模的软件工程,并对项目版本、状态做环境隔离。每一个Git克隆都是一个完整的文件库,含有全部历史记录和修订追踪能力,不依赖于网络连接或中心服务器。最大特色是分支和合并操作非常快速、简便。

与SVN相比,git有以下5点优势:

  1. 版本库本地化,支持离线提交,相对独立不影响协同开发。每个开发者都拥有自己的版本控制库,在自己的版本库上可以任意的执行提交代码、创建分支等行为。例如,开发者认为自己提交的代码有问题?没关系,因为版本库是自己的,回滚历史、反复提交、归并分支并不会影响到其他开发者。

  2. 更少的“仓库污染”。git对于每个工程只会产生一个.git目录,这个工程所有的版本控制信息都在这个目录中,不会像SVN那样在每个目录下都产生.svn目录。

  3. 把内容按元数据方式存储,完整克隆版本库。所有版本信息位于.git目录中,它是处于你的机器上的一个克隆版的版本库,它拥有中心版本库上所有的东西,例如标签、分支、版本记录等。

  4. 支持快速切换分支方便合并,比较合并性能好。在同一目录下即可切换不同的分支,方便合并,且合并文件速度比SVN快。

  5. 分布式版本库,无单点故障,内容完整性好。内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。

主分支

对于 Git 初学者来讲,应该都很熟悉 master-develop 双分支模型吧。我们一般把 origin/master 库认作为主分支,HEAD 的源代码存在于此版本中,并且随时是预备生产状态。而 origin/develop 分支 HEAD 源码始终体现下个发布版的最新变更,当 develop分支的源码到达了一个稳定状态待发布,所有的代码变更需合并到 master 分支,然后标记一个版本号。

所以,每次变更都合并到了master,这就是新产品的定义。理论上,每当对master 有一个提交操作,我们就可以使用Git钩子脚本来自动构建并且发布软件到生产服务器。

辅助性分支

开发模型一般会使用各种辅助性分支,这些分支与关键分支(masterdevelop)一起,用来支持团队成员并行开发,协助生产发布环境准备,以及快速修复实时在线问题。与关键分支不同,这些分支是有生命周期的,因为它们最终会被移除。

我们用到的分支类型包括:

  • 功能分支(feature
  • 发布分支(release
  • 测试分支(sit
  • 预发布分支(uat
  • 热修复分支(hotfix

每一种分支有一个特定目的,并且有一定程度的规则,比如:可以用哪些分支作为源分支,哪些分支能作为合并目标。

功能分支(feature)

功能分支通常为即将发布或者未来发布版开发的新的功能。当新功能开始研发时,负责此需求的工作者应该本地创建一个相应的功能分支进行开发(注意不要将本地分支 push 到远程)。

可能是 develop/release 分支的分支版本,最终必须合并到 develop/release 分支中。

根据企业自身状况,功能分支可能也会从 release/版本号 中拉取,因为也许企业需要两个版本的系统并行开发,只有 develop 分支作为主要的开发环境不太能满足这种需求。

创建一个功能分支

开始一项功能的开发工作时,基于develop创建分支

1
$ git checkout -b feature/v1.0/myfeature develop

合并一个功能到develop分支

完成的功能可以合并进develop分支,明确加入到未来的发布:

1
2
3
4
$ git checkout develop
$ git merge -no-ff myfeature
$ git branch -d myfeature
$ git push origin develop

–no-ff 标志导致合并操作创建一个新commit对象,即使该合并操作可以fast-forward。这避免了丢失这个功能分支存在的历史信息,将该功能的所有提交组合在一起。

如果直接 git merge,不可能从 Git 历史中看到哪些提交一起实现了一个功能,必须手动阅读全部的日志信息。因此回滚一组提交也变得非常困难。

发布分支(release)

Release 分支是为新产品的发布做准备的,创建时要为即将发行版本分配一个版本号。

Release分支可能从 develop 分支分离,但是最终一定要合并到 developmaster 分支上。

develop分支创建新的 release 分支的关键时刻是 develop 分支达到了发布的理想状态。release 分支允许一些小bugs的修改和准备发布元数据(版本号、开发时间)。

创建一个release分支

若当前产品的发行版本是1.1.5,同时有一个大的版本即将发行。develop分支为下次发行做好了准备,决定好下一个版本是1.2。所以此时可以将release分支分离出来,给一个能反映版本号的分支名。

1
2
3
$ git checkout -b release-1.2 develop
$ ./bump-version.sh 1.2
$ git commit -a -m "Bumped version number to 1.2"

创建新分支以后,切换到该分支,添加版本号。这里,bump-version.sh 是一个虚构的shell脚本,它可以复制一些文件来反映新的版本(这当然可以手动改变–目的就是修改一些文件)。然后版本号被提交。

这个新分支可能会存在一段时间,直到该发行版到达它的预定目标。在此期间,bug的修复可能被提交到该分支上(而不是提交到develop分支上)。在这里严格禁止增加大的新features。他们必须合并到develop分支上,然后等待下一次大的发行版。

完成一个release分支

当一个release分支准备好成为一个真正的发行版时(即已经通过所有测试),release分支要合并到master上(因为每一次提交到master上的都是一个新定义的发行版)。提交到master上必须打一个标签,以便以后更加方便引用历史版本。最后,在release分支上的修改必须合并到develop上,以便未来的发行版包含这些bugs的修复。

1
2
3
$ git checkout master
$ git merge --no-ff release-1.2
$ git tag -a 1.2

合并到develop上:

1
2
$ git checkout develop
$ git merge --no-ff release-1.2

最后删除掉这个release分支

1
$ git branch -d release-1.2

测试分支(sit)

测试环境分支,只接受 featurehotfixrelease 分支的合并。研发内测联调通过后,由研发人员将自己的 feature 分支或 hotfix 分支代码合并到 sit 。该分支对应测试环境,测试人员的专用测试环境。

创建一个 sit

sit 可以基于 feature 分支创建

1
$ git checkout -b sit myfeature

完成一个 sit

1
2
$ git checkout -b sit myfeature
$ git branch -d sit

预发布分支(uat)

产品版本即将发布上线时,必须经过预发布分支的测试。

预发布环境,只接受 hotfixrelease 分支的代码合并。

创建一个uat

一般此分支是基于 release 分支创建:

1
2
3
$ git checkout -b uat-1.2 release
$ ./bump-version.sh 1.2
$ git commit -a -m "Bumped version number to 1.2"

如果生产环境下突然出现需要紧急修复的情况,uat 也可以支持 hotfix 的合并

完成一个uat

1
2
3
4
$ git checkout -b uat-1.2 release
$ ./bump-version.sh 1.2
$ git commit -a -m "Bumped version number to 1.2"
$ git branch -d uat-1.2

热修复分支(hotfix)

热修复分支通常用于紧急修复的情况,项目上线到生产环境后多多少少会有一些漏洞需要进行紧急修复,因此热修复分支可以基于master分支上对应与线上版本的tag创建。

可以基于 master 分支,必须合并回 developmaster 分支。

创建修补bug分支

hotfix branch 是从 master 分支上面分出来的。例如,1.2 版本是当前的生产环境版本并且有 bug。但是开发分支变化还不稳定,需要分出来一个修补 bug 分支来解决这个问题。

1
2
3
$ git checkout -b hotfix-1.2.1 master
$ ./bump-version.sh 1.2.1
$ git commit -a -m "Bumped version number to 1.2.1"

分支关闭时不要忘记更新版本号,然后修复bug

1
$ git commit -m "Fixed severe production problem"

完成一个 hotfix 分支

完成一个hotfix之后,需要把hotfix合并到masterdevelop分支去,这样就可以保证修复的这个bug也包含到下一个发行版中。这一点和完成release分支很相似。

首先,更新master并对release打上tag:

1
2
3
$ git checkout master
$ git merge --no-ff hotfix-1.2.1
$ git tag -a 1.2.1

编辑:你可能也会想使用 -sor-u 参数来对你的tag进行加密

下一步,把bugfix添加到develop分支中:

1
2
$ git checkout develop
$ git merge --no-ff hotfix-1.2.1

如果一个release分支已经存在,那么应该把hotfix合并到这个release分支,而不是合并到develop分支。release分支完成后, 将hotfix分支合并回release分支也会使得hotfix被合并到develop分支。

最后,删除临时分支:

1
$ git branch -d hotfix-1.2.1