65.9K
CodeProject 正在变化。 阅读更多。
Home

项目定义和版本控制

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (11投票s)

2013年2月25日

CPOL

63分钟阅读

viewsIcon

20041

项目结构和构建场景

1. 适用性和范围

本文档提出的建议和指导适用于发布日期时的 Team Foundation Server 版本控制、Visual SourceSafe 和 Subversion。请遵循 Visual SourceSafe 项目的项目结构指南,因为这些项目将很快迁移到 Subversion 或 Team Foundation Server。

2. 目的和来源

本文档力求最大限度地减少对使用特定产品的关注,旨在为建议的存储库、项目和解决方案结构提供指导和依据,这些结构基于任何组织软件项目遇到的场景以及 IT 行业和软件配置管理 (SCM) 普遍推崇的最佳实践。我旨在向您展示如何根据 SCM 以及涉及的决策和概念来完成特定目标。

本文档借鉴了使用各种版本控制系统 (VCS) 的开发人员的综合公开经验。核心版本控制结构 (BTT) 的建议主要由 Subversion 用户发布和推广。这本身就代表了过去 21 年来全球数百万开发人员在各种规模的项目上的综合经验。当然,每个 VCS 供应商都有关于此主题的指导和建议,但侧重点各不相同,以突出其自身系统的功能。

本文档还包括项目结构指导,虽然这些指导与任何类型的版本控制没有特定关系,但无疑能使一些版本控制任务得以干净、简单地执行。项目结构指导本身借鉴了与版本控制和软件配置管理 (SCM) 相关或不相关的来源。这些指导融合了众多 IT 学科、项目模型以及来自所有平台上的开发人员和供应商的建议的理论和实践经验。

本文档之后将是一系列集中的“操作指南”,使用当前使用的产品和工具演示所呈现的每个场景和任务。

3. 警告

良好的版本控制系统就像时间机器。您可以随意删除内容,并确信只要需要,随时都可以找回来。大多数版本控制系统提供的视图显示一个目录结构,看起来与文件系统非常相似。重要的是要认识到您看到的只是特定时间点(通常是现在,除非您指示它显示某个早期日期或更改时的系统)的结构。

一个好的版本控制系统会忠实地存储您放入其中的任何内容,并且永远不会丢失它,这使得您永远不要存储任何您不想永久保留记录的内容变得相当重要。私人密码、信用卡或银行账号、个人数据以及任何形式的 PHI 都不应放入版本控制。尽管它可以轻松地从当前视图中删除,但它将始终存在于系统的历史视图中;任何具有适当访问权限的人都可以访问。

如果版本控制系统用于维护源代码,请确保只存储源代码信息和构建软件解决方案所需的依赖二进制文件。例如,请勿存储 Visual Studio 安装 DVD 的 ISO 映像或其他工具的安装包。这只是浪费空间,因为这些都可以从其他来源轻松获得,并且如果放入版本控制,则永远无法真正删除。

话虽如此,请一定删除您不再需要的任何内容。系统的默认、当前视图应该只显示现在相关、受支持或正在积极开发的内容。不要将已死或已退役的分支保留在当前视图中超过必要的时间。如果您将来再次需要它们,它们仍然在那里。

TFS 2008 支持“销毁”功能

聪明的读者可能会很快指出,“销毁”(Destroy)功能,即永久从版本控制中删除某个项的功能,在 Team Foundation Server 2008 中可用。这是一把双刃剑。

从安全角度(例如 PHI,或个人健康信息)和存储库维护角度(例如意外提交到版本控制的 CD/DVD 映像或 SQL 数据库)来看,完全根除数据所有痕迹的功能当然很重要,但必须非常谨慎地使用,并且极端小心。

请记住,版本控制的两个主要目的是允许人们共享信息,并保留所有对这些信息的更改历史记录。销毁项的功能会破坏这两个目的。

销毁功能可能产生严重的意外后果

首先,考虑一个文件,其中包含必须从系统中删除的 PHI,无论后果如何。该文件本身已多次更改,分支到六个不同的发行版,并与“安全”信息多次合并。您决定“销毁”首次出现 PHI 的文件。其他版本,无论是在引入 PHI 之前还是之后,会发生什么?该文件的历史会发生什么?

此外,还考虑一种情况,即发行版被分支到错误的位置。不是移动新分支,而是再次分支到正确的位置,并且不是删除错误的那个分支,您决定使用“销毁”将其永久删除。正确分支的历史会发生什么……不见了吗?因为分支的基础被销毁了,正确的分支甚至还存在吗?

4. 版本控制模型

当前有两种主要的版本控制模型:锁定/编辑/发布模型和复制/修改/合并模型。有关每种模型的优点和缺点的描述和比较,请访问版本控制模型

第一个是锁定/编辑/发布模型,其中用户必须请求对文件进行独占访问才能编辑它,然后记住提交更改(并释放锁定)以允许其他人以相同的方式修改文件。这是 Visual SourceSafe 默认使用的模型,所有对单个文件的操作都需要串行化(一个用户接一个用户)。

第二个模型是复制/修改/合并模型,在这种模型中,用户可以在从服务器检索文件后随意编辑任何文件,但必须在将更改提交回服务器时解决其他用户引入的任何潜在冲突。这是 TFS 默认使用的模型,并允许多个任务并行进行。

从 Visual SourceSafe 迁移的用户应该特别注意这些更改。虽然在大多数版本控制系统中,“签出”一词仅指从服务器上的特定 URI 和版本创建工作副本的行为;在 TFS 中,此操作意味着“设为可写”,并保留“获取最新”和“获取…”等术语来执行签出。当您在 TFS 中执行“签出”时,它不会从服务器检索任何更改(它只切换文件的只读属性),并且默认情况下,它不会阻止其他用户签入他们的更改。

虽然 TFS 可以修改为更像 VSS 的方式工作,包括独占锁定语义,但请勿设置这些选项。您将减少或消除并行开发带来的许多好处,并退回到串行化、低效的工作方式。

最近听到的一个支持锁定/编辑/发布模型的论点是:“我们的团队在努力方面很分散,不如其他团队那样紧密集成。我们今年还将再次发展。因此,我们认为日常协作和沟通不足以让多个人同时处理同一个文件……可以说是作为一种安全措施。”这个论点是有缺陷的。许多多样化且全球分布的用户每天都在使用复制/修改/合并模型(并且根本无法使用锁定/编辑/发布模型工作)。例如,看看任何开源软件 (OSS) 项目。CodePlex、Google Code、SourceForge.net 以及许多其他公共和私有存储库上的所有代码都是通过这种分布式模型开发的,其开发人员可能甚至不知道彼此的存在。非常少数高度成功的项目也以这种方式开发,例如 Microsoft Enterprise Library、NUnit 和 ASP.NET AJAX Controls。

如果我们有必要,我们将会在企业级别强制执行多项签出选项,并移除团队项目管理员调整这些选项的能力。通过变更集注释策略,已经强制执行了支持大型和地理分散团队所需的协作级别。

5. 日志消息和变更集注释

“所有版本控制系统都必须解决同一个基本问题:系统如何允许用户共享信息,同时防止他们意外地干扰彼此?用户很容易在存储库中意外覆盖彼此的更改。” – 版本控制模型

复制/修改/合并模型支持并行开发,并且是有效使用现代编程工具的必需。例如,重构工具(内置于 Visual Studio 2005 中)可以用一次更改修改大量文件……如果其中任何文件被另一个用户锁定(独占签出),则在锁定/编辑/发布模型中将无法实现这一点。即使是向项目中添加一个新文件的简单操作,也会修改至少两个文件(项目文件和要添加的文件),并且可能还有更多文件(设计器、代码隐藏文件等)。

然而,复制/修改/合并模型的缺点是,人们必须沟通他们正在做什么。好的版本控制系统通过拒绝任何未经更改说明的提交尝试来强制执行此沟通,或者将其与更改请求关联起来。

如果启用此选项,Team Foundation Server 可以更进一步,要求每个更改集都与请求的工作项相关联。这意味着任何任何业务项目所做的更改都需要先有对此特定更改的请求,并且这些请求及其关联的更改可以追溯到业务或技术需求。在开发人员开始工作之前,项目经理或技术/团队负责人必须详细列出这些更改请求。

