專案中的專案:git submodule 的協作魔法與陷阱

更新於 發佈於 閱讀時間約 13 分鐘

學校快開學了,孩子們的暑假已進入尾聲,工作這邊則是越來越上手,也越來越忙碌。

這週因為工作上需要處理一個較為特殊的專案結構,接觸到了 git submodule,可能有很多人都聽過,但實際應用在專案中的機會可能不多。

在 iOS 開發的生態系中,我們早已習慣使用 CocoaPods 或 Swift Package Manager (SPM) 來管理第三方套件。它們就像是 App 開發的得力助手,幫我們處理好 Dependency 的下載、編譯與整合。那為什麼我們還需要 git submodule呢?它和這些套件管理工具有什麼不同?又適合用在什麼樣的情境?

今天,就讓我們一起來揭開 git submodule 的神秘面紗。

什麼是 git submodule

簡單來說,git submodule 允許你在一個 Git repository(我們稱之為主專案 superproject)中,嵌入另一個 Git repository(稱之為子模組 submodule)。

這就像是在你的專案資料夾中,放入一個指向另一個專案的「書籤」。主專案本身不包含子模組的實際程式碼,它只記錄了兩件事:子模組的 repository URL,以及要使用的確切版本。

這意味著,主專案最終儲存的是一個特定的 commit hash。當然也可以設定讓子模組追蹤(track)某個特定的 branch或 tag。當你更新子模組時,Git 會自動抓取該分支或標籤的最新 commit,並將其 hash 記錄到主專案中。這讓版本控制變得既精確又靈活。

.gitmodules 檔案結構

當你新增 submodule 時,Git 會自動在專案根目錄建立一個 .gitmodules 檔案,記錄所有子模組的資訊:

[submodule "SharedLibrary"]
path = SharedLibrary
url = https://github.com/some/shared-library.git
branch = main

這個檔案包含三個關鍵資訊:

  • path:子模組在主專案中的相對路徑
  • url:子模組的遠端 repository URL
  • branch:要追蹤的分支(選用,預設為預設分支)

這個檔案會被納入版本控制,確保團隊成員都能取得相同的子模組設定。

submodule vs. CocoaPods / SPM

既然有了方便的 SPM 和 CocoaPods,為什麼還需要 submodule?它們的核心差異在於「目的」與「管理方式」。

raw-image

簡單來說,SPM 和 CocoaPods 更像是「消費者」模式,我們引入別人打包好的套件來使用。而 git submodule 則更像是「協作者」模式,我們將另一個我們也需要維護的專案,直接納入目前的開發流程中。

多人協作的魔法與陷阱

git submodule 在多人協作時,既是魔法,也充滿陷阱。

魔法之處

當你的團隊有多個專案需要共享一個私有的核心模組時,submodule 就非常方便。開發者可以在開發主專案的同時,直接修改 submodule 的程式碼,並將主專案與 submodule 的變更分別提交。這對於需要頻繁、同步修改主從專案的情境非常有用。

陷阱之處

然而,它的複雜性也帶來了許多協作上的挑戰:

  1. 忘記初始化:新成員 git clone 主專案後,submodule 的目錄會是空的。他們必須記得執行 git submodule update --init --recursive,否則專案會因為缺少檔案而編譯失敗。這是最常見的新手錯誤。
  2. 游離的 HEAD (Detached HEAD):當你進入 submodule 目錄時,Git 會自動切換到主專案所記錄的那個特定 commit,這會使你處於 detached HEAD 狀態。如果你在這個狀態下做了修改並 commit,這個 commit 不屬於任何分支,很容易在下次更新時遺失!正確的做法是先 cd 進入 submodulegit checkout 到一個分支,再進行修改。
  3. 更新流程繁瑣:要更新 submodule 到最新版本,你需要:
    • cd 進入 submodule 目錄,git pull 拉取最新程式碼。
    • 回到主專案目錄,git add 子模組的路徑,將新的 commit hash 記錄下來。
    • git commit 提交這次的更新。
      這個多步驟的流程很容易出錯或遺漏。
  4. 合併衝突:當不同分支都更新了 submodule 到不同的 commit 時,合併分支就會產生衝突,而解決這種衝突比解決一般程式碼衝突要複雜一些。
  5. 遠端 URL 過期 (Outdated Remote URL):如果子模組的 repository URL 變更了(例如,專案遷移到新的 Git 伺服器),單純執行 git submodule update 是不夠的。主專案的 .gitmodules 檔案雖然更新了,但每個協作者的本地 .git/config 仍然記錄著舊的 URL。這時就需要 git submodule sync 來同步設定。

