HOWTO · Git

Git 中的子模組更新

本教程演示瞭如何在 Git 中使用多個命令更新子模組。

本頁內容

Git 會將子模組儲存為父工作目錄中特定路徑下的一個 Git 倉庫。父倉庫記錄的是某個特定的子模組提交(gitlink),而不是分支名稱,也不會自動追蹤子模組的最新提交。子模組有自己的歷史紀錄與工作目錄。在子模組內更新檔案,以及更新父倉庫所記錄的提交,是彼此相關但分開的步驟。

Git 子模組如何追蹤提交

父倉庫中的 .gitmodules 檔案會將子模組名稱對應到路徑,通常也會提供其複製網址。當你在子模組內提交變更時,父倉庫不會將那些檔案納入自己的提交;父倉庫的索引可以改為在該路徑記錄新的子模組提交。之後複製父倉庫的人,會取出父倉庫記錄的那個提交。

常見的更新目標有兩種。一般的 git submodule update 會取出父倉庫已記錄的提交,通常會讓子模組停留在分離的 HEAD 狀態。加上 --remote 則會從子模組遠端擷取資料,並從已設定的遠端追蹤分支選取提交。這可能會讓子模組移到父倉庫目前記錄的提交之後;但 Git 不會替你在父倉庫提交這項變更。

複製後初始化子模組

若要複製專案並遞迴填入子模組,請使用 git clone --recurse-submodules <repository-url>。如果你已複製專案,但沒有使用這個選項,請在父倉庫中執行 git submodule update --init --recursive。--init 會依照父倉庫的設定初始化已取出的子模組;--recursive 也會初始化巢狀子模組。

git submodule init 指令會將 .gitmodules 中可用的子模組設定複製到本機的 .git/config,但不會建立 .gitmodules。該檔案屬於父專案,必須已經存在(或由專案維護者新增)。如果 .gitmodules 中的子模組網址有所變更,請先執行 git submodule sync --recursive,將更新後的網址複製到本機設定,再擷取或更新子模組。

從遠端更新子模組

若要擷取遠端追蹤提交並更新所有已設定的子模組,請在父倉庫中執行 git submodule update --remote --recursive。Git 會使用子模組已設定的分支;如果沒有指定分支,就會使用遠端的預設分支。專案可以在 .gitmodules 中設定分支,例如使用 git config -f .gitmodules submodule.<name>.branch <branch>。若要讓協作者取得這項設定,請在父倉庫中提交經過確認的 .gitmodules 變更。

遠端更新後,子模組可能會停留在與父倉庫記錄不同的提交。Git 不保證工作目錄會保持乾淨:本機修改、衝突或未追蹤檔案都可能仍然存在。繼續操作前,請檢查子模組與父倉庫的狀態,並在切換提交前保留或解決本機工作。

更新單一子模組或所有子模組

在選項後方傳入路徑,即可將更新限制在單一子模組:git submodule update --remote -- path/to/submodule。若要更新至已記錄的提交,而非遠端分支最新提交,請省略 --remote:git submodule update -- path/to/submodule 會更新單一子模組;git submodule update --init --recursive 則會初始化並取出所有已記錄的提交,包括巢狀子模組。請使用 .gitmodules 和父工作目錄中所列的路徑。-- 用來分隔選項與路徑。

如果你想讓 Git 擷取並取出已設定的遠端分支最新提交,請使用父倉庫層級的遠端更新。如果你想使用父專案指定的確切提交,例如另一位開發者提交的父倉庫變更更新了 gitlink,請使用一般更新。

檢查並提交父倉庫記錄的子模組提交

遠端更新或取出指定提交後,請回到父倉庫,檢查 git status 和 git diff --submodule。如果子模組路徑有變更,表示父倉庫目前看到的已取出提交與索引記錄的提交不同。如果這是專案應採用的版本,請使用 git add path/to/submodule 暫存該路徑,檢查 git diff --cached --submodule,再提交父倉庫的變更。這筆提交會記錄選定的 gitlink,讓協作者能重現該版本。

如果你在子模組內修改並提交了檔案,該提交屬於子模組倉庫。如果協作者需要擷取這筆提交,請先將它推送到子模組倉庫,再於父倉庫暫存並提交更新後的子模組路徑。提交 gitlink 不會推送子模組提交,也不會自動發布任一倉庫;請依照你的工作流程,分別推送各個倉庫。

選擇指定提交或合併策略

若要選擇已知的提交,請進入子模組目錄,並使用 git checkout <commit-id>(或 git switch --detach <commit-id>)取出該提交。取出提交後進入分離狀態是預期行為,適合用來固定在確切版本。接著檢查父倉庫狀態;若要讓父倉庫記錄該提交,請暫存子模組路徑。單獨取出提交不會改變父倉庫的提交。

如果你想將遠端變更整合到子模組分支,而不是分離狀態下取出提交,請進入子模組,先執行 git switch <branch>,再執行 git pull。這會依據該分支的上游設定操作,也可能建立合併提交或需要解決衝突。這和在父倉庫層級執行 git submodule update --remote 不同;後者會選取已設定的遠端追蹤提交。你也可以在遠端更新時要求合併或重設基底,使用 git submodule update --remote --merge 或 git submodule update --remote --rebase;請在子模組中檢查結果,若要與他人分享,再將產生的提交暫存到父倉庫。

排解常見的更新問題

如果複製後目錄是空的,或沒有已取出的檔案,請使用 git submodule update --init --recursive 初始化。如果更新後停留在非預期的提交,請確認你使用的是已記錄提交模式還是 --remote,並使用 git config -f .gitmodules --get submodule.<name>.branch 檢查已設定的分支。若未設定分支,Git 會使用遠端的預設分支。

如果 Git 回報有本機變更,或拒絕取出提交,請檢查子模組自己的 git status。更新前請先提交、暫存,或以其他方式保留這些變更;父倉庫的狀態不會顯示子模組內所有編輯的細節。如果合併或重設基底時發生衝突,請在子模組內解決衝突並完成操作,再檢查父倉庫。最後,父倉庫中的子模組路徑變更本身並不代表錯誤:這表示已取出的子模組提交與父倉庫目前記錄的提交不同。請決定是否保留這項變更;若確定要採用,就提交父倉庫層級的變更。