之前在研究切割 Git Repository 的時候,曾經寫了一篇《分割 Git Repository》做紀錄,不過最近要再做處理的時候,才發現當時紀錄的方法,比較接近使用「filter-branch」這個指令來硬上;實際上 Git 本身還有提供其他的方法,可以更直覺地把 Repository 的一部分、切割出來成為一個獨立的 Repository。
這樣的功能,在 Git 裡面是所謂的 subtree。
如果要針對現有的 respository 做切割的話,基本上就是要使用 git subtree split 這個指令;例如:
git subtree split -P ModuleAPath -b "ModuleA"
上面的 git 指令的效果,就是建立出一個新的新的分支(branch)「ModuleA」,裡面只有「ModuleAPath」這個資料夾下的東西而已。
之後,則可以直接使用這個分支,或是再另外建立一個新的 repository,來專門儲存這個新建立的 branch,這樣就可以了。這邊的指令會類似:
mkdir ModuleARepo
cd ModuleARepo
git init
git pull OriginRepoPath ModuleA
而如果在 split 的指令後面不加上「-b」的話,他會把指定的路徑(ModuleAPath)當作一個獨立的 repository 來處理,而不會獨立出來;在這個概念下,其實也可以不建立出新的分支,而直接針對 subtree 進行 push 或 pull。
下面就是一個範例:
git subtree push -P ModuleAPath RemoteA master
來把 ModuleAPath 這個子資料夾的東西,推送到 RemoteA 這個 repository  的 master 分支。
附帶一提,這邊比較討厭的一點,是 TortoiseGit(官網)並不支援 subtree,所以相關的動作沒辦法用 TortoiseGit 的圖形介面來做操作,必須要使用 Git 的指令列才行。
在 Heresy 自己的經驗,使用 Git 本身的 subtree split 來做切割,算是滿方便的。不過,這樣做的缺點是切割出來的新 repository 是由單一分支來的,所以假設本來這個資料夾下面,本來有不同分支的變更紀錄,都不會被保存下來;最後留存下來的,只有單一分支,而記錄也只有用來切割的分支的紀錄而已。
相較於此,用 「filter-branch」硬上的方法,則是本來的分支結構都在,而且有較完整的紀錄。
此外,subtree split 也僅能切割出單一資料夾,沒辦法同時保有兩個資料夾;相較之下,使用 filter-branch 會有更大的自由度。
最後,透過 subtree 切割之後,和該資料夾相關的 commit 紀錄都會被複製一份,成為一條獨立的線;所以理論上再透過 subtree 切割出分支之後,應該要在使用之後,把該分支移除、以減少 repository 中重複的 commit 紀錄(SHA-1 不一樣,但是內容是相同的)。
而切出來之後要怎麼使用呢?之前 Heresy 還沒研究,這次也稍微看了一下。
基本上,要把一個外部的 repository 拿來使用,在 git 環境下可以透過 subtree 和 submodule 這兩種方法來做到。兩種不同的方法,在使用概念上有滿大的差異,而且看來也是各有優缺點。
在概念上:
- git subtree
- 把外部的 repository 整個包含紀錄、一起複製進來,變成單一個 repository。
- 完成合併之後,就是單一 repository 了,所有操作都和之前一樣,沒有任何變化。
- 要只做這個模組的更新、或是重新切出來,都會比較複雜,需要重新跑 subtree 的指令。
- git submodule
- 建立一個連結,在 submodule 資料夾下另外去 clone 外部的 repository 來用,實際上會是獨立的 repository。
- 由於是採用特殊的連結架構,所以在操作上會需要特別的指令。
- 在跟目錄下會多一個「.gitmodules」的檔案,紀錄 submodule 的資訊。
- submodule 會指定到特定的版本,如果要更新 submodule 的內容的話,會需要另外處理。
在 Heresy 來看,submodule 比較適合用來連結外部的函式庫、或是多個專案的共用元件;而 subtree 則比較適合用來把其他的 repository 完全合併進來、放棄他的獨立性。
參考: