Git教程 · 拆分大項目
- 1?? 概述
- 2?? 使用要求
- 3?? 執行過程及其實現
- 3.1 拆分模塊版本庫
- 3.2 將拆分出的模塊作為外部版本庫集成
- 4?? 替代解決方案
通常軟件項目都是由單體小型系統開始的,在開發過程中項目規模和團隊人員不斷擴大, 將項目模塊化會顯得越發重要。第一步是將項目內部結構模塊化,最終會需要將各個模塊獨立開發并擁有不同的提交發布周期。
由于 Git 版本庫是以整個版本庫作為一個整體來發布版本的,所以每個擁有獨立發布周期的模塊都需要新的Git 版本庫。
為 Git 版本庫拆分模塊過程中的挑戰之處在于要盡可能保留原版本庫中文件及其版本信息。同時,新的版本庫不應該包含本模塊不需要的文件,也不需要包含那些沒有更改本模塊相關文件的提交。
在主版本庫中,模塊的歷史沒有被刪除,所以原項目中的歷史版本已然存在并可以復現。因此,不同模塊的歷史數據同時存在于拆分前后兩個版本庫中。
大部分拆分出的模塊依然被主項目所需要,應以外部模塊的角色集成到主項目中,這種集成關系,在Git 中被稱為子模塊 (submodule)。
這段工作流演示了如何在Git 中抽取模塊,同時實現這樣3種目標。
- 只有該模塊所需要的文件被導入到新版本庫。
- 模塊文件歷史將被保留在新版本庫中。
- 模塊可以作為外來模塊再次被集成到主項目中。
1?? 概述
接下來這段操作中,我們使用如圖上部顯示的項目結構為例。這段實例工作流是基于Java 目錄結構的,整個項目有3個模塊,每個模塊中的文件分別置于源代碼 (src) 和測試代碼 (test) 兩個子目錄中。換句話說,也就是每個模塊包括兩個部分。接下來將模塊3分化到獨立的版本庫中。
第一步,刪除所有無用的文件,使用 filter branch
命令在原版本庫的一個克隆分支上提交即可。接下來,更新新模塊版本庫的目錄結構用以管理模塊3。最后,將模塊3從原項目中移除,再將新模塊版本庫作為子模塊合入原項目的外部引用目錄中,結果如圖下部所示。
新的模塊版本庫中可以重建文件的修改歷史,也就是跟蹤記錄誰在什么時間做了什么修改,但是不可以完整地重現歷史版本。原因是一個模塊的文件往往源自另外一些模塊。在模塊版本庫中嘗試恢復項目的某一歷史版本可能不僅會涉及本模塊目錄,而是不同目錄文件的混雜集合。而且,在過去的版本中本模塊可能被用作某些文件的依賴,而這些文件已經不存在了。
在主版本庫中,整個項目的舊版本依然可以恢復重現。
2?? 使用要求
- 項目內部需要模塊化時:項目內部需要被分為不同的模塊,比如當某一模塊需要獨立開發和發布版本。
- 模塊文件被分置于不同的目錄中時:這時要提取模塊的某一歷史版本,文件在不同個目錄中將需要不同的處理,如果文件十分分散代價將非常大。
3?? 執行過程及其實現
一個模塊從項目中被刪除并遷移到獨立的版本庫中,提交歷史將被保留下來,無用的文件和提交歷史將被刪除。獨立模塊將以外部子模塊的形式回到項目中。
需要注意,部分以下命令將徹底改變版本庫。雖然 Git 中改變通常可以撤回,但仍應在開始之前確保你的版本庫已備份。
> git clone --no-hardlinks --bare projekt.git projekt.backup.git
使用 --no-hardlinks
選項來保證克隆的版本庫和源版本庫不共享任何文件。
3.1 拆分模塊版本庫
-
第1步:克隆主版本庫
作為模塊版本庫的起點,首先將主版本庫克隆一份。> git clone --no-hardlinks --bare projekt.git modul3-work.git
-
第2步:刪除無用的文件和提交
接下來,必須刪除無用的文件和提交,這是最復雜的一步,也是為了保留模塊歷史至關重要的一步。
刪除一個版本庫中的部分內容可以用filter-branch
命令。它將針對待修改的提交來創建一次新的提交,通過配置不同的過濾器來改變這次提交的內容。
以下示例filter-branch
命令將刪除src/module1
目錄下內容。> cd module3 > git filter-branch --force --index-filter 'git rm -r -cached --ignore-unmatch src/module1'--tag-name-filter cat--prune-empty -- --all
參數可以這樣配置。
--index-filter 'git rm -r -cached --ignore-unmatch …'
: 通過配置這樣的參數,可以將文件從提交中移除。rm
命令逐個提交操作。在如上示例中,將作用于src/module1
文件目錄。 如果待清理的項目沒有明顯的模塊化結構層次,可能需要刪除多個文件或多個文件目錄。--tag-name-filter cat
: 可以為已經存在的或者新建的提交標注標簽。--prune-empty
: 將刪除經過前面的過濾器后不包含任何文件的空提交。--all
: 將過濾器適用于整個項目的所有分支。
在示例的項目中,如此的操作需要依次在 test/module1 、src/module2 和 test/module2 文件目錄下執行。
關于filter branch
命令每項參數的詳細描述,可以參照 Git 幫助。
-
第3步:刪除無用的分支和標簽
不是所有標簽和分支在新的模塊分支都有意義。例如,那些與模塊不相關的文件標簽和分支就是無意義的,需要被刪除。> git tag -d v1.0.1 > git branch -D v2.0_bf
-
第4步:縮減模塊版本庫的規模
Git 為了縮減規模在管理數據中刪除無用的文件需要重復克隆一次。> git clone --no-hardlinks --bare module3-work.git module3.git
這樣,過去的模塊版本庫 module3-work.git 就不再有效,可以刪除了。
> rm -rf modul3-work.git
-
第5步,定制模塊版本庫文件架構
到目前為止,新版本庫的文件結構和主項目一樣,只是刪除了無關本模塊的文件。調整文件目錄結構是通過一般的文件操作完成的,為了這個目的,首先應做一份帶有工作空間的克隆。> git clone module3.git module3
將源代碼目錄
src/module3
重命名為src
, 測試代碼目錄test/module3
重命名為test
。> cd module3 > mv src/module3 module3 > rmdir src > mv module3 src > mv test/module3 module3 > rmdir test > mv module3 test
接下來,修改操作通常是借助于
commit
命令,再通過push
上傳到干凈的版本庫中。> git add --all > git commit -m "Directory structure adapted" > git push ,
如果版本庫中有多個分支,那文件操作要在各個分支上依次完成。 .
通常沒有必要保留主項目所有的分支。新的版本庫有新的分周期,舊分支通常沒有意義。 -
第6步:在主項目中刪除已被拆分出來的模塊目錄
當拆分出的模塊已遷移到新的版本庫中,下一步就是讓主項目來做拆分后的調整。刪除 無用的源代碼目src/module3
和測試目錄test/module3
。這里的調整主要是在主版本庫中的一些普通的文件操作。如果項目中有多個分支需要集成這一個改變,那也需要分別進行調整。
cherry-pick
命令可以用作將變化調整部署到不同的分支。
3.2 將拆分出的模塊作為外部版本庫集成
經過前面一系列操作,現在已經擁有兩個版本庫了,通常原主項目仍需引用拆分后的模塊,所以需要集成操作。
集成操作嚴格依賴于開發平臺。例如在 Java Maven項目中,可以將拆分出的模塊項目獨 立創建編譯,并將結果保存在 Maven 項目中,在主項目中將其定義為依賴條件,在創建編譯主項目的過程中充 Maven 中獲得模塊項目。
如果使用 Git 來執行集成操作,那就需要用到子模塊 (submodule) 。 有了子模塊, 一個Git版本庫就可以鏈接到另外一個 Git版本庫了。
在上述示例中,模塊3 (module3) 的版本庫被鏈接到了主項目的extern/module3
文件目錄下。首先從主項目版本庫的一個克隆版開始操作。選定項目的根目錄,使用 submodule add
命令加入子模塊,該命令有兩個參數,第一個是模塊版本庫的路徑或 URL, 第二個參數是在主項目中即將鏈接的路徑。
>git submodule add /global-path-to/module3.git extern/module3
submodule add
命令會在特定的目錄下創建一個模塊版本庫的克隆,這個克隆會在主版本庫中作為外部引用。
文件目錄 extern/module3
指向外部版本庫的最新一次提交 (HEAD) 。
截至目前,子模塊只能在工作區中可見,需要只用 commit
命令提交使修改作用于整個版本庫。
>git add -all
>git commit -m "Modul3 added"
可以使用 push
命令將添加子模塊鏈接捷徑的修改推送到中央版本庫。
4?? 替代解決方案
-
何不采用一個全新的版本庫
有另外一種可以考慮的替換方案是簡單地為模塊創建一個新版本庫。那么,新版本庫中就沒有原模塊的歷史記錄了,但是原始版本庫中仍然有模塊的舊版本信息。如果這種缺陷可以被接受,那這種方案在實現上顯得最為簡單。 -
為什么不采用
--subdirectory-filter
選項
使用filter-branch
命令和-index-filter
參數可以實現將提交中的文件刪除。
filter-branch
命令的--subdirectory-filter
參數可以指定將排除某一文件夾之外的文件全部刪除,還可以在刪除指定的文件夾后改變項目的根目錄節點。只要模塊全部獨立存儲在一個文件目錄下,那么這個命令就可以較方便地用來創建模塊版本庫。在上文示例中,模塊文件被分散在兩個目錄下,因此不能適用這樣的操作。
即使模塊中的文件所屬文件目錄被遷移或者被改名,
subdirectory-filter
仍然可以保留部分歷史信息。
《【Git教程】(十七)發行版交付 — 概述及使用要求,執行過程及其實現,替代解決方案 ~》