基本操作

雖然有些複雜,但掌握以下幾個核心指令,就能應付大部分場景。

# 1. 新增一個 submodule
git submodule add https://github.com/some/shared-library.git SharedLibrary

# 2. Clone 一個包含 submodule 的專案
# 方法一:Clone 後再初始化
git clone https://github.com/my/project.git
cd project
git submodule update --init --recursive

# 方法二:Clone 時一併處理
git clone --recurse-submodules https://github.com/my/project.git

# 3. 更新 submodule 到遠端的最新版本
# 方法一:自動更新所有 submodule 到其追蹤分支的最新 commit
git submodule update --remote --merge

# 方法二:手動進入 submodule 更新
cd SharedLibrary
git pull origin main
cd ..
# 將 submodule 的變更加入主專案的 stage
git add SharedLibrary
git commit -m "Update SharedLibrary to latest"

# 4. 移除一個 submodule
# 步驟較為複雜,需要清理多個地方

# 4a..gitmodules 移除設定並 stage 變更
git submodule deinit -f SharedLibrary

# 4b. 從 Git 索引移除子模組目錄
git rm -rf SharedLibrary

# 4c. 提交變更
git commit -m "Remove SharedLibrary submodule"

# 4d. 清理 .git 目錄中的殘餘檔案(可選,但建議執行)
rm -rf .git/modules/SharedLibrary

進階操作:讓協作更順暢的技巧 

為了讓團隊合作更順利,特別是為了照顧對指令不熟悉的成員,可以採用以下技巧來簡化流程:

使用 Git GUI 工具 

如果你不習慣使用 CLI,許多 Git GUI 工具(如 Sourcetree、Fork、Tower 等)都內建了對 submodule 的支援。你可以透過圖形化介面來新增、更新或同步子模組,這可以大幅降低操作的複雜度,並減少人為失誤。

設定全域 Git Config 

為了避免每次 pull 主專案後都要手動更新 submodule,你可以設定一個全域的 Git 設定:

git config --global submodule.recurse true

設定之後,每當你執行 git pull 時,Git 會自動幫你更新 submodule 到主專案所記錄的 commit,省去了一個步驟。

建立輔助 Scripts 

對於團隊協作,建立一個簡單的 shell script 來標準化更新流程是個好方法。一個可靠的腳本應該包含 sync 和 update兩個步驟,確保 URL 和 commit 都保持最新狀態。例如,你可以建立一個 update_project.sh 腳本:

#!/bin/sh
# update_project.sh

# 1. 拉取主專案的最新變更
echo "Pulling latest changes for the main project..."
git pull

# 2. 同步 submodule 的 URL 設定,確保本地指向正確的遠端倉庫
echo "Syncing submodule URLs..."
git submodule sync --recursive

# 3. 初始化並更新 submodule 到主專案指定的 commit
echo "Updating and initializing submodules..."
git submodule update --init --recursive

# 4. 更新所有 submodule 到各自追蹤分支的最新版本(可選)
echo "Updating all submodules to latest commits..."
git submodule foreach 'git fetch origin && git pull origin main'

# 5. 將 submodule 的變更記錄到主專案
echo "Staging submodule updates..."
git add .
git status