实现工作项跟踪需要每个团队成员都有极高的纪律性,并且给项目经理和团队负责人增加了当前开发模型中不存在的额外不必要负担。因此,我们认为目前这种实现方式是不可行的。(顺便说一句,这是 TFS 中 MSF for CMMI 过程模板中使用的默认实现。)

我们不要求每个更改都与请求相关联,而是使用并推荐一种实现方式,即每个更改都必须与该更改的描述相关联。在 TFS 中,这被称为变更集注释策略。

版本控制中任何一组更改的每次提交都必须附带一个注释,描述为什么进行了这些更改。这是一个要求,所有团队成员都必须清楚地理解和传达。

不幸的是,TFS 也允许单个用户在每次提交时选择覆盖此策略,但您仍然必须说明覆盖策略的原因才能执行提交。请勿尝试覆盖策略并将您的更改描述放在“策略覆盖原因”中。这是对注释和策略覆盖的错误使用。

注释本身必须描述为什么进行了更改以及/或进行这些更改的意图或目标。诸如“sdf”、“添加了更改”、“修改了函数”、“每日签入”等注释将被拒绝,并且必须由开发人员或其团队负责人或团队项目管理员纠正。考虑一下注释在下周、三个月或两年后会有什么意义。在当前更改集之前和之后的注释旁边,它是否足以向您或您的读者传达更改了什么以及为什么?好的注释示例包括“纠正了文档中的拼写错误”、“向解决方案添加了单元测试项目”、“更改了 *web.config* 以启用模拟”以及“删除了已弃用的 *BaseDAO.cs* 文件——项目中不再需要或使用它”。

企业架构团队会审查所有团队项目的所有提交的注释,并可能将特定的注释标记为错误、含糊不清或信息不足,放置在错误的位置(策略覆盖原因),或两者兼有。

当检测到此类问题时,对于每一个检测到的问题,企业架构团队将通过电子邮件回复,描述这些功能的正确用法,并抄送您项目的所有团队项目管理员,告知他们应将此信息和策略重新分发给您的团队成员,并且由他们负责确保此做法被理解并始终如一地应用。

收到此类消息时,我们建议由原始提交者尽快更新其注释,并在其更改仍然清晰在脑海中时进行。如果无法联系到开发人员或开发人员未及时响应,则责任将落在团队负责人或团队项目管理员身上进行这些更新,这通常会涉及对现有变更集与前一个变更集进行“差异”比较,以确定更改了什么以及为什么,从而提供适当的注释。

描述性注释是必需的,而信息丰富且目标导向的注释是首选。这是我们能够一眼了解项目为何发生更改而无需深入查看源代码文件的方法。如果您无法描述您为什么要这样做,那么您可能根本不应该这样做(请撤销您的更改而不是提交它们)。

6. 常用术语

URI (名词)

术语 URI (统一资源标识符) 在此处用于指代版本控制系统中项的路径。版本控制 URI 以美元符号 ($) 开始,后跟一个斜杠,然后是零个或多个由斜杠分隔的路径组件,例如 '$/Enterprise Architecture/trunk/ Application Blocks/v2.0/Src'。

分支 (名词,动词)

分支是任何独立的开发线。本文档中,“分支”(branch)一词用于指代版本控制中具有共同历史的一个或多个路径(参见“廉价副本”)。动词“分支”(branch)用于指代在版本控制中创建软件解决方案不同路径的廉价副本的过程。

BTT (名词)

BTT 结构是指在企业版本控制系统中常用的 /branches、/tags 和 /trunk 根目录。在 TFS 中,BTT 结构创建在每个团队的 Team Project 内。

廉价副本 (名词)

当版本控制服务器创建新的 URI 并将该 URI 的内容指向现有 URI 和版本时,会创建一个廉价副本。最终结果是,没有实际数据被复制,但从用户的角度来看,现在存在两个包含相同数据的完整源树。

当对树 A(当前版本为 534)进行修改时,它将开始与树 B 中的源发生分歧,因为树 B 中的源基于版本 534。任一树上的下一个版本都将是 536 及以上。当对树 B 进行修改时,它将以相同的方式与树 A 分歧。

提交 (名词,动词)

提交是指将一组更改发送到服务器的过程。在支持原子提交的系统中,要么所有更改都成功传输到服务器,要么都不传输。TFS 代表了这样一个系统。

干净的提交意味着一组更改在某种程度上是相关的。例如,重构工具“重命名…”可能会修改多个——甚至数百个——跨多个项目的独立文件,当这些更改与注释“将 Foo 重命名为 Bar”一起发送到服务器时,可以认为是一次干净的提交。

脏提交意味着一组更改之间没有有意义的关系。在上面的示例中,如果开发人员还向项目添加了一个新类,但未在注释中提及(或者,如常发生的那样,忘记了所做的更改),这就构成了脏提交。如果可能,应避免脏提交,因为它们使得跟踪哪些更改实现了哪些目标变得困难。

冲突 (名词)

当系统没有足够的信息成功执行自动合并时,就会发生冲突。例如,如果合并操作的源和目标都包含对同一行代码的更改,并且每个更改都不同,就会发生冲突,系统会提示用户决定如何处理这种情况。

合并 (名词,动词)

合并是指将一组已提交的更改合并到与发生更改的分支不同的 URI 的过程。合并的源是包含应合并的更改的 URI,合并的目标是您希望更新的分支所在的 URI。执行合并时,目标分支中的所有更改都将处于待定状态,直到更改被提交;这允许您在使更改永久化之前检查合并结果。

工作副本

工作副本是指用户硬盘上存在的项目的结构和文件,这些文件与版本控制中存储的相应项相关联。工作副本中的更改在提交之前不会反映在服务器上(或对其他用户可用)。

暂存

工作副本中的更改可以通过暂存来与其他人共享,而不是提交。暂存可用于代码审查目的。

7. 使用 BTT 的核心版本控制结构

开发的主线定义在 /trunk 中,尽管这并不是典型的开发发生的地方。trunk 应始终包含您软件解决方案当前最稳定的发行版。一些组织坚持认为 trunk 应始终处于可部署状态,并将 trunk 的写入访问权限限制为负责准备发行版的选定人员。

主开发线的分支,包括尚未发布的版本,定义在 /branches 中。典型的开发发生在此子树的一个或多个分支中。根据分支的预期用途,有两种根本不同的分支类型:功能分支和版本分支。

功能分支用于隔离潜在的破坏性更改,而不会不必要地影响在其他分支上工作的开发人员。功能分支的目的是引入和稳定新功能(或功能集),然后将更改合并到更稳定的分支中。因此,功能分支通常是短暂的,并在功能分支合并后删除。

版本分支用于引入新的开发线,同时维护现有版本。版本分支的目的是为下一个发行版添加/更改/增强/弃用功能。错误修复和现有版本的更改将被向前滚动到此分支,版本分支最终将成为当前发行版并提升到 /trunk。此分支的源——原始 URI——最终将被弃用然后删除。

/tags URI 的目的是包含在业务角度来看有意义的项目的“快照”。通常,这些“时间点”将与发行版一致,但这并不是它们唯一的用途。关于标签,需要记住的重要一点是,一旦创建,它们就应该被视为只读。对标签分支进行任何更改都会破坏标签的目的。实际上,如果存在自动构建系统,它应该是唯一具有足够权限创建标签分支的实体,而其他所有人都应具有只读权限。在此场景中,当发行版准备好执行时,构建系统将从 trunk 创建一个标签,构建该标签,然后执行部署(如果自动化),或者将构建结果交给发布工程师进行手动部署。

请注意,BTT 结构的价值在于遵守其预期用途以及结构中每个名称的语义。如果一个发行版是从 /branches 子树中的源执行的,而未先将其提升到 /trunk,或者 /trunk 本身在进行大型重构工作期间连续几天都处于中断状态,或者对 /tags 子树进行了任何更改;那么该结构的价值将大大降低。虽然其中一些可以通过电子方式强制执行(如上述自动构建示例中 /tags 的正确使用),但大部分取决于软件无法强制执行的社会契约。

