Git Subtree:多專案整合的實用技巧
Git Subtree 介紹
什麼是 Git Subtree?
git subtree 是 Git 內建的專案管理工具,它解決了一個常見的開發需求:
如何在專案 A 中整合專案 B 的程式碼,同時保持專案 B 的獨立性與可更新性?
假設你正在開發一個主專案,需要使用另一個獨立維護的函式庫。如果只是把那個函式庫 clone 下來後複製到主專案中,雖然可以使用,但會遇到幾個問題:無法追蹤程式碼來源、難以同步原始專案的更新、無法將改進推送回原始專案。類似的情境還包括:
- 如何將主專案中某個模組拆分成獨立的 repository 供其他專案使用?
- 如何將多個獨立的小專案整合到一個統一的大 repository 中?
Git Subtree 提供的功能可以很好地解決這些問題。它可以將一個外部 Git repository 的內容完整嵌入到你的專案的子目錄中。這些內容會真正成為你的專案的一部分(不是連結或參照),同時保留與原始專案的關聯。使用 Git Subtree 後,你的專案結構可能如下:
main-project/
├── .git/
├── src/
│ └── main.js
├── lib/
│ └── shared-library/ ← Git Subtree (來自外部 repo)
│ ├── index.js
│ └── utils.js
└── README.md
在這個例子中,lib/shared-library/ 目錄的內容來自另一個獨立的 Git repository(例如 https://github.com/user/shared-library.git),但它已經完全整合到 main-project 中。
Git Subtree 有以下幾個主要的功能特點:
- 完整整合:當其他開發者 clone 主專案時,會直接取得
lib/shared-library/的所有檔案和內容,不需要執行任何額外的初始化指令。這與 Git Submodule 不同,後者 clone 後只會得到一個空目錄的參照,需要額外執行git submodule init和git submodule update才能取得實際內容。 - 可直接修改:
lib/shared-library/是真實存在於主專案中的目錄和檔案,不是符號連結或參照。你可以直接在主專案中編輯這些檔案,所有修改都會被 Git 正常追蹤。這與 Git Submodule 不同,後者的子目錄實際上是一個獨立的 Git repository,修改時需要進入該目錄並在其 Git 環境中操作。 - 雙向同步:可以從原始專案拉取更新(使用
git subtree pull將原始shared-libraryrepository 的新版本同步到主專案),也可以將主專案中的修改推送回原始專案(使用git subtree push將你在主專案中對lib/shared-library/的改進推送回原始的shared-libraryrepository)。
Git Subtree vs Git Submodule
| 特性 | Git Subtree | Git Submodule |
|---|---|---|
| 內容儲存 | 完整複製到主 repo | 僅儲存 commit 參照 |
| Clone 行為 | 一次 git clone 即可取得所有內容 | 需要額外執行 git submodule init/update |
| 學習曲線 | 較簡單,使用標準 Git 指令 | 較複雜,需要理解 submodule 概念 |
| 歷史記錄 | 子專案歷史可選擇性保留或壓縮 | 子專案歷史獨立於主專案 |
| 檔案大小 | 主 repo 較大(包含所有內容) | 主 repo 較小(僅參照) |
| 適用情境 | 需要完整整合、簡化協作流程 | 需要明確版本控制、多專案共用 |
| 修改子專案 | 可直接在主 repo 中修改並推回 | 需要進入 submodule 目錄操作 |
Git Subtree 的兩大常見用法
1. 將外部專案整合到主專案(Add & Pull)
使用情境:在主專案中使用某個函式庫或共用模組,並且能夠定期 同步上游的更新。
2. 從主專案拆分出子專案(Split & Push)
使用情境:將主專案中開發某個模組拆分成獨立的 repository,方便其他專案使用。
Git Subtree 指令詳解
git subtree add
用途:將外部 repository 的內容加入到當前 repo 的指定子目錄中。
語法
git subtree add --prefix=<dir> <repository> <ref> [--squash] [--message=<msg>]
參數說明
| 參數 | 必填/選填 | 說明 | 預設值 |
|---|---|---|---|
--prefix=<dir> | 必填 | 指定子專案要放置的目錄路徑 | 無 |
<repository> | 必填 | 遠端 repo 的 URL 或已設定的 remote 名稱 | 無 |
<ref> | 必填 | 要拉取的分支名稱、tag 或 commit hash | 無 |
--squash | 選填 | 將外部 repo 的所有歷史壓縮成單一 commit | 不壓縮,保留完整歷史 |
--message=<msg> | 選填 | 自訂合併 commit 的訊息 | 自動生成訊息 |
實際範例
# 範例 1:加入外部函式庫(保留完整歷史)
git subtree add --prefix=lib/utils https://github.com/user/utils-lib.git main
# 範例 2:加入外部函式庫(壓縮歷史,保持主專案乾淨)
git subtree add --prefix=vendor/logger https://github.com/user/logger.git v1.0 --squash
# 範例 3:使用已設定的 remote
git remote add utils-remote https://github.com/user/utils-lib.git
git subtree add --prefix=lib/utils utils-remote main --squash
git subtree pull
用途:從遠端 repository 拉取最新變更,並合併到當前 repo 的 subtree 目錄中。用於同步上游專案的更新。
語法
git subtree pull --prefix=<dir> <repository> <ref> [--squash] [--message=<msg>]
參數說明
| 參數 | 必填/選填 | 說明 | 預設值 |
|---|---|---|---|
--prefix=<dir> | 必填 | 指定要更新的 subtree 目錄路徑 | 無 |
<repository> | 必填 | 遠端 repo 的 URL 或 remote 名稱 | 無 |
<ref> | 必填 | 要拉取的分支、tag 或 commit | 無 |
--squash | 選填 | 將更新壓縮成單一 commit | 不壓縮,保留完整歷史 |
--message=<msg> | 選填 | 自訂合併 commit 訊息 | 自動生成訊息 |
實際範例
# 範例 1:更新 subtree(保留完整歷史)
git subtree pull --prefix=lib/utils https://github.com/user/utils-lib.git main
# 範例 2:更新 subtree(壓縮歷史)
git subtree pull --prefix=lib/utils https://github.com/user/utils-lib.git main --squash
# 範例 3:使用 remote 名稱
git subtree pull --prefix=vendor/logger logger-remote v2.0 --squash