echo "Project and submodules are up to date!"
echo "Don't forget to commit the submodule updates if needed."

團隊成員只需要執行這個腳本,就能確保專案和所有子模組都保持在正確的狀態,避免了因操作不一致導致的問題。

結論:我該用它嗎? 

那麼,回到最初的問題:我們應該在 iOS 專案中使用 git submodule 嗎?

我的建議是:除非你有非常明確的理由,否則優先選擇 SPM

SPM 作為 Apple 官方的套件管理工具,已經非常成熟,與 Xcode 深度整合,能滿足絕大多數 Swift/Objective-C 的依賴管理需求,無論是公開還是私有套件。它的學習曲線更平緩,協作流程也更簡單直觀。

git submodule 則是一個更底層、更強大的工具,它不受語言或生態系的限制,適用於一些特殊場景:

  • 跨平台或跨語言專案:當你需要管理的模組包含非 Swift/Objective-C 的程式碼(例如 C++, Python, 或網頁前端資源),submodule 是唯一可行的原生 Git 方案。
  • 緊密的同步開發:你需要對模組進行非常頻繁的修改,並且希望將其與主專案的開發流程緊密綁定。
  • 歷史專案結構:公司的專案結構有歷史因素,已經在使用 submodule

如果你決定使用 git submodule,請務必確保團隊中的每個人都理解其工作流程與潛在的陷阱,並建立清晰的協作規範。否則,它帶來的麻煩可能會遠大於便利。

希望這篇文章能幫助你更了解 git submodule,有任何疑問或想法也歡迎留言討論。

See ya!

留言
avatar-img
留言分享你的想法!
avatar-img
Rice把拔的生活與開發筆記
0會員
24內容數
這裡是我的開發與生活筆記。 分享iOS開發經驗、教學技巧,也記錄生活中的點滴與觀察。 偶爾來點評論,輕鬆聊聊技術與日常。
2025/08/22
新工作的頭兩週,我遭遇了8分鐘 build time 的巨型Legacy Objective-C專案。這個燙手山芋最後的結局會是....?
2025/08/22
新工作的頭兩週,我遭遇了8分鐘 build time 的巨型Legacy Objective-C專案。這個燙手山芋最後的結局會是....?
2025/07/31
SF Symbols 的出現,為我們提供了一套共享的「視覺語言」。降低了對設計方面的溝通成本,並提高了設計的一致性。
Thumbnail
2025/07/31
SF Symbols 的出現,為我們提供了一套共享的「視覺語言」。降低了對設計方面的溝通成本,並提高了設計的一致性。
Thumbnail
2025/07/16
Swift Forums 宣布 Android Workgroup 的計畫後,對 Mobile App Development 生態造成了什麼樣的變化?
Thumbnail
2025/07/16
Swift Forums 宣布 Android Workgroup 的計畫後,對 Mobile App Development 生態造成了什麼樣的變化?
Thumbnail
看更多
你可能也想看
Thumbnail
2025 vocus 推出最受矚目的活動之一——《開箱你的美好生活》,我們跟著創作者一起「開箱」各種故事、景點、餐廳、超值好物⋯⋯甚至那些讓人會心一笑的生活小廢物;這次活動不僅送出了許多獎勵,也反映了「內容有價」——創作不只是分享、紀錄,也能用各種不同形式變現、帶來實際收入。
Thumbnail
2025 vocus 推出最受矚目的活動之一——《開箱你的美好生活》,我們跟著創作者一起「開箱」各種故事、景點、餐廳、超值好物⋯⋯甚至那些讓人會心一笑的生活小廢物;這次活動不僅送出了許多獎勵,也反映了「內容有價」——創作不只是分享、紀錄,也能用各種不同形式變現、帶來實際收入。
Thumbnail
嗨!歡迎來到 vocus vocus 方格子是台灣最大的內容創作與知識變現平台,並且計畫持續拓展東南亞等等國際市場。我們致力於打造讓創作者能夠自由發表、累積影響力並獲得實質收益的創作生態圈!「創作至上」是我們的核心價值,我們致力於透過平台功能與服務,賦予創作者更多的可能。 vocus 平台匯聚了
Thumbnail
嗨!歡迎來到 vocus vocus 方格子是台灣最大的內容創作與知識變現平台,並且計畫持續拓展東南亞等等國際市場。我們致力於打造讓創作者能夠自由發表、累積影響力並獲得實質收益的創作生態圈!「創作至上」是我們的核心價值,我們致力於透過平台功能與服務,賦予創作者更多的可能。 vocus 平台匯聚了
Thumbnail
這篇文章將介紹工程師使用版控和git的相關知識和技能,包括版本控制的意義和git的基本指令,以及開發流程和webhook的概念。
Thumbnail
這篇文章將介紹工程師使用版控和git的相關知識和技能,包括版本控制的意義和git的基本指令,以及開發流程和webhook的概念。
Thumbnail
建立自己的Module有哪些好處?
Thumbnail
建立自己的Module有哪些好處?
Thumbnail
這篇文章描述了作者從兼職開發轉為全職開發的過程,並分享了從混進學界指日可待的積極態度。作者也提及自己在專案製作與個人生活上的矛盾與感想,最後分享了專案管理和敏捷開發相關的文章與影片。
Thumbnail
這篇文章描述了作者從兼職開發轉為全職開發的過程,並分享了從混進學界指日可待的積極態度。作者也提及自己在專案製作與個人生活上的矛盾與感想,最後分享了專案管理和敏捷開發相關的文章與影片。
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。
Thumbnail
本篇文章介紹如何使用Git Bash進行版本控制操作,包括創建repository、查看狀態、歷程以及加入暫存和提交暫存等操作。透過基本的Git指令,您可以更深入地瞭解Git工具的使用方法。
Thumbnail
本篇文章介紹如何使用Git Bash進行版本控制操作,包括創建repository、查看狀態、歷程以及加入暫存和提交暫存等操作。透過基本的Git指令,您可以更深入地瞭解Git工具的使用方法。
Thumbnail
這系列是我在 2023 六角學院 Vue作品實戰班的筆記,筆記以本人理解的方式記錄。此篇主題為 Slot Props 進階應用 ,其中包含單筆資料、多筆資料。
Thumbnail
這系列是我在 2023 六角學院 Vue作品實戰班的筆記,筆記以本人理解的方式記錄。此篇主題為 Slot Props 進階應用 ,其中包含單筆資料、多筆資料。
Thumbnail
在我剛開始寫程式的時候,深切地感受到要學的東西實在太多了,尤其在課堂上學的東西跟在公司要打造產品的技能非常的不一樣,有非常多需要自學的地方。 在我剛開始實習的時候,除了看書、看文章、用線上網站練習新語言的語法,我覺得幫助我最多的就是直接練習寫一個 Side project 了。
Thumbnail
在我剛開始寫程式的時候,深切地感受到要學的東西實在太多了,尤其在課堂上學的東西跟在公司要打造產品的技能非常的不一樣,有非常多需要自學的地方。 在我剛開始實習的時候,除了看書、看文章、用線上網站練習新語言的語法,我覺得幫助我最多的就是直接練習寫一個 Side project 了。
Thumbnail
最近在找資料的時候,偶然發現了兩個有趣的 git 指令:git commit --fixup 和 git rebase -i <sha> --autosquash。 研究了下發現對於像我這種每次 commit 都要斤斤計較,盡可能完美的人來說非常好用,因此寫一篇筆記記錄一下用法。
Thumbnail
最近在找資料的時候,偶然發現了兩個有趣的 git 指令:git commit --fixup 和 git rebase -i <sha> --autosquash。 研究了下發現對於像我這種每次 commit 都要斤斤計較,盡可能完美的人來說非常好用,因此寫一篇筆記記錄一下用法。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News