该结构的持续可行性在很大程度上取决于在不更改项目的情况下成功重新定位项目(例如,修复引用路径、虚拟目录等)的能力。建议的项目结构通过使每个业务项目完全自成一体来满足这一需求,只需要必要的框架和编译器即可构建和测试生成的应用程序。

请注意,Visual Studio 2005 SP1 Web 应用程序和 Web 服务应用程序项目在此方面需要特殊处理,因为它们依赖于 IIS 虚拟目录,而 Web Site 和 Web 服务项目则不需要。

8. 核心项目结构

此处定义的核心项目结构是您在上述 BTT 结构中 /trunk 子树中应看到的结构。在 /branches 子树中使用的此结构的变体在分支任务和场景中有所说明。

核心项目结构定义了一个父子关系的至少两个文件夹。父文件夹包含业务项目的名称,而一个或多个子文件夹包含目标发行版的名称。本文档中的父文件夹称为“项目文件夹”,子文件夹称为“发行版文件夹”。项目文件夹将保留到业务项目退役为止(届时可以删除),而新的发行版文件夹将根据需要添加或删除,以与计划的发行版保持一致。在任何时候,每个“已发布”(在某个公共环境,即使是内部环境)的发行版都会存在一个发行版文件夹。

在这个结构中,发行版文件夹本身将是分支和合并操作的源和目标。左侧的图形显示了使用所有读者应该熟悉的产品的这个结构可能是什么样子。

以所示示例为例,假设 Office 2003 (v11) 已发布,Service Pack 1 (v11SP1) 已在内部进行“狗粮化”(相当于我们的 TEST 环境),并且 Service Pack 2 (v11SP2) 正在积极开发中。

届时,将存在所有 v11 目录以及 v11SP1 目录(因为它已在内部发布),但 v11SP2 将仅存在于 /branches 子树中。此外,RibbonToolbar 项目甚至尚未创建,因为业务部门仍在最终确定 Office 2007 (v12) 的需求并整合用户反馈——该版本尚未开始开发。

开发人员将在各自的 /branches 中工作,要么是在 v11SP2 上,要么是在响应 v11SP1 上的缺陷报告和 v11 上的快速修复工程 (QFE) 请求。在每次内部发布 v11SP1 之前,更改将被合并到 /trunk 中相应的文件夹,稳定化,在 /tags 中标记,然后发布。

随着 v11SP1 准备好进行 CTP 和早期采用者计划发布(相当于我们的 QA),v11SP2 在内部发布并最终进入 /trunk。此外,通过将 /trunk/[Project Name]/v11SP2(作为最新发行版)分支到 /branches/[Project Name]/v12,在 /branches 中创建了 v12 目录。由于 v12 将包含一种名为 Ribbon Toolbar 的新技术,因此为其在 /branches 中创建了一个新项目,并且 Office 2007 的开发开始认真进行。

从该模型中可以得出一些重要的启示:

  1. 主动开发仅发生在 /branches 中。/trunk 用于集成和稳定化目的(作为来自各种源的合并目标),而 /tags 为每个实际发行版创建。
  2. 此处显示的每个业务项目都可以独立测试。任何‘CommandBar’的发行版,即使它代表了多个产品使用的通用库(或一组库),也不需要完整的‘Excel’或‘Word’程序来进行测试。它只需要一个简单的测试框架来调用‘Excel’和‘Word’将要期望的 API。
  3. 该结构组织得当,可以提供自动化工具来显示业务项目列表,一旦选择,就可以显示其目标发行版,以便随时构建、分析、测试或部署。
  4. 这些发行版目录中定义的实际软件项目会经常被移动(分支、合并、分支提升到 trunk 然后是 tags 等)。这些项目可以被任何具有适当访问权限的人签出到他们机器上的任何位置,并在本地进行构建和处理。简而言之,发行版目录内的所有内容都是且需要是自成一体的。
  5. 在 /branches 和 /trunk 之间存在一个周期。功能和修复在 /branches 中完成,然后分支到或合并到 /trunk 以便发布。然后将 /trunk 中的分支再次分支到 /branches 以便进行下一个功能集或修复轮次。
  6. 清理很重要。由于每个发行版文件夹本质上代表了前一个发行版的完整副本并应用了一些更改,因此定期删除您当前未使用的任何发行版的本地副本,然后在需要时从服务器重新签出它们。您希望签出整个 /trunk——或者更糟,整个存储库 ($/),因为您将收到当前在任何环境中的每个项目的每个发行版的完整副本。

9. 扩展项目结构

在每个发行版文件夹内,软件解决方案的实际组件以一种能够实现此自成一体模型的方式进行组织。幸运的是,通过适当的分支,此结构只需要为每个业务项目定义一次(甚至可以通过自动化或将其存储为“模板”分支来减少人为错误的可能性)。

我们假设您使用 Visual Studio 2005 来创建软件。如果软件使用旧技术(尤其是 Visual Studio 6.0 开发工具套件),则可能适用其他指南,因为这些工具倾向于将文件放在它们想要的位置。

发行版文件夹至少包含一个“Src”目录,该目录旨在作为您的 Visual Studio 解决方案的根目录。Visual Studio *.sln 文件本身定义在此目录中。“Src”文件夹的目的是包含您软件解决方案的所有源代码资产,包括构成解决方案的所有 Visual Studio 项目和文件。

如果您的解决方案使用了不属于应用程序本身的任何外部文件引用,例如应用程序块、第三方库或来自其他项目文件夹的通用库,则应将它们保存在名为“Lib”的文件夹中,该文件夹位于发行版文件夹中与“Src”相同的级别。“Lib”文件夹的目的是包含您解决方案中各种 Visual Studio 项目使用的所有外部文件引用及其依赖项。

如果需要存储特定于业务项目或发行版的开发人员之间文档,则应将其保存在发行版文件夹中名为“Doc”的文件夹中。此文件夹的位置仅作为开发人员创建和阅读有关正在开发中的软件的文档的便利位置,该文档将与软件本身一起进行版本控制。

遵循这些建议,发行版文件夹及其内容应如下所示

在“Doc”和“Lib”文件夹内,您可以定义适合您应用程序的任何其他内部结构。有关“Src”文件夹结构的更多指导将在“解决方案结构”部分提供。请勿重命名这些文件夹或重新解释其含义或用途。这样做将阻止自动化工具根据此命名约定访问或适当地使用它们。

引用“Lib”文件夹(或其子文件夹)中二进制依赖项的每个 Visual Studio 项目都使用相对路径进行引用。这种结构保证了任何人都可以将发行版文件夹签出到其硬盘上的任何位置并立即进行构建。无论发行版文件夹位于何处,Visual Studio 项目中的相对路径都将保持有效。

此时,许多开发人员开始想知道是否可以创建一个“共享”库文件夹,供多个项目引用,特别是当一个解决方案的依赖项是另一个解决方案的输出,而该解决方案又由多个不同的业务项目共享时。表面上看,这是有道理的——毕竟,通用库=通用位置。如果其他开发人员的机器上具有相同的结构,应该可以正常工作,对吗?……对吗?以下图形显示了当今使用的该主题的变体。

