我想處理的情境如下:Git Repository X 內含多個資料夾,每個資料夾可視為子專案,比如 Project A、B、C、D。隨著專案規模長大及時空環境改變,想將其中的 Project D 移出來,另外新建 Git Repository Y 獨立發展,而搬移時要求修改歷程必須保留。

爬文查了一些做法,評估 Subtree 是最直覺簡便。

這裡用 Github 上的 WebCompiler 專案做示範,假設我想將其中的 src/WebCompiler 資料夾搬入另一個 Git Repository - MyRepo。

第一步是在 WebCompiler 專案下指令 git subtree split -P src/WebCompiler -b sub-project 將 src/WebCompiler 目錄分割成 Subtree。有一點要注意,-P 指向待分割的目錄,名稱「大小寫有別」,在 Windows 環境操作時很容易忽略這點,便會落得 No new revisions were found 的下場,我在這裡被卡了一下:

-b 參數可指定分支名稱,這個分支會指向分割後的結果 (src/WebCompiler 的內容被搬到 Repository 根目錄),方便後面拉資料時使用。

接著是新增 Repository 並將 Subtree 加進去,操作方法如下:

REM 建立 Repo 資料夾
mkdir MyRepo
cd MyRepo
REM 建立 Git Repository
git init && git commit --allow-empty -m "Init"
REM 將原專案設為遠端 Repo 來源
git remote add source ..\WebCompiler
REM subtree add 從來源加入 
REM -P 是要放置 Subtree 內容的資料夾名稱,source 是遠端名稱,sub-project 是剛才取的 Branch 名稱
git subtree add -P WebCompiler source sub-project

用 Visual Studio 檢查一下,檔案跟修改歷史都有進來,到這裡專案就算搬好了。

至於來源端,如果不打算保留移出的資料夾/專案,刪除 Subtree 分支,在 master 分支該資料夾刪除並 Commit;搬入 Subtree 的 Repository 則移除遠端設定斷開鎖鍊,搬家作業就算整個完成了。若來源端想要連歷程都抺去(是有多麼不堪回首呢?),可以用 filter-branch

依據網路文件,Subtree 還有一個神奇的功能,若來源的 Subtree 仍保留檔案,則搬出去的 Subtree 與來源之間仍能維持連繫,可從遠端 Pull 更新回來,Subtree 所做的修改甚至也可以 Push 回遠端,這若即若離的弱關聯還挺微妙的 ,像極了愛情

但 Pull 及 Push Subtree 的實驗我試了好一陣子沒能成功,加上查到有前輩分享曾用 Subtree 共用原始碼一陣子,但遇到不少花惹發等級的同步問題,最後回頭使用 Submodule。目前決定先不採取 Subtree 共用程式碼的策略,單純只用它搬子專案,未來有機會再深入研究。

Example of using Git subtree split and add to move a subproject between repositories.


Comments

# by Switch_Squirrel

居然連黑大也玩像極了愛情的梗 哈哈

# by Danny Lin

git subtree pull 或 push 失敗應該是因為沒有先 git subtree add。git subtree add 會建立 commit 把主線和 subtree 線關聯在一起,之後應該就可以 git subtree pull|pull 了。 但是 git subtree add 必須在工作目錄不含該子目錄時才能執行,如果是主專案用 git subtree split 分出的子目錄,要先用 git rm -r -- <子目錄> 移除才能 git subtree add;或者如果想省略 rm 和 add,也可以用 git subtree split --rejoin 直接把分出的分支和主線關聯在一起。 不過如果是想把子目錄的歷史移到主專案,應該會比較偏好用 git subtree --squash,但是 git 目前有個 bug 導致用 squash 合併子目錄的主線會在 push 時會把與子目錄無關的分支歷史一併推到子目錄(有興趣可參見 https://lore.kernel.org/git/CAMbsUu5bYN9q34st8AhOCv8y5eJQpVpEKOO3ebTi+CxbYetgmQ@mail.gmail.com/),所以建議只 pull 不 push,主專案就不要更動子目錄。

# by Danny Lin

(上一則留言錯字太多,修一下orz) git subtree pull 或 push 失敗應該是因為沒有先 git subtree add。git subtree add 會建立 commit 把主線和 subtree 線關聯在一起,之後應該就可以 git subtree pull|push 了。 但是 git subtree add 必須在工作目錄不含該子目錄時才能執行,如果主專案用 git subtree split 分出的子目錄,要先用 git rm -r -- <子目錄> 移除(並且 commit)才能 git subtree add;或者如果想省略 rm 和 add,也可以用 git subtree split --rejoin 直接把分出的分支和主線關聯在一起。 不過如果是想把子目錄的歷史從主專案移除,應該會比較偏好用 git subtree --squash,但是 git 目前有個 bug 導致用 squash 合併子目錄的主線會在 push 時會把與子目錄無關的分支歷史一併推送到子專案(有興趣可參見 https://tinyurl.com/y3ydbntw),所以建議只 pull 不 push,主專案不要有更動子目錄內容的 commit。

# by Jeffrey

to Danny Lin, 謝謝專業補充。

Post a comment