自從 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 分支指南的下一個版本中,我們將在復雜開發方案(例如功能團隊開發)方面增加更多的說明。
我們希望將分支策略討論分為兩個方面:
- 我的組織如何開發 軟件? 我們是否需要一個更小、更簡單的團隊結構,或者我們是否需要對進行並行開發工作的更多復雜團隊提供支持?
- 我的組織如何向其客戶(不管是內部還是外部客戶)發布軟件? 我們是否需要對多個已發布的版本提供支持? 我們是否需要提供修補程序或服務包?
在某些情況下,組織的發布策略可能影響開發過程,尤其是開發團隊的結構。 但在很多情況下,發布過程和分支策略的復雜性可能與開發過程和分支策略的復雜性無關。
在設計一種分支策略時,不僅要考慮分支結構,而且還要考慮分支過程。 例如,在 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)。
總之,在自動模型合並方面目前還沒有很好的策略。 建議的策略是盡量避免模型的分支和合並情況,或在合並之前使用可視和手動模型編輯。
現在,我們已介紹了多種您在復雜的實際環境中可能遇到的新分支情況。 在本系列的下一篇文章中,我們將研究團隊項目和團隊項目集合。