请勿从映射驱动器或发行版文件夹之外的共享文件夹引用共享库。这正是应该避免的情况。有几个原因可以解释这个警告:

  • 版本管理——假设,使用上图,某人运行了 Solution A 并解决了所有(已知的)错误。他们获取 Solution A 的最新源代码并重新构建,为添加新功能做准备,突然一切都坏了。从 Solution A 正常工作时起,其历史记录中没有任何更改,那么是什么出了问题?如果使用了此模型,那么很有可能有人错误地更新了 Common Lib……您无从知晓。请注意,如果出现问题,您就非常幸运了。典型的此类问题不会立即显现出来,直到它们出现在生产环境中,并且一个以前经过彻底测试的功能被最终用户调用,而“未知更改”取而代之。如果出现问题,您应该能够将其追溯到解决方案本身的更改,或环境的更改(添加/删除程序,虚拟目录被删除,硬件/网络问题等)。
  • SCM 实践和审计合规性——当 Solution A(再次,来自上图)被标记并部署时,关于与之一起部署的外部引用的唯一信息是部署时其存在的相对路径。部署了哪个版本的 CommonUtility.dll?很难确定,因为 Common Lib 没有包含在标签中。
  • 可移植性——每个软件解决方案都应作为独立实体存在。任何时候引用解决方案根目录之外的项,您都会引入一个依赖项,该依赖项必须复制到使用该解决方案的每台计算机上,并且您将失去有效分支和标记的能力。如果解决方案中的项目引用来自团队共享的目录的库,那么当您创建分支或标记时,这些引用可能会突然中断,并且当另一位不知晓此依赖项的用户检索解决方案时,或者即使知晓,在不同的本地路径检索解决方案时,引用肯定会中断。
  • 意外的依赖项爆炸——您的应用程序中对已编译库的每个引用都可能依赖于其他引用,而这些引用又依赖于其他引用,依此类推。除非这些依赖项“摆在您面前”并得到显式管理,否则当您准备实际部署时,您可能需要将数百个程序集复制到部署文件夹中,才能满足几个项目引用。每个这些依赖项都是一个紧密耦合,其中任何一个的更改都可能意外地破坏您的应用程序。更糟糕的是,这些依赖项可能在不同版本下共享一个公共依赖项。例如,LibraryA 引用 LibraryB 和 LibraryC;LibraryB 引用 LibraryD 的版本 1,而 LibraryC 引用 LibraryD 的版本 2。只有当开发人员显式管理这些引用时,才会向开发人员显现这一点。如果不显现并且代码被部署,事情将不会按预期工作,因为同一个目录中不能存在两个不同版本的 LibraryD。

不幸的是,共享库模型虽然看起来很有吸引力,但随着时间的推移,管理起来越来越困难。自成一体的模型虽然需要一些额外的预备工作,但管理起来越来越容易。企业架构团队正在使用这些指南更新参考应用程序,并且为了使此显式依赖项管理更加容易,现在“Lib”目录中有一个“UpdateExternalDependencies.cmd”批处理脚本,用于引入应用程序块引用的最新版本(及其引用等)。依赖项管理仍然是显式的,但通过单击鼠标,我们现在可以测试参考应用程序与应用程序块中的最新更改。

将所需共享库仅从映射驱动器或您的团队成员共享的通用文件夹复制到“Lib”目录是完全合适的,特别是当这些组件由您的组维护时。关键区别在于,该驱动器或通用文件夹不受版本控制(也不应受版本控制)。例如,如果您的团队购买了 Infragistics 控件,那么将设置一个通用目录并与您的团队共享,从中可以获取购买的版本。为了在您的项目中使用,您需要的特定 DLL 将被复制到您发行版的“Lib”目录并从该位置引用,而不是从通用位置引用。如果您获得了新版本并将其放入通用文件夹,它不会影响您的应用程序,直到您准备好为止,让您有机会使用新版本进行构建和测试,并解决升级可能引起的任何问题。

在 .NET 中,共享库有一个(且只有一个)恰当的用途:全局程序集缓存 (GAC)。GAC Shell 扩展(Fusion.dll)竭尽全力隐藏有效管理共享库多个版本所涉及的复杂性,但为了说明所涉及的复杂性,您可以使用以下注册表脚本来启用或禁用此 Shell 扩展。GAC 可在 %windir%\assembly (C:\WINDOWS\assembly) 中查看。

10. 解决方案结构

“Src”目录的内容大致按 Visual Studio 项目类型进一步组织。“Src”目录本身包含 Visual Studio 解决方案文件和其他解决方案项,如 Visual Studio 的解决方案资源管理器所示。

本节中的建议主要基于构建工程师(负责构建和最终部署软件的人员)表达的需求,并使自动化工具和人工都能轻松识别可部署和不可部署的源代码项目。

因此,应按原样使用此处提供的名称和语义,但请咨询企业架构部门以确定是否应在此结构中添加新的“项目类型”。

应指出的是,此结构的单个元素只有在有需求时才应创建。也就是说,除非该文件夹包含一个或多个符合该项目类型的 Visual Studio 项目,否则不要创建任何这些文件夹;不要创建整个结构然后留下部分为空。

以下是对此结构中每个文件夹及其目的的描述,但迄今为止已识别的主要项目类型可以用以下术语表示:“Data”、“Hosts”、“Installers”、“Libraries”和“Tests”。右侧显示了一个包含所有这些项目类型的解决方案的图形表示。在您自己的项目中,您可能只有“Hosts”和“Libraries”,或者“Hosts”、“Libraries”和“Tests”,或者只有“Data”。其他组合也存在,您应该使用适合您应用程序的组合。

I. 数据文件夹

“Data”文件夹旨在包含数据库项目和用于创建和初始化数据库的批处理脚本(即数据库“源代码”)。这包括 Visual Studio 的“数据库项目”类型以及 Visual Studio 2005 随附的用于数据库专业人员的新的“SQL Server 2000”和“SQL Server 2005”项目类型。这些项目通常包含 SQL 脚本、数据库架构以及创建、初始化、加载和更改关系数据库所需的其他文件。使用这些项目类型来在版本控制中存储数据库配置脚本具有优势。Visual Studio 2005 数据库专业版甚至将“重命名…”重构支持扩展到数据库对象名称,并允许数据库架构本身与您解决方案的其余部分一起进行版本控制。

这些项目中的脚本通常在部署期间用于确保数据库更改反映在版本控制中(这可以通过让 SQL Enterprise Manager 或 SQL Server Management Studio 在每次发布前脚本化数据库并将生成的脚本移入其中一个项目来轻松实现)。这些脚本也可能移交给 DBA 以在其他环境中影响数据库更改。

II. Hosts 文件夹

“Hosts”文件夹包含您解决方案的 *.EXE 和网站/Web 应用程序项目。在“Hosts”中的每个 Visual Studio 项目都代表一个可部署的项目,并且实际上在发布期间会被部署。每个主机都是独立“可运行的”(或可浏览的,对于网站和 Web 应用程序项目),并且可以设置为 Visual Studio 中的“启动项目”。

将要部署的每个业务项目和发行版文件夹都应该至少有一个定义的“Hosts”项目,而多层应用程序通常至少有两个主机(Web 层和应用程序/Web 服务层),甚至可能更多。请记住,不是解决方案也不是业务项目本身被部署,只有“Hosts”被部署。这意味着一个业务项目可能包含多个具有共享核心功能的“应用程序”,并且一个解决方案的“Hosts”项目不限于单个层。

术语“Hosts”被用来替代“Webs”或“Exes”等替代词,以表明这些项目应该作为其他项目类型(特别是“Libraries”)中定义的应用程序功能的“主机”。“Hosts”中的任何项目都不应被其他项目(除了可能的“Tests”)引用,并且主机中定义的任何需要由其他 Visual Studio 项目使用的功能都应移入可重用的库中。

III. Installers 文件夹

“Installers”文件夹用于包含和定义您的软件解决方案的安装程序(如果业务项目需要)。这些项目包括 Visual Studio 的“Setup Project”、“Merge Module Project”、“CAB Project”、“Web Setup Project”、“Setup Wizard”和“Smart Device CAB Project”项目类型,以及用于此目的的其他项目类型(如 WiX,或 Windows Installer XML,项目类型)。

IV. Libraries 文件夹

“Libraries”文件夹包含您解决方案的 Visual Studio 类库项目(及相关项目类型,如 Windows Control Library、Web Control Library、WCF Service Library 和 Windows Workflow Library 项目)。此文件夹及其内容构成了您应用程序的大部分功能,并且此文件夹通常包含您解决方案中数量最多的独立项目。

“Libraries”文件夹中定义的任何 Visual Studio 项目本身都不可部署,而是公开由其他库或“Hosts”文件夹中的项目使用的功能和服务。例如,如果不带主机(如您的应用程序)而将应用程序块本身部署到任何环境的服务器上,以利用其功能,则价值不大。

V. Tests 文件夹

