Team Foundation Server

Visual Studio TFS 分支和合并指南

Bill Heys

下载代码示例

自从 2006 年组建以来,Visual Studio ALM Rangers 团队就一直在 Microsoft 开发事业部工作,旨在促进 Visual Studio 产品组、Microsoft 服务与 Microsoft 最有价值专家 (MVP) 社区间的协作。Rangers 团队通过解决功能缺失问题以及消除阻碍产品采用的因素,实现他们的基本目标宣言:“通过为缺失的功能或指南提供带外解决方案来加速 Visual Studio 的采用”。借助各种技术专家和业务专家之间的合作,Rangers 通过共享实际经验来为社区提供强大支持。(如需了解有关 Rangers 的更多信息,请访问 msdn.microsoft.com/vstudio/ee358786。)

Visual Studio Team Foundation Server (TFS) 分支指南 2010 (tfsbranchingguideiii.codeplex.com) 通过提供从社区学习到的实际动手实验和课程,将使用 Visual Studio TFS 2010 进行分支与合并方面的富有深刻见解的实际指南进行了整合。在本文中,我们将为您介绍我们正在为下一版指南而制定的一些高级分支方案。

分支:“当前情况”

Rangers 分支指南是在 Visual Studio 2005 和 TFS 2005 发布之后作为一个 Rangers 项目而开始的。Rangers 指南是于 2007 年在 CodePlex 上首次发布的。

2008 年,Rangers 启动了分支指南 II 项目。在第二次发布中,我们将该指南重新组织为一组相关文档(“主要内容”、“方案”、“问题与解答”、“图表”、“海报”等)。第二批文档中的每个文档都建立在主分支文档中提出的初级指南之上。Rangers 分支指南 II 是于 2008 年下半年在 CodePlex 上发布的。

2009 年,Rangers 团队再次启动了一个新的分支指南项目:分支指南 2010。第三次发布的分支指南侧重于展示 Visual Studio 2010 和 TFS 2010 中的许多新的分支功能。2010 版中的一个重要新增功能就是分支可视化。

可能由于我们将最新版本命名为“Rangers Visual Studio TFS 分支指南 2010”,因此造成用户误认为该指南只适用于 Visual Studio 2010。我们希望澄清的是,2010 指南文档中展示的最佳实践与指南仍适用于早期版本的 Visual Studio 和 TFS。事实上,Rangers 团队已经从使用其他工具进行源代码控制管理 (SCM) 的人员那里获得积极的反馈。

2011 年,Rangers 团队将再次计划对 Rangers 分支指南进行一次更新。

您可随时在 CodePlex 网站发布疑问、中肯的反馈或关心的问题。

分支目标和策略

分支的一个主要目标是在并行工作流之间提供隔离。在当前的 Rangers 分支指南 2010 中,我们更侧重于版本隔离而不是复杂开发计划中的隔离。

在许多情况下,某种产品下一个版本的所有开发活动都可由单一开发团队来完成。在这种简单情况下,只需要一个开发分支就可以将开发工作与不断进行的稳定化(主分支)或持续的工程设计(交付产品版本,带有不断进行的修补程序和服务包支持)隔离开。

Rangers 常常被问到有关为更加复杂的开发方案提供支持的问题,在这种开发方案中,单一的开发分支无法针对较大型的产品开发工作提供足够的灵活性或隔离。在 Rangers 分支指南的下一个版本中,我们将在复杂开发方案(例如功能团队开发)方面增加更多的说明。

我们希望将分支策略讨论分为两个方面:

  1. 我的组织如何开发 软件?我们是否需要一个更小、更简单的团队结构,或者我们是否需要对进行并行开发工作的更多复杂团队提供支持?
  2. 我的组织如何向其客户(不管是内部还是外部客户)发布软件?我们是否需要对多个已发布的版本提供支持?我们是否需要提供修补程序或服务包?

在某些情况下,组织的发布策略可能影响开发过程,尤其是开发团队的结构。但在很多情况下,发布过程和分支策略的复杂性可能与开发过程和分支策略的复杂性无关。

在设计一种分支策略时,不仅要考虑分支结构,而且还要考虑分支过程。例如,在 Rangers 分支指南 2010 所描述的基本分支计划中,只有三种分支(主分支、开发分支和发布分支)。一个良好的分支策略将会描述分支关系(例如,主分支是开发分支和发布分支的父分支)。

另外,一种分支策略应该描述该分支结构所必然需要的过程。例如,在主分支中生成代码的频率如何?从主分支向开发分支合并代码(正向集成)的频率如何?从开发分支向主分支合并代码(反向集成)的条件是什么?(等等)让我们讨论一些典型的分支方案。

功能团队方案

组织经常需要采用一种分支策略,为需要多个开发团队或功能团队并行工作的大型、复杂的开发活动提供支持。这样就产生了需要多少个单独的开发分支的问题。如果我有多个开发分支,我该何时以及如何将一个团队开发的功能与其他团队开发的功能进行集成?这些问题的答案应体现在一个开发分支策略中。

让我们首先介绍一种复杂的开发方案。虽然整个方案可能有一个共同的发布计划,但可能会有多个功能团队分别致力于实现独立的里程碑。这些功能在完成并经过测试后将被集成到主分支之中。

在一个团队中,各个开发人员都使用局部工作区将所做更改与团队中其他人的更改进行隔离。要将一个功能团队所做的更改与针对同一产品并行工作的其他团队所做的更改进行隔离,功能团队分支提供了一种良好的方法。如果没有这种功能团队隔离,则由一个团队所做的更改可能会带来将影响其他团队速度的重大变化。

创建用于功能团队隔离的分支结构相对简单。但是,我们首先需要计划以后对功能团队分支进行集成的方式。我们是否要像图 1 所示那样,在主分支与功能团队分支之间添加一个“集成分支”?

图 1 主分支和集成分支

或者,我们是否要取消集成层而以另外一种方式来集成功能团队更改?最佳实践建议是什么?

我们建议尽量减少分支层次结构中的层次数目。在主分支和功能团队分支之间添加一个集成层之后,实际上将在主分支和功能团队分支之间移动更改所需的合并工作加倍了。分支有助于隔离更改,但分支的代价是需要在各个分支之间合并代码,并需要解决避之不及的合并冲突。添加集成层会将合并工作加倍,并且可能也将解决合并冲突的工作量加倍。

如果取消集成层,就可以将分支层次结构中的层数减少。但是,功能团队 1 与功能团队 2 的集成将在哪里进行?将在哪里对集成进行测试?为了使主分支尽可能保持稳定,请避免向主分支中引入未经测试的集成更改。在没有集成层的情况下,功能合并和集成测试必须以可控的方式在功能团队分支自身内部完成。

建议在主分支(稳定分支)中执行日常生成,并在一个良好的日常生成之后,执行从主分支到开发(功能)分支的合并。在功能分支中的代码相对稳定之前,请不要将代码从功能分支合并回主分支。换言之,功能分支应通过质量保证大门才能与主分支合并。

只有当功能分支中的代码被认为“已准备好发布”或“已准备好与其他团队共享”时,我们才能考虑将此功能分支与主分支或其他功能分支集成。图 2 说明了每个“已准备好发布”里程碑之后的这一过程。

图 2 功能分支

下面是处理步骤:

  • 将功能团队 1 分支与主分支合并之前,执行从主分支到功能团队 1 分支的一次最终合并(正向集成,即 FI)。
  • 完成对主分支中的代码与功能团队 1 分支中的代码进行集成的最终测试。
  • 在功能团队 1 分支中的代码稳定之后,将该代码合并回主分支(反向集成,即 RI)。
  • 此时,主分支中的代码将与来自功能团队 1 的代码合并。
  • 在主分支中执行等同于日常生成的生成与测试。在下一次成功生成主分支时,将主分支合并到每个功能团队分支。最初,这会导致功能团队 1 中的代码与功能团队 2 中的代码合并。
  • 在功能团队 2 分支中,测试功能团队 1 代码与功能团队 2 代码的集成。
  • 当功能团队 2 代码准备好发布或准备好与其他团队共享时,将功能团队 2 代码合并回主分支。但首先要执行一次从主分支到功能团队 2 的最终合并,并测试最终集成。

注意:省略单独的集成层的一个关键要求是能够对集成进行自动化测试。在团队致力于识别和解决在将很多更改合并到一个分支的过程中产生的 Bug 时,自动化测试有助于降低对代码速度(也就是功能团队工作效率)的影响。

如果不能对集成更改进行自动化测试,所存在的风险是功能团队的代码速度会受到不利影响,因为他们需要执行手动测试来识别和解决 Bug。在这种情况下,组织可能会考虑在主分支和功能分支之间添加一个集成层。如前所述,集成层可能会导致合并以及解决合并冲突工作的增加。但其益处是,有了这个层之后,集成对功能团队的代码速度产生的影响较小。

良好的分支策略需要有健全的分支结构以及健全的分支过程,这样才能确保功能团队获得最高代码速度,同时保持主分支的稳定性。

公共代码共享方案

在项目之间共享公共代码对于很多组织来说都是一种挑战。在 Visual Studio 中,共有三种用于在项目之间或解决方案之间共享代码的主要方法:

  • 文件链接
  • 二进制(程序集)共享
  • 源代码共享

正如我们在本文中别处所述,还有多种用于代码隔离的方法:

  • 团队项目隔离
  • 分支隔离
  • 工作区隔离

