本文章是由機器翻譯。

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