“Tests”文件夹包含您解决方案的 Visual Studio 测试项目以及单元测试和集成测试。这些项目是库,但被区别对待,因为它们通常不被部署,也不像您解决方案中的其他项目类型那样受到相同的分析(代码分析、代码覆盖率和性能剖析)。

测试指南超出了本文档的范围,但我们强烈建议您通过在项目名称后附加“.Tests”和“.Integration”后缀来区分此文件夹中的测试项目是包含单元测试还是集成测试。这些术语定义如下:

单元测试——程序员定义的测试,用于隔离地测试类或方法的**功能。单元测试仅需要被测试的代码、其依赖的运行时和框架以及一个测试框架。具体来说,单元测试需要安装或提供任何其他产品(如数据库、性能计数器、事件日志或外部服务),也不需要任何网络访问。

集成测试——程序员定义的测试,用于测试一组协作的类或组件的**功能。集成测试可能需要额外的设置和使用外部服务和软件才能运行。

以测试一个向数据库写入一行的方法为例,来说明这些测试类型的区别。该方法的签名如下:

internal int AddCustomer(DbCommand command, string firstName, string lastName);

单元测试项目将定义一个 `MockDbCommand` 来代替 `DbCommand` 传递,并用于测试各种场景。

第一组单元测试将确保该方法在任何参数被指定为 null 时抛出适当的 `ArgumentNullException`。

第二组单元测试将确保该方法内部调用了适当的 `DbCommand.ExecuteNonQuery();` 命令,并且它已与适当的 `DbParameter` 对象一起填充,这些对象已使用 firstName 和 lastName 的值填充。

下一组单元测试将确定在 `DbCommand.ExecuteNonQuery();` 调用期间可能发生的各种错误情况下采取了适当的操作,通过模拟各种错误条件,如 `DbException`(包括 `SqlException`、`OleDbException` 和 `OracleException`)、`IOException` 等。

最后一组单元测试将确定此方法的返回值代表正确的值(在这种情况下,假设它是新的 Customer ID,而不是受影响的行数)。我们的 `MockDbCommand.ExecuteNonQuery();` 方法将返回 1(作为受影响的行数),但我们在 `MockDbCommand` 中添加了一个名为“`@CustomerID`”的输出 `DbParameter`,其值为 5。通过测试,我们验证了被测试方法 (AddCustomer) 的返回值确实是 5。

相比之下,此方法的集成测试会将真实的 `SqlCommand` 传递给该方法,该方法将使用与真实数据库的真实连接进行初始化。

