[轉載]https://www.ibm.com/developerworks/cn/java/j-lo-git-mange/
Git 是目前最流行的源代碼管理工具。大量的軟件項目由 GitHub、Bitbucket 和 GitLab 這樣的云服務平臺或是私有的 Git 倉庫來管理。在使用 Git 時通常會遇到的一個問題是采用何種分支管理實踐,即如何管理倉庫中作用不同的各類分支。和軟件開發中的其他實踐一樣,Git 分支管理并沒有普遍適用的最佳做法,而只有對每個團隊和項目而言最適合的做法。簡單來說,在項目開發中使用多個分支會帶來額外的管理和維護開銷,但是多個分支對于項目的團隊合作、新功能開發和發布管理都是有一定好處的。不同的團隊可以根據團隊人員組成和意愿、項目的發布周期等因素選擇最適合的策略,找到最適合團隊的管理方式。本文將介紹三種常見的 Git 分支管理方式。
單主干
單主干的分支實踐(Trunk-based development,TBD)在 SVN 中比較流行。Google 和 Facebook 都使用這種方式。trunk 是 SVN 中主干分支的名稱,對應到 Git 中則是 master 分支。TBD 的特點是所有團隊成員都在單個主干分支上進行開發。當需要發布時,先考慮使用標簽(tag),即 tag 某個 commit 來作為發布的版本。如果僅靠 tag 不能滿足要求,則從主干分支創建發布分支。bug 修復在主干分支中進行,再 cherry-pick 到發布分支。圖 1 是 TBD 中分支流程的示意圖。
圖 1. TBD 中的分支流程的示意圖
由于所有開發人員都在同一個分支上工作,團隊需要合理的分工和充分的溝通來保證不同開發人員的代碼盡可能少的發生沖突。持續集成和自動化測試是必要的,用來及時發現主干分支中的 bug。因為主干分支是所有開發人員公用的,一個開發人員引入的 bug 可能對其他很多人造成影響。不過好處是由于分支所帶來的額外開銷非常小。開發人員不需要頻繁在不同的分支之間切換。
GitHub flow
GitHub flow 是 GitHub 所使用的一種簡單的流程。該流程只使用兩類分支,并依托于 GitHub 的 pull request 功能。在 GitHub flow 中,master 分支中包含穩定的代碼。該分支已經或即將被部署到生產環境。master 分支的作用是提供一個穩定可靠的代碼基礎。任何開發人員都不允許把未測試或未審查的代碼直接提交到 master 分支。
對代碼的任何修改,包括 bug 修復、hotfix、新功能開發等都在單獨的分支中進行。不管是一行代碼的小改動,還是需要幾個星期開發的新功能,都采用同樣的方式來管理。當需要進行修改時,從 master 分支創建一個新的分支。新分支的名稱應該簡單清晰地描述該分支的作用。所有相關的代碼修改都在新分支中進行。開發人員可以自由地提交代碼和 push 到遠程倉庫。
當新分支中的代碼全部完成之后,通過 GitHub 提交一個新的 pull request。團隊中的其他人員會對代碼進行審查,提出相關的修改意見。由持續集成服務器(如 Jenkins)對新分支進行自動化測試。當代碼通過自動化測試和代碼審查之后,該分支的代碼被合并到 master 分支。再從 master 分支部署到生產環境。圖 2 是 GitHub flow 分支流程的示意圖。
圖 2. Github flow 中的分支流程的示意圖
GitHub flow 的好處在于非常簡單實用。開發人員需要注意的事項非常少,很容易形成習慣。當需要進行任何修改時,總是從 master 分支創建新分支。完成之后通過 pull request 和相關的代碼審查來合并回 master 分支。GitHub flow 要求項目有完善的自動化測試、持續集成和部署等相關的基礎設施。每個新分支都需要測試和部署,如果這些不能自動化進行,會增加開發人員的工作量,導致無法有效地實施該流程。這種分支實踐也要求團隊有代碼審查的相應流程。
git-flow
git-flow 應該是目前流傳最廣的 Git 分支管理實踐。git-flow 圍繞的核心概念是版本發布(release)。因此 git-flow 適用于有較長版本發布周期的項目。雖然目前推崇的做法是持續集成和隨時發布。有的項目甚至可以一天發布很多次。隨時發布對于 SaaS 服務類的項目來說是很適合的。不過仍然有很大數量的項目的發布周期是幾個星期甚至幾個月。較長的發布周期可能是由于非技術相關的因素造成的,比如人員限制、管理層決策和市場營銷策略等。
git-flow 流程中包含 5 類分支,分別是 master、develop、新功能分支(feature)、發布分支(release)和 hotfix。這些分支的作用和生命周期各不相同。master 分支中包含的是可以部署到生產環境中的代碼,這一點和 GitHub flow 是相同的。develop 分支中包含的是下個版本需要發布的內容。從某種意義上來說,develop 是一個進行代碼集成的分支。當 develop 分支集成了足夠的新功能和 bug 修復代碼之后,通過一個發布流程來完成新版本的發布。發布完成之后,develop 分支的代碼會被合并到 master 分支中。
其余三類分支的描述如表 1所示。這三類分支只在需要時從 develop 或 master 分支創建。在完成之后合并到 develop 或 master 分支。合并完成之后該分支被刪除。這幾類分支的名稱應該遵循一定的命名規范,以方便開發人員識別。
表 1. git-flow 分支類型
分支類型 | 命名規范 | 創建自 | 合并到 | 說明 |
---|---|---|---|---|
feature | feature/* | develop | develop | 新功能 |
release | release/* | develop | develop 和 master | 一次新版本的發布 |
hotfix | hotfix/* | master | develop 和 master | 生產環境中發現的緊急 bug 的修復 |
對于開發過程中的不同任務,需要在對應的分支上進行工作并正確地進行合并。每個任務開始前需要按照指定的步驟完成分支的創建。例如當需要開發一個新的功能時,基本的流程如下:
- 從 develop 分支創建一個新的 feature 分支,如 feature/my-awesome-feature。
- 在該 feature 分支上進行開發,提交代碼,push 到遠端倉庫。
- 當代碼完成之后,合并到 develop 分支并刪除當前 feature 分支。
在進行版本發布和 hotfix 時也有類似的流程。當需要發布新版本時,采用的是如下的流程:
- 從 develop 分支創建一個新的 release 分支,如 release/1.4。
- 把 release 分支部署到持續集成服務器上進行測試。測試包括自動化集成測試和手動的用戶接受測試。
- 對于測試中發現的問題,直接在 release 分支上提交修改。完成修改之后再次部署和測試。
- 當 release 分支中的代碼通過測試之后,把 release 分支合并到 develop 和 master 分支,并在 master 分支上添加相應的 tag。
因為 git-flow 相關的流程比較繁瑣和難以記憶,在實踐中一般使用輔助腳本來完成相關的工作。比如同樣的開發新功能的任務,可以使用 git flow feature start my-awesome-feature 來完成新分支的創建,使用 git flow feature finish my-awesome-feature 來結束 feature 分支。輔助腳本會完成正確的分支創建、切換和合并等工作。
Maven JGit-Flow
對于使用 Apache Maven 的項目來說,Atlassian 的 JGit-Flow 是一個更好的 git-flow 實現。JGit-Flow 是一個基于 JGit 的純 Java 實現的 git-flow,并不需要安裝額外的腳本,只需要作為 Maven 的插件添加到 Maven 項目中即可。JGit-Flow 同時可以替代 Maven release 插件來進行發布管理。JGit-Flow 會負責正確的設置不同分支中的 Maven 項目的 POM 文件中的版本,這對于 Maven 項目的構建和發布是很重要的。
在 Maven 項目的 pom.xml 文件中添加代碼清單1中的插件聲明就可以使用 JGit-Flow。<configuration>中包含的是 JGit-Flow 不同任務的配置。
清單 1. JGit-Flow 的 Maven 設置
|
<``build``>
<``plugins``>
<``plugin``>
<``groupId``>external.atlassian.jgitflow</``groupId``>
<``artifactId``>jgitflow-maven-plugin</``artifactId``>
<``version``>1.0-m5.1</``version``>
<``configuration``>
<``flowInitContext``>
<``versionTagPrefix``>release-</``versionTagPrefix``>
</``flowInitContext``>
<``releaseBranchVersionSuffix``>RC</``releaseBranchVersionSuffix``>
<``noDeploy``>true</``noDeploy``>
<``allowSnapshots``>true</``allowSnapshots``>
<``allowUntracked``>true</``allowUntracked``>
</``configuration``>
</``plugin``>
</``plugins``>
</``build``>
|
JGit-Flow 提供了很多配置選項,可以在 POM 文件中聲明。這些配置項可以對不同的任務生效。常用的配置項如表2 所示。
表 2. JGit-Flow 的配置項
名稱 | 描述 | 適用任務 |
---|---|---|
flowInitContext | 配置不同類型的分支的名稱 | 全局 |
allowSnapshots | 是否允許存在 SNAPSHOT 類型的依賴 | |
allowUntracked | 是否允許本地 Git 中存在未提交的內容 | |
scmCommentPrefix 和 scmCommentSuffix | JGit-Flow 會進行代碼合并工作。通過這兩個配置項來設置 JGit-Flow 進行代碼提交時的消息的前綴和后綴 | 全局 |
username 和 password | 進行 Git 認證時的用戶名和密碼,適用于 HTTPS 倉庫 | 全局 |
releaseBranchVersionSuffix | release 分支中項目的 POM 文件版本號的后綴 | |
developmentVersion | 下一個開發版本的版本號 | |
releaseVersion | 發布版本的版本號 | |
pushReleases | 是否把 release 分支 push 到遠程倉庫 | |
goals | 當部署發布版本到 Maven 倉庫時執行的目標 | release-finish,hotfix-finish |
keepBranch | 當發布完成之后是否保留相應的分支 | release-finish,hotfix-finish |
noDeploy | 是否啟用部署到 Maven 倉庫的功能 | release-finish,hotfix-finish |
noReleaseBuild | 在完成發布時是否禁用 Maven 構建 | release-finish |
noReleaseMerge | 在完成發布時是否把 release 分支合并回 develop 和 master 分支 | release-finish |
noTag | 在完成發布時是否添加標簽 | release-finish,hotfix-finish |
squash | 在進行分支合并時,是否把多個 commit 合并成一個 | release-finish,hotfix-finish |
featureName | 新特性分支的名稱 | feature-start,feature-finish,feature-deploy |
pushFeatures | 是否把特性分支 push 到遠程倉庫 | feature-start,feature-finish |
noFeatureBuild | 在完成特性時是否禁用 Maven 構建 | feature-finish |
noFeatureMerge | 在完成特性時是否把特性分支合并回 develop | feature-finish |
pushHotfixes | 在完成 hotfix 時是否把分支合并回 master | hotfix-finish |
noHotfixBuild | 在完成 hotfix 時是否禁用 Maven 構建 | hotfix-finish |
其余的配置項可以參考插件不同任務的文檔。
在啟用了 JGit-Flow 之后,可以通過 mvn 運行 jgitflow:feature-start 和 jgitflow:feature-finish 來開始和結束新特性的開發。與版本發布和 hotfix 相關的命令分別是 jgitflow:release-start 和 jgitflow:release-finish 以及 jgitflow:hotfix-start 和 jgitflow:hotfix-finish。在運行命令之后,JGit-Flow 會完成相關的 Git 分支創建、合并、刪除、添加 tag 等操作,不需要開發人員手動完成。
每個分支的 pom.xml 中的版本號的格式并不相同。如 master 分支的版本號是標準的發布版本號,如 1.2.3;develop 分支中則是 SNAPSHOT 版本,比 master 分支的版本號要高,如 1.2.4-SNAPSHOT;release 分支可以通過<releaseBranchVersionSuffix>配置來指定版本號的后綴,如 1.2.3-RC-SNAPSHOT;feature 分支可以通過<enableFeatureVersions>配置來把 feature 分支名稱作為后綴添加到版本號中,如 1.2.3-my-awesome-feature-SNAPSHOT;hotfix 分支的版本號基于 master 分支的版本號,如 1.2.3.1 是對于 1.2.3 版本的第一個 hotfix。當使用 jgitflow:release-finish 完成一個 release 分支時,develop 分支的版本號會被自動更新成下一個小版本,如從 1.2.3 到 1.2.4。當需要手動修改版本號時,可以使用 Versions 插件,如 mvn versions:set -DnewVersion=2.0.0-SNAPSHOT。
持續集成
由于 JGit-Flow 是純 Java 的 Maven 插件實現,可以很容易的與常用的持續集成服務器進行集成。不過在與 Atlassian 的 Bamboo 集成時,有幾個細節需要注意。首先是 Bamboo 在進行構建的時候,使用的是一個虛擬的 Git 倉庫,其倉庫地址是一個不存在的文件系統路徑。因此需要在 JGit-Flow 的配置中手動設置 Git 倉庫的地址,保證 Git 操作可以正確執行,如代碼清單2所示。
清單 2. 手動設置 JGit-Flow 的 Git 倉庫地址
|
<``configuration``>
<``defaultOriginUrl``>[Git url]</``defaultOriginUrl``>
<``alwaysUpdateOrigin``>true</``alwaysUpdateOrigin``>
</``configuration``>
|
另外在開始新的 release 之前,需要確保前一個發布分支已經被刪除。JGit-Flow 在默認情況下會自動在發布完成之后,刪除對應的 Git 分支。但是可能本地倉庫中還保留有之前的發布分支,這會導致新的 release-start 任務執行失敗。一種解決方式是每次都重新 checkout 新的倉庫,這樣可以保證不會出現已經在遠程被刪除的分支。不過可能會增加構建的時間。另外一種解決方式是通過 Git 命令來刪除本地分支,如代碼清單3所示。
清單 3. 刪除 Git 本地分支
|
${bamboo.capability.system.git.executable} fetch --prune --verbose
${bamboo.capability.system.git.executable} branch -vv | awk '/: gone]/{print $1}' |
xargs ${bamboo.capability.system.git.executable} branch -d 2> /dev/null
echo 'stale branches deleted'
Git 分支合并沖突處理
|
當把發布分支合并到 develop 時,可能會出現沖突。因為在發布分支中有與 bug fix 相關的改動,在 develop 分支中有可能修改相同的文件。當有沖突時,直接運行 JGit-Flow 的 release-finish 任務會出錯。這個時候需要開發人員手動把發布分支合并到 develop 分支,并解決相應的沖突。然后再次運行 release-finish 任務即可。
選擇合適的實踐
每個開發團隊都應該根據團隊自身和項目的特點來選擇最適合的分支實踐。首先是項目的版本發布周期。如果發布周期較長,則 git-flow 是最好的選擇。git-flow 可以很好地解決新功能開發、版本發布、生產系統維護等問題;如果發布周期較短,則 TBD 和 GitHub flow 都是不錯的選擇。GitHub flow 的特色在于集成了 pull request 和代碼審查。如果項目已經使用 GitHub,則 GitHub flow 是最佳的選擇。GitHub flow 和 TBD 對持續集成和自動化測試等基礎設施有比較高的要求。如果相關的基礎設施不完善,則不建議使用。
小結
Git 作為目前最流行的源代碼管理工具,已經被很多開發人員所熟悉和使用。在基于 Git 的團隊開發中,Git 分支的作用非常重要,可以讓團隊的不同成員同時在多個相對獨立的特性上工作。本文對目前流行的 3 種 Git 分支管理實踐做了介紹,并著重介紹了 git-flow 以及與之相關的 Maven JGit-Flow 插件。
相關主題
- 了解 Google 和 Facebook 的單主干實踐。
- 了解 GitHub flow 的更多內容。
- 了解 git-flow 的更多內容。
- 了解 Maven JGit-Flow 插件的更多內容。
- developerWorks Java 技術專區:這里有數百篇關于 Java 編程各個方面的文章。