常见分支模式(译)

##前言

今天在将分支合并回 trunk 的时候,发现之前采用的分支模式不太合适,于是去查阅了 SVN 的用户手册 Version Control With Subversion - Common Branching Patterns,对分支管理有了更全面的认识。

版权声明:本文翻译自 Ben Collins-Sussman, Brian W. Fitzpatrick, C. Michael Pilato 撰写的 Version Control With Subversion。这是一本自由书籍,遵循 Creative Common Attribution License,这里是它的版权协议,本译文同样遵循此协议。

##译文

使用分支 (branch) 和合并 (merge) 的方式有很多,本节介绍了其中最常用的几种。

版本控制是在软件开发中最常用到的概念,所以我们来看一下被开发团队们最常用到的分支/合并模式。如果你在软件开发中并没有用 Subversion 来做版本控制,直接略过这一节就好。如果你是第一次在软件开发中使用版本控制,那请格外注意,因为这些模式通常已被认为是最佳实践。这些模式并不是 Subversion 才独有的,而是适用于任意的版本控制系统。我们以 Subversion 来描述这些模式,让它更容易理解。

###用于发布的分支 (Release 分支)

大多数软件都遵循这样的生命周期:编码,测试,发布,如此循环往复。在这个过程中存在两个问题。第一,在质量保证团队测试稳定版本的同时,开发团队依然需要继续开发新的功能。当软件在接受测试时,新的开发工作不能停顿。第二,开发团队大多都需要支持较老的、已经发布出去的版本。如果在较老的代码中发现了 bug,这个 bug 很有可能也存在于已经释出的版本中。客户想要马上修复它,而不用等待下一个大版本发布时才能解决。

这两个问题借助版本控制可以解决。以下是经典的过程:

  1. 开发者将所有新工作提交到 trunk 上。 每日的改变都被提交到 /trunk :包括新功能、bug 修复等。
  2. 复制 trunk 到一个 “release” 分支。 当团队认为软件可以发布的时候(比如,发布 1.0 版本),/trunk 会被拷贝到 /branches/1.0
  3. 团队继续并行工作。 一个团队开始对 release 分支进行严格的测试,同时,另一个团队在 /trunk 继续新的工作(比如,开发 2.0 版本)。无论哪一个团队发现了 bug,在必要的时候,对于这个 bug 的修复都可以移植 (ported) 到另一个团队。这个过程在某个时间点会停止,然后这个分支将会被“冻结”,以供发布前进行最终测试。
  4. 标记这个分支 (tagged) 并发布。 当测试结束后,为 /branches/1.0 创建一个标记到 /tags/1.0.0 ,作为一个引用快照。这个 tag 中的版本将会被打包并发布给用户。
  5. 这个分支随着时间的推移进行适当的维护。 2.0 版本的开发工作在 /trunk 中继续,修复的 bug 也陆续从 /trunk 中移植到 /branches/1.0 。当修复的 bug 数量累积到足够多的时候,管理层可能决定发布 1.0.1 版本,此时应把 /branches/1.0 创建标记到 /tags/1.0.1 ,这个 1.0.1 的 tag 会被打包发布。

这整个过程将随着软件的成熟而不断反复进行:当 2.0 的工作完成后,一个新的名为 2.0 的 release 分支会被创建、测试、标记,最终发布。几年后,整个 repository 将会是好多个处于“维护”模式的 release 分支,和好多个标志着最终发布给用户的 tag。

###用于新特性的分支 (Feature 分支)

用于新特性的分支 (feature branch) 是在这一章最合适的例子(当 Sally 继续在 /trunk 工作时,你需要开发一个新功能)【译者注:请参见原文在本节之前创设的情境】。这时一种临时的分支,用于进行一些复杂的修改,而不会影响到 /trunk 的稳定。不像 release 分支(需要永久支持),feature 分支的生命历程是:创建,使用它来进行新特性的开发,合并回 trunk,然后被永久删除。它们的作用是有限的。

另外,什么时候需要创建 feature 分支是不一定的,与不同工程的不同情况有关。有些工程从来不使用 feature 分支:所有人都可以自由地向 /trunk 提交。这种系统的好处是非常简单--不需要有人懂得有关分支和合并的知识。缺点是 trunk 的代码经常是不稳定的甚至是不可用的。而有些工程将分支用到了极致:所有的修改都 从不 直接提交到 trunk。即使是最微不足道的修改,也需要专门创建一个短命的分支,仔细审查,然后合并回 trunk。然后这个短命的分支就被删除了。这样可以保证在任何时间 trunk 都绝对稳定并可用,但是导致了大量的流程开销。

大多数工程采用了折中的方式。这种方式坚持 trunk 在任何时间都可以正常编译并通过回归测试。只有当某次修改包含大量提交,可能造成不稳定时,才需要创建 feature 分支。一个好的判断准则是:如果一个功能需要开发者独立工作好几天,然后一次性提交大量的修改(因为要保证 trunk 的稳定,所以要一次性),那这样的修改是不是大到需要进行代码审查了?如果对这个问题的回答是肯定的,那这些修改就应该在一个 feature 分支中进行。这样,开发者就能以递增的方式向 feature 分支中多次提交,而不必担心影响稳定,其他人也可以很容易地审阅到分支中这些代码的变化情况。

最后还有一个问题,如何在工作的进程中保持 feature 分支和 trunk 的同步。正如我们之前所说,在一个分支上工作数周甚至数月是有很大风险的;trunk 上的修改越来越多,与 feature 分支中对应代码行的差距越来越大,从 branch 合并回 trunk 可能会变得噩梦一般。

可以通过时常将 trunk 的变更合并到 branch 来避免这种状况【译者注:合并不是单向的,可以从 branch 合并回 trunk,也可以从 trunk 合并到 branch,后者也被称为“同步”】。制定一个原则:每周一次,将上周 trunk 上的变更合并到 branch 中。

在某个时间点,你会准备将“已同步”的 feature 分支合并回 trunk 中。要做这件事,首先要进行最后一次同步,将 trunk 最新的修改同步到 feature 分支中。完成后,最新的 branch 和 trunk 除了你在 branch 上的修改之外,将是完全一致的。然后就可以通过 reintegrate 选项将分支合并回去了:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cd trunk-working-copy
$ svn update
At revision 1910.
$ svn merge --reintegrate http://svn.example.com/repos/calc/branches/mybranch
--- Merging differences between repository URLs into '.':
U real.c
U integer.c
A newdirectory
A newdirectory/newfile
U .

另外一种理解这种模式的方法是,每周从 trunk 向 branch 的同步可以看作是在 working copy 中执行了 svn update ,最后的合并操作可以看作是在 working copy 中执行了 svn commit 。既然这样,working copy 不就可以认为是一种非常浅的、私有的分支吗?它就是一种特别的分支,每次只有很少的变更。