第一个集成测试将向该方法传递有效的 firstName 和 lastName,然后读取数据库中由返回值指示的行,以确保行已成功添加(例如,“`SELECT FirstName, LastName FROM Customers WHERE CustomerID = @CustomerID”)。这验证了成功场景。请注意,这会验证 `AddCustomer` 方法以及 `SqlCommand`、`SqlConnection`、`SqlParameter` 和链中的任何其他对象与数据库服务器本身以及与之通信所需的网络之间的预期交互。

下一组集成测试将验证各种失败场景的预期行为(例如,通过修改 `SqlCommand` 的 Connection 属性指向无效或不存在的数据库,或尝试添加相同的客户两次并导致唯一键冲突)。

这两种类型的测试对于完全指定被测系统的预期行为都是必需的。

VI. Visual Studio 项目布局

上述每个文件夹中将存在一个或多个 Visual Studio 项目文件夹;每个项目一个文件夹。请勿嵌套这些内部项目文件夹。如果需要其他组织,请使用 Visual Studio 解决方案资源管理器中的“解决方案文件夹”功能来表示此附加组织结构。解决方案文件夹在物理上不映射到硬盘或版本控制中的文件夹,仅作为 Visual Studio 解决方案中的便捷组织机制。我们建议您使用解决方案文件夹来模仿上述物理布局,但您可以根据需要进行添加。

为了说明,让我们以一个理赔处理流程软件解决方案为例。这个假设的解决方案包含一个用于纸质理赔录入的 Windows 桌面客户端,用于提供商提交理赔的 Web 服务,以及用于手动录入的 Web 应用程序,还有一个内部用于提供统计管理信息的 Web 应用程序,一个用于向 Blackberry 设备提供警报和通知以进行应用程序健康监控的移动 Web 应用程序,以及用于提供后端处理和批量处理功能的 Windows 和 Web 服务。

这样的系统可能具有以下项目和结构。请注意,此解决方案代表了一套相当复杂的应用程序,并且“Src”文件夹中很可能存在额外的 *.sln 文件,这些文件仅引用当前开发工作所需的项目,以提供更集中的视图。

11. Windows MAX_PATH 限制和 Team Build

Windows® 操作系统历史上(至少到 Vista™)对任何文件系统路径中可表示的字符数都有限制。此限制通常为 260 个字符,但由于文件夹和文件本身可能具有很长的描述性名称,您可能会遇到此限制,尤其是在构建场景中。

版本控制提供程序本身没有这些相同的限制,因此完全有可能在版本控制下拥有深度嵌套的文件夹,每个文件夹都有很长的描述性名称。这会导致源代码管理中的项无法在本地机器上以相同的结构签出,应避免这种情况。

Team Build 还使用“构建类型”(Build Type)的概念来定义各种构建选项和要一起构建的解决方案。默认情况下,当构建服务器签出要构建的项时,它将遵循源代码管理中使用的相同结构(从 Team Project 根目录开始),但签出的根目录将是‘F:\MyBuilds\[Team Project Name]\[Build Type Name]\Sources\’(其中 [Team Project Name] 和 [Build Type Name] 分别对应您的 Team Project 和 Build Type 名称)。这会增加 22 + Team Project Name 长度 + Build Type Name 长度的字符到版本控制 URI 长度,并将有用路径限制在远低于 238 个字符。例如,企业架构组定义了一个名为 AB (Application Blocks) 的构建类型,将任何项在版本控制中的最大有用路径限制在 212 个字符以内。更长的 Team Project 和 Build Type 名称只会加剧这个问题。

提到这些问题和限制只是为了提高认识。过去,Visual Studio 解决方案中的单个项目已创建为具有描述性名称,例如 ApplicationBlocks.UIPPersistence 和/或 Microsoft.Practices.EnterpriseLibrary.Configuration;分别为该名称创建文件夹和文件 (*.csproj),从而使这些路径对应于ApplicationBlocks.UIPPersistence\ ApplicationBlocks.UIPPersistence.csprojMicrosoft.Practices.EnterpriseLibrary.Configuration\Microsoft.Practices.EnterpriseLibrary.Configuration.csproj。创建项目时使用缩短的名称(如上面示例中的 UIPPersistence 和 Common.Configuration)可能是明智的,然后显式重命名项目并设置根命名空间和程序集名称属性。

12. 场景

I. 场景 #1 – 新项目或技术迁移

您的团队刚接手了一个新项目(或者正在将 VB6/ASP/COM 应用程序转换为新平台),需要在版本控制中创建一个开发位置。该项目的名称是 Medical Records Request Tracking,为简便起见,团队内部简称 MRRTracking。该应用程序的第一个计划发布是 8.2 版本(2008 年 2 月)。您的 TFS 中的 Team Project 名称是“Provider Services”。

使用您的版本控制工具,您将在 $/Provider Services/branches URI 中添加一个名为“MRRTracking”的新项目文件夹,以创建 $/ProviderServices/branches/MRRTracking URI。在该文件夹中,您将添加名为“v8.2”的发行版文件夹,以创建 $/ProviderServices/branches/MRRTracking/v8.2 URI。

此时,提交您的更改,注释为“Created Medical Records Request Tracking project and release folder”。这会通知任何订阅者这些文件夹的用途以及它们将包含的项目,并且对于任何查看此项目历史记录的人来说,都将起到提醒作用。请注意,注释中未使用缩写形式“MRRTracking”。

现在已经创建了发行版文件夹,请继续添加 Doc 和 Lib 文件夹,并在 Doc 文件夹中放置一个名为“Glossary.txt”的简单文本文件,其中包含以下行:

MRRTracking = Medical Record Request Tracking

这可以作为项目词汇表,并且应该是新合同工、开发人员和审阅者在不熟悉术语时首先查找的地方。要理解,这段代码会被不熟悉您项目的人(至少是审阅者)看到,并且对于离岸合同工来说,甚至可能由不熟悉文化和术语的人开发,或者项目本身可能被移交给另一个团队。缩写 CIM 对熟悉 Windows Management Instrumentation (WMI) 的开发人员来说是 Common Information Model,对其他人来说是 Corporate Information Management。这些区别必须在一个对所有团队成员都易于访问的位置做出。与“懂行”的团队成员进行顺畅的沟通是很有意义的,但公司和团队的术语对其他人来说只不过是晦涩难懂的。提供自助的方法来理解它是一种极其宝贵的生产力提升,可以防止误解和对其他团队成员的不断打扰。

以下 URI 应该处于“已添加”状态(尚未提交)

$/Provider Services/branches/MRRTracking/v8.2/Doc
$/Provider Services/branches/MRRTracking/v8.2/Doc/Glossary.txt
$/Provider Services/branches/MRRTracking/v8.2/Lib

使用注释“Added project glossary and Lib”提交您的更改,或者,为了一个“更干净”的提交,只提交文件夹更改,注释为“Added Doc and Lib folders”,然后提交 Glossary.txt 的添加(仍然是待处理的“添加”)并注释为“Added project glossary”。

接下来,在发行版文件夹中,在 Visual Studio 中创建一个新的空白解决方案,名为“Src”(这将创建“Src”目录),然后立即重命名该解决方案。您可以使用 MedicalRecordRequestTracking 或 MRRTracking 作为解决方案名称;无论哪个更清晰易懂,首先考虑将来可能加入项目的新开发人员和合同工,然后考虑那些每天都会看到它的人。这些考虑因素是优先的,因为您很可能希望让任何新员工快速上手,而不是花费数小时或数天“给他们导览”,甚至更糟的是,根本不给他们任何信息。

重命名解决方案后,您可以为该项目添加所需的任何解决方案文件夹。请记住,解决方案文件夹不映射到物理文件夹;当您将项目添加到这些文件夹中时,您仍然需要添加前面描述的适当解决方案结构文件夹。

将初始化的解决方案添加到版本控制并提交,注释为“Created initial project structure”。这样的解决方案可能具有以下 URI(许多未显示,如单独的 *.csproj 文件和 AssemblyInfo.cs 文件以及 Properties 文件夹)

$/ProviderServices/branches/MRRTracking/v8.2/Src
$/ProviderServices/branches/MRRTracking/v8.2/Src/MedicalRecordsRequestTracking.sln
$/ProviderServices/branches/MRRTracking/v8.2/Src/Hosts/Web
$/ProviderServices/branches/MRRTracking/v8.2/Src/Libraries/Controllers
$/ProviderServices/branches/MRRTracking/v8.2/Src/Libraries/ClientServices
$/ProviderServices/branches/MRRTracking/v8.2/Src/Libraries/Common
$/ProviderServices/branches/MRRTracking/v8.2/Src/Libraries/Core

II. 场景 #2 – 现有项目,迁移到 TFS

您的团队在一个服务器环境中有现有项目,或者用户或测试人员手中有一个桌面应用程序。该应用程序的源代码目前在 VSS 中,您想将其移至 TFS。‘公共视图’(测试人员、最终用户)中的现有应用程序是 1.0 版本,但 2.0 版本的工作已经开始。您一直遵循先前的 EA 指导,并在 VSS 中为这些版本创建了“分支”,但 2.0 版本已经应用了一些关键的错误修复和非周期性更新。项目名称是 Turn Center Wizard,但团队成员称之为 TCW。您的 TFS 中的 Team Project 名称是“Enterprise Architecture”。

首先,告知您的团队您将把 2.0 版本迁移到 TFS,并让每个人都能够签入他们的更改,并且项目能够编译。此外,确保所有将要从事该项目的人都安装了相应的 Team Foundation Server 工具。

从您的硬盘驱动器中删除任何现有的 2.0 发行版目录,并从 VSS 执行该分支的“Get Latest…”操作。

打开 2.0 版本(仍绑定到 VSS),并首先确保其能够编译。如果不能,请在它仍然绑定到 VSS 的情况下进行任何必要的更改,直到您可以获得良好的构建。签入所有已做的更改后,联系您的 VSS 管理员(通常是 EA 团队),将该项目的所有访问权限设置为“只读”,并撤销任何剩余的已签出文件。此步骤将防止在迁移完成之前或之后对 VSS 中的项目进行任何更改。在 VSS 管理员完成此任务后,执行另一次“Get Latest…”操作(以防在项目设置为只读之前其他人进行了编辑),并重新验证您有一个良好的构建。

在关闭此解决方案并告别 VSS 之前的最后一步是取消解决方案与 VSS 的绑定。使用“File|Source Control >|Change Source Control…”菜单选项打开“Change Source Control”对话框,并“Unbind”每个项目和解决方案。关闭打开的解决方案,保存任何更改,并将您的 Source Control 插件设置为 Team Foundation Server。

使用您的版本控制工具,将名为“Turn Center Wizard”的新项目文件夹添加到 $/Enterprise Architecture/branches URI,以形成 $/EnterpriseArchitecture/branches/Turn Center Wizard URI。在该文件夹下,添加名为“v2.0”的发布文件夹,以形成 $/EnterpriseArchitecture/branches/Turn Center Wizard/v2.0 URI。

接下来,创建“new”项目结构文件夹(Doc、Lib、Src),以获得以下 URI:

$/Enterprise Architecture/branches/Turn Center Wizard/v2.0/Doc
$/Enterprise Architecture/branches/Turn Center Wizard/v2.0/Lib
$/Enterprise Architecture/branches/Turn Center Wizard/v2.0/Src

到目前为止创建的所有文件夹仍处于待处理的“已添加”状态,但已存在于您的工作副本中。将解决方案和项目文件夹从您的 2.0 VSS 工作文件夹复制到 TFS 工作副本中的 Src 文件夹。

接下来,在 Src 目录中创建解决方案结构文件夹,并将每个项目文件夹拖或移动到其对应的解决方案结构文件夹中(例如,*.exe 和 Web 应用程序放入“Hosts”,库放入“Libraries”,任何“TestCases”项目放入“Tests”等)。

使用记事本或其他纯文本编辑器打开解决方案文件本身,并更正其查找每个项目文件的位置。

保存您的更改,然后打开解决方案以验证它是否能找到每个项目。 *此时请勿*尝试生成。当所有项目都在解决方案资源管理器中表示(并加载)后,右键单击解决方案并选择菜单选项“将解决方案添加到源代码管理…”这会将解决方案文件和项目结构作为待处理的“添加”注册到版本控制提供程序。

如果您打开任何给定项目的“引用”节点,您可能会看到一些损坏的引用。这些是您的项目使用的外部二进制引用,应放置在“Lib”目录中。

将每个外部引用及其依赖项复制到“Lib”文件夹,并更新每个项目以从此文件夹中检索适当的引用。

您可能还希望将下面的 VBScript 文件放入“Lib”文件夹,并在构建项目之前修改任何 Web 应用程序、AJAX Web 应用程序和 Web 服务项目以执行它。这将确保为项目创建适当的 IIS 目录和虚拟目录,并将其指向项目的根文件夹。此脚本属于“Lib”文件夹,因为它是一个外部文件引用(尽管是构建引用,而不是项目引用),并且它不是您解决方案源代码的一部分。

可以通过右键单击项目并选择“卸载项目”上下文菜单项来更新必要的项目。在项目变灰(卸载)后,再次右键单击卸载的项目并选择“编辑…”上下文菜单项。

这将显示用于项目的实际 MSBuild 项目文件。在该文件的底部附近,您会看到一个已注释掉的区域(如下所示)。

<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

存在其他类似的扩展点,请参阅 Microsoft.Common.targets。

<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->

取消注释此部分,并将 BeforeBuild 目标替换为以下内容(请注意 Exec 命令中的换行符…在此文件片段中没有换行符)

<Target Name="BeforeBuild" DependsOnTargets="GetFrameworkPaths">
<Exec Command="cscript.exe &quot;..\..\..\Lib\EnsureIISVDir.vbs&quot; "$(MSBuildProjectFullPath)" "$(FrameworkDir)" />
</Target>

无论项目在硬盘上的位置如何,或者您使用什么 IIS 虚拟目录,您现在都可以在 IIS 中构建和运行它。上述脚本还将 IIS 中的 Framework 版本设置为 Visual Studio 当前使用的版本。

现在,您只需构建项目即可验证所有必需的程序集和脚本是否已放入 Lib 文件夹。纠正可能发生的任何错误,然后使用注释“Migration of Turn Center Wizard from VSS”提交整个项目。

三. 场景 #3 – 干净的首个发布

您的团队负责“Medical Records Request Tracking”项目,团队成员之间称之为 MRRTracking。您是该项目的构建工程师,您的 TFS 中的团队项目名称是“Provider Services”。现在是 2008 年 2 月中旬,项目已满足 8.2 发布的要求,代码审查在 Changeset 738 上通过。项目上的所有开发人员都已签入代码,并在 TGIFriday's 喝啤酒,祝贺彼此完成了一项出色的工作。在加入他们之前,您只需要完成这最后一项任务。

项目本身是新的,以前从未发布过,因此在 /trunk 中不存在其目录。这是 8.2 发布,下一个发布计划是 8.4。

使用您的版本控制工具,导航到当前开发文件夹 $/ProviderServices/branches/MRRTracking/v8.2 并执行分支操作。您的版本控制提供程序的 UI 会询问您要分支哪个 Changeset(默认为最近的)以及要分支到何处。

明确将 Changeset 设置为 738(经过审查的版本),并将目标分支设置为 $/ProviderServices/trunk/MRRTracking/v8.2。如果您有机会切换到目标分支或从中创建本地工作副本,请设置该选项并执行分支操作。

作为 Changeset 738 中存在的软件解决方案的副本将在您的硬盘驱动器上创建,并在版本控制中的目标分支 URI 下创建。打开 $/ProviderServices/trunk/MRRTracking/v8.2/Src 文件夹中的解决方案并验证其是否编译。(如果无法编译,请删除分支并让那些开发人员返回办公室修复他们的代码。)

使用注释“Initial 8.2 release of Medical Records Request Tracking”将分支提交到版本控制。

接下来,选择 $/ProviderServices/trunk/MRRTracking/v8.2 处的发布文件夹并执行另一个分支操作。再次,UI 会询问您要分支哪个 Changeset(默认为最近的)以及要分支到何处。

这次,保留默认的 Changeset(最新版本),并将目标分支设置为 $/ProviderServices/branches/MRRTracking/v8.4。如果您不需要在此分支上工作,则不要设置切换到或创建其本地工作副本的选项。现在执行分支操作。

在版本控制中的 $/ProviderServices/branches/MRRTracking/v8.4 下将创建 8.2 发布软件解决方案的副本。

使用注释“Initialized 8.4 development from r738”提交新创建的分支,并向整个团队发送电子邮件,告知他们所有 8.4 功能都应在此分支中开发,并且只应在 8.2 分支上进行 bug 修复。

如果您有自动化构建系统,请指示构建系统在 $/ProviderServices/trunk/MRRTracking/v8.2/Src 文件夹中构建和标记解决方案(并且,如果它还执行部署,则可以选择在哪里部署它找到的每个 Hosts 项目)。如果是这种情况,那么您就完成了,可以去 TGIFriday's 加入团队了。如果不是,请继续阅读手动步骤。

四. 场景 #4 – 肮脏的首个发布

您的团队负责“Medical Records Request Tracking”项目,团队成员之间称之为 MRRTracking。您是该项目的构建工程师,您的 TFS 中的团队项目名称是“Provider Services”。现在是二月中旬,项目已满足 8.2 发布的要求,代码审查在 Changeset 738 上通过。开发人员已经注意到并一直在忙于修复一些他们在代码审查后发现的 bug,以及重构他们仍然想纳入 8.2 发布的一些代码… 8.4 发布尚未开始,也没有为其创建分支。最新的 Changeset 是 813,这是您被指示发布的版本。所有更改都已由所有团队成员提交。

项目本身是新的,以前从未发布过,因此在 /trunk 中不存在其目录。

使用您的版本控制工具,导航到当前开发文件夹 $/ProviderServices/branches/MRRTracking/v8.2 并执行分支操作。您的版本控制提供程序的 UI 会询问您要分支哪个 Changeset(默认为最近的)以及要分支到何处。

明确将 Changeset 设置为 813,并将目标分支设置为 $/ProviderServices/trunk/MRRTracking/v8.2。如果您有机会切换到目标分支或从中创建本地工作副本,请设置该选项并执行分支操作。

作为 Changeset 813 中存在的软件解决方案的副本将在您的硬盘驱动器上创建,并在版本控制中的目标分支 URI 下创建。打开 $/ProviderServices/trunk/MRRTracking/v8.2/Src 文件夹中的解决方案并验证其是否编译。(如果无法编译,则删除分支并让开发人员修复其代码。)

使用注释“Initial 8.2 release of Medical Records Request Tracking – Reviewed at r738”将分支提交到版本控制。这表明任何观察者,被发布的代码不一定是经过审查的代码(当审计员询问 EA 团队为什么审查了 738 而您发布了 813 时,这也会保护您)。

接下来,选择 $/ProviderServices/trunk/MRRTracking/v8.2 处的发布文件夹并执行另一个分支操作。再次,UI 会询问您要分支哪个 Changeset(默认为最近的)以及要分支到何处。

这次,保留默认的 Changeset(最新版本),并将目标分支设置为 $/ProviderServices/branches/MRRTracking/v8.4。如果您不需要在此分支上工作,则不要设置切换到或创建其本地工作副本的选项。现在执行分支操作。

在版本控制中的 $/ProviderServices/branches/MRRTracking/v8.4 下将创建 8.2 发布软件解决方案的副本。

使用注释“Initializing 8.4 development from r813”提交新创建的分支,并向整个团队发送电子邮件,告知他们所有 8.4 功能都应在此分支中开发,并且只应在 8.2 分支上进行 bug 修复。

如果您有自动化构建系统,请指示构建系统在 $/ProviderServices/trunk/MRRTracking/v8.2/Src 文件夹中构建和标记解决方案(并且,如果它还执行部署,则可以选择在哪里部署它找到的每个 Hosts 项目)。

五. 场景 #5 – 真正肮脏的首个发布

您的团队负责“Medical Records Request Tracking”项目,团队成员之间称之为 MRRTracking。您是该项目的构建工程师,您的 TFS 中的团队项目名称是“Provider Services”。现在是 2008 年 2 月中旬,项目已满足 8.2 发布的要求,代码审查在 Changeset 738 上通过。开发人员已经注意到并一直在忙于修复一些他们在代码审查后发现的 bug,以及重构他们仍然想纳入 8.2 发布的一些代码。一些开发人员甚至开始实现 8.4 功能,并在 8.2 文件夹中提交了该发布的更改。最新的 Changeset 是 906。所有更改都已由所有团队成员提交。

项目本身是新的,以前从未发布过,因此在 /trunk 中不存在其目录。

使用您的版本控制工具,导航到当前开发文件夹 $/ProviderServices/branches/MRRTracking/v8.2 并执行分支操作。您的版本控制提供程序的 UI 会询问您要分支哪个 Changeset(默认为最近的)以及要分支到何处。

明确将 Changeset 设置为 738(最近审查的),并将目标分支设置为 $/ProviderServices/trunk/MRRTracking/v8.2。如果您有机会切换到目标分支或从中创建本地工作副本,请设置该选项并执行分支操作。

作为 Changeset 738 中存在的软件解决方案的副本将在您的硬盘驱动器上创建,并在版本控制中的目标分支 URI 下创建。打开 $/ProviderServices/trunk/MRRTracking/v8.2/Src 文件夹中的解决方案并验证其是否编译。(如果无法编译,请删除分支并让开发人员修复他们的代码,以及重新提交审查,以期实现干净的首个发布。)

使用注释“Preparing 8.2 release of Medical Records Request Tracking – Reviewed at r738”将分支提交到版本控制。这表明任何观察者,被发布的代码不一定是经过审查的代码。

接下来,选择 $/ProviderServices/branches/MRRTracking/v8.2 处的开发文件夹并执行“合并…”操作。您的版本控制提供程序的 UI 会询问合并的源分支和目标分支,以及您是要合并所有更改还是仅合并选定的更改。此时,选择仅合并选定更改的选项,并将合并目标设置为您的 $/ProviderServices/trunk/MRRTracking/v8.2 发布文件夹。

合并 UI 将显示自分支版本 (738) 以来发生的所有更改。此时,您可以选择需要包含在 8.2 发布中的更改。请参阅下面的屏幕截图以了解此 UI 的示例。注意良好注释的价值以及干净提交的价值在您做出决定时的作用(什么应该在 8.2 发布中,什么应该在 8.4 发布中)。

选择要合并的 Changeset 后,执行合并。这将使用指定的更改更新您本地工作副本中的文件。打开目标分支处的解决方案,验证其是否能正确生成,然后使用注释“Merged changes r###-r###)”(其中 ### 是正在合并的起始和结束 Changeset)提交更改。

对于需要包含在 8.2 发布中的任何非连续更改集,请重复从 8.2 开发文件夹到 /trunk 中的 8.2 发布文件夹的合并过程。

一旦您对 8.2 发布已准备就绪并且所有特定于它的更改都已合并(同时排除了任何特定于 8.4 的更改),请记下 changeset 编号(假设此时为 916),然后选择 $/ProviderServices/trunk/MRRTracking/v8.2 处的发布文件夹并执行另一个分支操作。再次,UI 会询问您要分支哪个 Changeset(默认为最近的)以及要分支到何处。

这次,保留默认的 Changeset(最新版本,916),并将目标分支设置为 $/ProviderServices/branches/MRRTracking/v8.4。这次,请设置切换到或创建新分支的本地工作副本的选项并执行分支操作。

在版本控制中的 $/ProviderServices/branches/MRRTracking/v8.4 下将创建“真实”8.2 发布的副本。

使用注释“Initializing 8.4 development from r916”提交新创建的分支,并向整个团队发送电子邮件,告知他们所有 8.4 功能都应在此分支中开发,并且只应在 8.2 分支上进行 bug 修复。

此时,8.2 开发分支仍然包含特定于 8.4 的更改,因此我们需要将它们合并到 8.4 开发分支中,并将现有的 8.2 开发分支恢复到与 /trunk 中当前的内容匹配。

选择 $/ProviderServices/branches/MRRTracking/v8.2 处的开发分支并执行另一个“合并…”操作。这次,合并的目标是 $/ProviderServices/branches/MRRTracking/v8.4,但我们仍然希望选择特定的 changesets 进行合并。

由于 TFS 会跟踪合并,UI *应该*只显示那些尚未合并到 8.2 发布的更改。选择所有相关的 changesets 并执行合并。

这将使用 8.2 中特定于 8.4 的更改更新您 8.4 开发分支的本地工作副本中的文件。使用注释“Merged r###,r###-r###,r###”(其中 ### 是合并中包含的每个 changeset 的 changeset 编号,或连续 changeset 的起始和结束 changeset 编号)提交更改。

现在 /trunk 中的 8.2 发布文件夹包含它应该包含的内容,并且 /branches 中的 8.4 开发文件夹包含它应该包含的内容,我们可以设置 /branches 中的 8.2 开发文件夹以匹配发布。

在一个好的版本控制系统中,您无法“撤销”,但您可以随时覆盖。我们将利用这一事实来撤销 8.2 中针对 8.4 所做的更改。

选择 $/ProviderServices/trunk/MRRTracking/v8.2 处的 8.2 发布文件夹,然后执行另一个“合并…”操作。将合并的目标设置为 $/ProviderServices/branches/MRRTracking/v8.2 开发文件夹,然后选择所有更改并执行合并。

合并操作完成后,8.2 开发文件夹现在将与 8.2 发布文件夹中的内容在目录、文件、行级上完全匹配,从而有效地撤销了特定于 8.4 的那些更改。使用注释“Merged from r916 to undo 8.4-specific changes”提交这些更改。

如果您有自动化构建系统,请指示构建系统在 $/ProviderServices/trunk/MRRTracking/v8.2/Src 文件夹中构建和标记解决方案(并且,如果它还执行部署,则可以选择在哪里部署它找到的每个 Hosts 项目)。

六. 场景 #5 – 初始开发

您的团队负责“Medical Records Request Tracking”项目,团队成员之间称之为 MRRTracking。您是该项目的开发人员,您的 TFS 中的团队项目名称是“Provider Services”。该项目已在版本控制中定义在 $/ProviderServices/branches/MRRTracking/v8.2,并且解决方案已在相应的“Src”文件夹中创建。

为了能够在此项目上工作,您首先需要从服务器获取一份副本。使用您的源代码管理工具,导航到 $/ProviderServices/branches/MRRTracking/v8.2 开发文件夹,并通过执行“获取最新”操作从 TFS 签出项目。

这将会在您的硬盘驱动器上的某个位置创建项目的源代码和依赖项的本地工作副本。您现在可以从 Windows 资源管理器或源资源管理器中打开解决方案文件。继续阅读日常开发场景以了解其他任务。

七. 场景 #6 – 日常开发

您的团队负责“Medical Record Request Tracking”项目,团队成员之间称之为 MRRTracking。您是该项目的开发人员,您的 TFS 中的团队项目名称是“Provider Services”。该项目已在版本控制中定义在 $/ProviderServices/branches/MRRTracking/v8.2,并且解决方案已在相应的“Src”文件夹中创建。您已经有了项目的本地工作副本,并且需要进行一些更改或实现新功能。您的工作副本当前没有任何未提交的更改。

使用您的源代码管理工具,用服务器上可能存在的任何更改更新您的本地源代码(在 TFS 中为“获取最新”),然后从 Windows 资源管理器、IDE 的最近使用列表或源资源管理器中打开解决方案。

在打开解决方案并进行最新源代码更新后,应采取的第一项操作是执行本地生成。此操作可确保您了解项目的当前状态,而不会浪费时间向非功能性项目添加功能或特性。本地生成或编译步骤是您的“冒烟测试”(术语借用自电气工程领域,其中一个组件通过插入电源进行“测试”…如果它开始冒烟,则显然有问题,如果不是,则可能无法正常工作,但没有明显损坏)。

如果项目无法编译,请联系项目最近的贡献者。您可以通过使用源代码管理工具查看分支的历史记录来找到此信息以及您需要联系的人员的姓名或电子邮件地址。您可能会看到类似以下屏幕截图的输出

根据用户列中的用户 ID,您可以通过运行以下命令(将 sxd7278 替换为感兴趣的用户 ID)来确定是哪个用户进行了更改

一旦您知道谁是最近的贡献者,就打电话或发消息给他们,询问他们是否完成了提交,并告知他们项目无法编译。这可能只是一个情况,即其他贡献者正在进行多步任务,尚未提交任务的所有部分,或者该贡献者可能无意中引入了仅在其机器上可用的依赖项。

如果可以联系到该贡献者,则与他们一起解决任何问题,直到项目稳定为止,然后执行另一次源代码更新和生成。

在许多情况下,可能无法立即联系到贡献者。仅举几例;一天中的时间、时区差异、会议、调职和离职都会影响最近贡献者的可用性。在这些情况下,您必须在进行更改之前获取项目上最新的可用功能版本,而不是停止工作。

13. 附加材料

请阅读以下材料,以获取有关本文档中提供的指导的更多来源和依据。软件配置管理(通用)

I. Team Foundation Server

II. Microsoft Visual SourceSafe

三. 项目文件夹结构(通用)

[1] “Lib”文件夹通常包含对应于特定第三方供应商的子文件夹,例如 Infragistics、Oracle、Log4Net 等。这种子结构使得在获得新版本时更容易更新每个单独供应商的库集。

© . All rights reserved.