大多数程序员已经离不开版本控制工具了。Git是一个免费的开源分布式版本控制系统,旨在快速,高效地处理从小型到大型项目的所有事务。1

我虽然已经使用过一段时间Git,但并没有使用它的大部分功能。大体上,我只使用了git clonegit add .git commit -m "xxxxxx"git pushgit pull等等非常简单的一些命令,从来没有真正深入去学习git的更多用法。

而现在,我也维护着一个有人使用的项目,开发和线上是并行存在的。感觉还是需要合理地运用好Git这个工具,妥善管理代码。在学习和使用Git的过程中,我感觉分支的管理方面最值得一讲。

项目默认位于master分支上,如果没有分支切换的操作的话,所有提交都位于master分支。这样的版本控制看上去就是一条直线,非常简单,但也很无趣。在这种情况下,线上代码和master分支上的代码是不同步的,并且功能上出现问题,要回溯也非常麻烦,完全没有利用好Git的特性。

事实上,有很多Git分支管理的范式可供参考,接下来我就将介绍一下我所了解到的比较主流的分支管理策略。

基本方案

我们来看看官方文档中的分支开发工作流:

因为 Git 使用简单的三方合并,所以就算在一段较长的时间内,反复把一个分支合并入另一个分支,也不是什么难事。 也就是说,在整个项目开发周期的不同阶段,你可以同时拥有多个开放的分支;你可以定期地把某些特性分支合并入其他分支中。

许多使用 Git 的开发者都喜欢使用这种方式来工作,比如只在 master 分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码。 他们还有一些名为 develop 或者 next 的平行分支,被用来做后续开发或者测试稳定性——这些分支不必保持绝对稳定,但是一旦达到稳定状态,它们就可以被合并入 master 分支了。 这样,在确保这些已完成的特性分支能够通过所有测试,并且不会引入更多 bug 之后,就可以合并入主干分支中,等待下一次的发布。

事实上我们刚才讨论的,是随着你的提交而不断右移的指针。 稳定分支的指针总是在提交历史中落后一大截,而前沿分支的指针往往比较靠前。

——Git官方文档2

这种方式也比较简单粗暴,简单来说就是保持master分支是公开的最新版本,而平时开发主要放在dev(development)分支上。然后每一个开发者有着自己的分支,开发者永远只在自己的分支上写代码,最后合并到dev分支上。

有一点值得一说的是,从master分支合并dev分支的时候,往往需要加上--no-ff参数:

1
2
3
4
# 切换到Master分支
$ git checkout master
# 对Develop分支进行合并
$ git merge --no-ff develop

其中ff代表的是“快进式合并(fast-farward merge)”,如果不加这个参数,该合并会直接将master分支指向dev分支,没有新的节点产生,使用--no-ff参数后,会执行正常合并,在Master分支上生成一个新节点,这样的版本演进更加清晰。3

这可以说是分支策略的基本原则,实际上大部分分支策略都是类似于这样的结构,只是细节不同罢了。

下面我将介绍在此基础上的其他的临时分支。

预发布分支

如果在编程中引用别人的库,那么就会经常看到release这个词。对于较为简单的项目而言在dev上测试完毕后就可以直接发布到master上了,而在比较大型的系统中会出现release分支。release分支即预发布分支,在这个分支上进行更为严格的测试。

然而其实我个人觉得release作为一个分支非常奇怪。因为测试以及bug的修复完全可以在dev分支上进行,没有必要单独弄一个分支出来。事实上在我眼中,release是一个版本的快照,它应该是直接可用的编译好了的发布文件,而不是项目的一个历史记录。这一部分应该是超脱于版本管理之外的。我们可以看到GitHub就提供了这种机制:

Releases are first-class objects with changelogs and binary assets that present a full project history beyond Git artifacts.

——The GitHub Blog4

因此,在实际项目中,我可能不会单独定义release分支。也许在项目庞杂到一定程度之后会需要这样的一个分支,但目前对我的意义不是很大。

特性分支

特性分支对任何规模的项目都适用。 特性分支是一种短期分支,它被用来实现单一特性或其相关工作。 也许你从来没有在其他的版本控制系统(VCS)上这么做过,因为在那些版本控制系统中创建和合并分支通常很费劲。 然而,在 Git 中一天之内多次创建、使用、合并、删除分支都很常见。

——Git官方文档2

从上述文字中可以看到Git作为版本控制工具的优越性。有一部分人的分支策略是这样的:除了master和dev两个分支以外,其他分支按照开发者分开,每个人只写自己负责模块的代码,然后推送到dev上。这种方式有一个致命的缺陷,那就是加入项目中有一个人将错误的代码推送到dev分支上,所有其他人都会受到该错误的影响,而这个错误来自于别人的分支,并且其他人如果想要修复这个BUG,必须要合并dev分支上的错误代码修改后再提交到dev,这样会出现可怕的冲突问题。在这个时候,最好的办法是在推送错误代码的人的分支上修改。既然按照开发者来区分分支,势必有严格的权限管理,这样就得集体等待那个人修改完毕才能继续正常进行其他测试。并且每个分支的意义不明,没有办法直接从分支上看出修改了那些部分的代码。

而如果我们建立特性分支,你就能快速并且完整地进行上下文切换(context-switch)——因为你的工作被分散到不同的流水线中,在不同的流水线中每个分支都仅与其目标特性相关,因此,在做代码审查之类的工作的时候就能更加容易地看出你做了哪些改动。 你可以把做出的改动在特性分支中保留几分钟、几天甚至几个月,等它们成熟之后再合并,而不用在乎它们建立的顺序或工作进度。2

修复分支

线上的代码有错误是最糟糕的一种情况,但倘若出现了这种情况,我们就需要从master分支上新建一个临时的hotfix分支,在此分支上修复BUG,然后分别merge到master分支和dev分支上。

总结

综上,我所遵循的实践是这样的:

  • master分支上保持与生产环境一致的代码。
  • dev分支独立于master分支,不在dev分支上直接开发。
  • 所有合并需要添加--no-ff参数,这样能保持分支结构的清晰。
  • 每次需要实现新的功能,则开启一个feature分支,具体的分支命名为“feature/xxx”,功能开发完毕并合并到dev分支后,删除该分支。
  • 修复线上BUG,则从master分支开启一个hotfix分支,具体的分支命名为“hotfix/xxx”,BUG修复后并合并到master分支以及dev分支后,删除该分支。
  • 不采用release分支。
  • 其他分支酌情添加。

这种方式不一定完全科学,如果其他相关的建议或意见,可以在评论区进行留言。