前言

Git 裡面有多強大但是使用時機沒這麼廣泛的功能,submodule (Git - Submodules) 就是其中一個。目前大多數的專案應該不會用到這個東西吧,最近也是因為部落格布景主題要換(又想換了 XD)所以才又重新再回憶這段指令。

What is submodule?

在軟體開發的過程中,有一些發展比較古老的大型專案,為了要將專案作更細緻的管理會將大型專案切分成數個小型專案,然後這些專案會交給不同的開發團對來開發,這樣做的話就可以讓一個 repo 瘦身,也可以讓多個團隊同時開發一個大型專案。而這些 submodule 又可以擁有各自的版控,但又不會跟主要的專案脫勾,在解耦的同時保有一定的連結。

還有另外一種使用 submodule 的情境,就是你的專案需要把別人的東西納入,而這依賴還不能透過套件管理程式 (比較常見的大概會是 Web 類型的專案,引入別人的樣式或是 template),需要將原始碼納入專案中,這時該怎麼處理?另外,假設對方還會更新這個東西的話,單純只把對方的原始碼 Copy 下來就顯的不切實際一點了。我們可以怎麼做呢?這個時候 submodule 就會是很好的解決方案。

新增一個 submodule

方式滿簡潔的,基本語法如下:

1
git submodule add <remote repository> <local path>

實際範例:

這邊假設你目前的位置就是主 repo 的根目錄。然後不需要先幫他建立空的目錄。

1
git submodule add https://github.com/chipzoller/hugo-clarity.git themes\hugo-clarity
1
2
3
4
5
6
7
8
9
🕙 {your prompt} ➜ git submodule add https://github.com/chipzoller/hugo-clarity.git themes/hugo-clarity
Cloning into 'D:/Repos/Tutorials/ElsaWorkflowTutorial/themes/hugo-clarity'...
remote: Enumerating objects: 5085, done.
remote: Counting objects: 100% (2/2), done.
remote: Compressing objects: 100% (2/2), done.
Receiving objects: 100% (5085/5085), 6.10 MiB | 12.88 MiB/s, done.

Resolving deltas: 100% (3126/3126), done.
warning: in the working copy of '.gitmodules', LF will be replaced by CRLF the next time Git touches it

從這邊的資訊可以發現所謂的 submodule add 就是會把別人的原始碼給抓下來,不過入版控的內容你會發現他其實只記錄了那個 repo 的 sha1。可以 diff 一下你會發現他的獨特之處

1
2
3
4
5
6
7
8
🕙 {your prompt} ➜ git diff --staged .\themes\hugo-clarity\
diff --git a/themes/hugo-clarity b/themes/hugo-clarity
new file mode 160000
index 0000000..5179510
--- /dev/null
+++ b/themes/hugo-clarity
@@ -0,0 +1 @@
+Subproject commit 5179510a03eac2aac855a4d9511ad3a589d3bc6c

若之前沒有加入任何 submodule 你會發現多了一個 .gitmodules 檔案裡面的內容也滿好懂得

1
2
3
[submodule "themes/hugo-clarity"]
	path = themes/hugo-clarity
	url = https://github.com/chipzoller/hugo-clarity.git

除了新增這個檔案之外你還可以在 .git 資料夾裡的 config 檔案中發現他也有紀錄你的 submodule 資訊,內容也跟 .gitmodules 差不多

1
2
3
[submodule "themes/hugo-clarity"]
	active = true
	url = https://github.com/chipzoller/hugo-clarity.git

最重要的就是他會指明你專案底下哪一個路徑是別的 repo ,這點滿重要的,因為 submodule 會把別人的原始碼抓下來,但是你會發現你的 git 不會把那些檔案納入版控,畢竟那不算是他主要追蹤的對象,不過他卻會記錄這個 submodule 你是吃它哪一個 commit。

新增完之後其實就可以把這個異動當成一般的異動 commit 掉。至此,你的 repo 已經順利完成新增 submodule 的動作了。

更新已註冊的 submodule

這個情境應該也是使用 submodule 後非常常見的指令,要更新 submodule 的話 git 有提供相對應的指令。

1
git submodule update --remote --merge

上面這個指令適用於,別人已經把主要 repo 的 submodule 給更新,而你要把這個異動同步回你自己的 repo。

若是你自己異動了 submodule 然後要更新主 repo 用的 submodule 版本的話,就先將 submodule 的異動推上 remote。這個時候回到主 repo 下 git status 就會發現 submodule 的 commit 有異動,這個時候就可以遵循一般異動的模式入版控。

1
2
3
git add .
git commit -m 'feat: update submodule'
git push

Clone 一個有 submodule 的 repo

這個情境在團隊使用時特別常見,或是你換個新電腦重新抓 repo 時也會用到。

主專案 clone 下來後應該會發現預設你不會把 submodule 裡的 repo 給一起抓下。

在 clone 主專案後於主專案的根目錄下執行以下指令

1
git submodule init
1
git submodule update

或是把這兩個command合併起來

1
git submodule update --init

好囉~這樣就會幫你把 submodule 的內容給抓下來了。若要確保 submodule 的內容是最新的話就依據上面寫的方式到 submodule 的資料夾用 pull 的方式更新。

如何移除 submodule?

移除 submodule 的方式比較手工一點,並沒有指令完整的支援,比較容易忘記一些手順。你需要先把 submodule 的內容給移除,這個部分他其實有提供指令。

基本語法:

1
git submodule deinit [-f|--force] (--all|[--] <path>…​)

範例:

這邊簡單暴力點做,所以直接 force + all 指令把所有的 submodule 給砍光

1
2
3
🕙 {your prompt} ❯ git submodule deinit -f --all
Cleared directory 'themes/hugo-clarity'
Submodule 'themes/hugo-clarity' (https://github.com/chipzoller/hugo-clarity.git) unregistered for path 'themes/hugo-clarity'

執行完後你會發現原本在 themes/hugo-clari 這資料夾底下的原始碼就被砍掉了,而 .gitmodule 檔案則是還留存著,不過 .git 裡的 config 的 submodule 資訊卻已經消失,所以 .gitmodule 裡的相關資訊要麻煩的手動刪除。做完這些後也是一樣把這些異動給 commit 掉就好。

Reference