为组织选择正确的代码共享策略可能涉及将代码共享方法与隔离方法进行组合。

**文件链接:**这是 Visual Studio 的一项功能(“添加现有项”),其中,多个项目可以共享对同一源文件的引用。文件链接更适合所共享的文件数目有限的小型项目。(这类似于 Visual Source Safe 中的文件共享。)

采用文件链接时,将仅保留所链接的源文件的一个版本。对链接文件所做的更改会立即被与该文件链接的所有项目接受。文件链接的缺点在于,对链接文件进行的更改应与所有相关项目团队进行协调。甚至经过仔细协调的更改也可能引起相关项目中的重大更改。

**二进制共享(程序集引用):**在二进制共享中,一个 Visual Studio 解决方案将通过程序集引用来引用共享代码。此时,生成或编译相关解决方案并不会同时编译公共的共享源代码。与使用项目引用相比,使用程序集引用来编译相关项目的速度将更快。

拥有公共代码的团队具有完全的所有权和控制权,在理论上,这意味着产品的控制、版本控制和质量或许会更佳,并会避免分支和合并的复杂性。

由于重复使用公共代码的团队不能访问公共源代码,所以他们依赖于拥有公共源代码的团队来添加新的功能并解决公共共享代码中的 Bug。

用于公共代码的程序集可通过复制到一个人所共知的文件共享中而得到共享,而该文件共享可由相关项目来引用。可能需要将签名程序集添加到全局程序集缓存中。或者,可将程序集从公共代码团队项目复制到相关项目主分支下的一个 bin 文件夹中。

**源代码共享:**通过 Visual Studio 中的源代码共享,相关项目可针对公共共享代码使用项目引用。生成解决方案时,将生成所有项目,包括公共共享代码项目。对于复杂项目,如果具有很多对共享代码的项目引用,则可能会显著增加生成时间。

在这种情况下,将由一个团队在其自身的 TFS 团队项目中拥有和管理公共共享代码。若要共享此公共代码,首先要将代码分支到含有占用(相关)项目的团队项目的文件夹中,如下所示:

  • 在相关项目的团队项目中,创建一个名为“Share”的文件夹(例如,$\Product1\Share)。
  • 将公共库(例如 EnterpriseLibrary)的 Main 分支分支到相关项目的 Share 文件夹中,例如,将 $\Enterprise Library\Main 分支到 $\Product1\Share\EnterpriseLibrary 中。
  • 将相应公共代码项目添加到相关项目的解决方案。
  • 创建从相关项目到解决方案中现有公共代码项目的项目引用。

注意:TFS 2010 中不支持嵌套分支。如果您尝试执行的分支操作会导致在文件夹结构中的现有分支之上或之下创建新分支,则可能会发生嵌套分支错误(请参见图 3*)。*

图 3 在 Team Foundation Server 2010 中引起错误的嵌套分支的示例

您的组织需要决定是否应在每个相关项目中允许对公共共享源代码进行更改。为防止更改,可在从公共库分支之后将新分支设置为只读。随后,必须在公共库团队项目中完成对公共代码源的所有更改,然后将更改合并到相关项目的团队项目中。

或者,可对相关团队项目中的共享代码源进行更改。可以将这些更改(通过反向集成)合并回公共库团队项目。您的组织需要仔细管理这些更改以避免不兼容性,这些不兼容性会导致将这些更改合并回公共库变得困难或不可能,或许还会产生该共享代码的多个副本。

体系结构工具和建模方案

在 Visual Studio 旗舰版中,您可以创建 UML 和层模型,这些模型存在于其自身的单独 Visual Studio 项目中,并且可以包含很多程序包,涉及解决方案的不同部分(请参见vsarchitectureguide.codeplex.com 上的“体系结构工具指南”和 msdn.microsoft.com/library/57b85fsc.aspx 上的“应用程序建模”)。

为了探索是否可对模型进行分支和合并,我们可创建一个包含三个方案的简单测试环境,如图 4 所示。

图 4 测试环境中用于测试分支和合并可能性的评估方案

我们可以通过一个解决方案创建一个 Main 分支,该解决方案包含一个模型项目,而该模型项目带有一个空的 UML 类关系图作为一个假定稳定项目。随后,我们可将 Main 分支到 Scenario1、Scenario2 和 Scenario3,然后将每个方案分支到代表开发团队的 Dev1 和 Dev2 分支,如图 5 所示。

图 5 将源资源管理器中显示的演示团队项目进行分支

显而易见,我们在分支时没有遇到问题,但是我们能够将更改反向集成(合并)到该模型中吗?

在 Scenario1 中,团队没有对模型进行更改,而在 Scenerio2 中,两个团队中只有一个团队扩展了模型。结果,从开发分支到关联的方案分支的反向集成平安无事,Scenario1 模型未发生改变,而 Scenario2 模型得到更新。

Scenario3 是一个更现实的例子,其中,两个团队都对模型进行了更新。Dev1 团队创建两个类,而 Dev2 团队创建一个类。

细心的读者会注意到,两个团队都创建了一个 Class1 类,但该类的操作却不同。

将两个开发分支中的第一个分支反向集成回 Scenario3 分支将给出虚假的安全感,即合并是很容易的。但是,当第二个团队将更改合并到 Scenario3 分支时,三个文件(.classdiagram、.layout 和 .uml)之间的冲突会阻止签入,如图 6 所示。

图 6 由于两个团队对 Class1 类所做的更改,合并引起了冲突

我们可以选择选项“保留目标分支版本”或“采用源分支版本”,并用“是”来回答是否可以合并这个问题。但结果将是一个团队丢失所做的更改,这让人感到十分不快。替代方法是选择“在合并工具中合并更改”选项以进行手动合并,但对我们大多数人来说,这样做不切实际、不直观并容易出错。

因此,体系结构模型的分支和合并是可能的,但建议这样做吗?UML 模型的问题是,虚拟化(如类关系图)将分布在三个主文件(.layout、.classdiagram 和 .uml)中,如图 7 所示。

图 7 虚拟化分布在三个主文件中

.layout 文件定义了模型中各个形状的大小和位置。.uml 文件是“主”模型,而 .classdiagram 文件含有存在于该图表中的 .uml 文件内容的缓存。

合并也会十分困难,因为要对建模工具中的正常编辑进行验证,并且经常要通过工具对正常编辑进行增强,以避免产生无效状态。在纯 XML 合并中不会进行这种验证,因为这会带来创建无效模型的风险,这种无效模型甚至可能无法打开。

如果每个团队仅对自己的图表进行更改,并且这些更改代表单独的程序包中的类,则问题可能会减少,因为大多数更改会出现在单独的文件中。即便如此,不可避免地会存在对跨越程序包边界的关系进行更改的情况。

事实上,某些团队希望在创建新的产品迭代时进行分支,这会造成源代码、文档和模型的分叉。诸如活动、序列、层和类关系图这样的模型是在迭代过程中发生变化的模型的很好例子,而交付团队将继续执行主流开发和维护。因此,模型可能并且经常 在两个或更多分支中发生变化,这意味着我们将在某一时间遇到分支情况和通常具有挑战性的合并情况。

所有当前模型都是分支的良好候选对象,但没有模型适合进行合并。如果可能发生具有挑战性和易于出错的合并,则提出以下两点建议:

  • 通过定义代表单独程序包中的类的解决方案和模型视图来避免合并。体系结构工具指南基于程序包提出了一个解决方案视图(如图 8 所示)和一个模型视图(如图 9 所示)。当图表含有多个程序包中的内容时(对于“类”、“组件”和“用例”是可能的),必须加以小心。在这种情况下,为完全避免冲突,用户必须避免编辑属于“外来”程序包的元素的元数据。

    图 8 所提出的基于程序包的结构(解决方案视图)

    图 9 所提出的基于程序包的结构(UML 模型资源管理器视图)

  • 与共享组件类似,将模型保持在一个不会分叉的分支上。

回退是为了使用图 10 中所示的“保留目标分支版本”或“采用源分支版本”选项,以可视方式手动编辑一个分支中的模型。

图 10 手动模型编辑合并

例如,模型将分流到所示的两个分支中,并通过以可视方式比较模型并在顶部分支中手动更新模型来进行手动合并(步骤 3)。含有整合模型的分支随后反向集成到主分支中(步骤 4),而含有过时模型的另一个分支将在解决模型冲突时使用“采用目标分支版本”选项进行反向集成(步骤 5)。

总之,在自动模型合并方面目前还没有很好的策略。建议的策略是尽量避免模型的分支和合并情况,或在合并之前使用可视和手动模型编辑。

现在,我们已介绍了多种您在复杂的实际环境中可能遇到的新分支情况。在本系列的下一篇文章中,我们将研究团队项目和团队项目集合。

Bill Heys是位于美国新英格兰地区的 Microsoft Americas Consulting Services 的一名高级顾问。作为 Visual Studio ALM Ranger,Heys 专门从事于使用 Visual Studio 进行自定义应用程序开发和应用程序生命周期管理。他的博客网址为 blogs.msdn.com/b/billheys

Willy-Peter Schaub是位于 Microsoft 加拿大开发中心的 Visual Studio ALM Rangers 的解决方案架构师。从 80 年代中期以来,他一直致力于提供软件工程中的简易性和可维护性。他的博客网址为 blogs.msdn.com/b/willy-peter_schaub

衷心感谢以下技术专家对本文的审阅:Marcel de VriesJens JacobsenBijan JavidiAlan Wills