Git Submodule的使用

前言

在公司的專案使用Git進行管理,不過因為還使用了其他submodule,有時候在版本或branch之間切換後,沒有同步更新submodule就可能導致build fail,這邊紀錄一下相關指令與使用時機。

測試環境

在我的環境中建立三個目錄,分別代表遠端repository(remoteRepo)、專案目錄(Project),以及Submodule目錄。
要在local端建立空的repository,可以使用bare參數:

1
git init --bare <Repo名稱>

接著我們先在submodule目錄下進行git初始化,並且新增一個檔案:file_ver_1,代表submodule的第一版,最後將這個變更進行提交。

1
2
3
4
5
# ~/GitTest/Submodule
git init
touch file_ver_1
git add .
git commit -m 'submodule init commit'

然後在Project目錄中,將git初始化後,加入一個檔案:main_ver_1,並且使用submodule add將submodule加入專案中,最後當然也要將變更進行提交。
這邊我們也將完成的專案push到我們建立的repository中。

1
2
3
4
5
6
7
8
9
# ~/GitTest/Project
git init
touch main_ver_1
git submodule add ../Submodule/
git add .
git commit -m 'main init commit'

git remote add origin /Users/Chris/GitTest/remoteRepo
git push -u origin master

可以留意在我們加入submodule之後,git會自動幫我們產生一個隱藏檔:.gitmodules,裡面記錄的就是submodule的位置資訊,實務上這個url內容應該為其他遠端的repository,這邊只是為了方便,先用local的位置進行說明。

此時我們再回到submodule的目錄中,將檔案更新為:file_ver_2並提交,如此可以營造出”專案目錄中的submodule並非最新版”的情況。

1
2
3
4
# ~/GitTest/Submodule
mv file_ver_1 file_ver_2
git add .
git commit -m 'submodule version 2'

到這邊我們的測試環境已經準備好,整理一下內容如下圖,我們有一個專案(Project),包含一個版本1的submodule,而實際來源的submodule最新的版本為2。

flowchart TB
	subgraph Submodule
	file_ver_2
	end
	subgraph Project
	main_ver_1
		subgraph submodule
		file_ver_1
		end
	end

專案的clone

在clone專案的時候,若要連同submodule一起clone的話,可以加上recurse-submodules參數。

1
git clone --recurse-submodules /Users/Chris/GitTest/remoteRepo ./ProjectClone

可以看到clone下來的project會包含submodule,且內容為第1版。

更新所有submodule

此時,若想要將submodule更新到最新版,若單純在專案中執行git pull並不會更新submodule,需要自行進入submodule執行git pull。
不過可以透過foreach搭配recursive來自動完成這個工作。

1
2
# ~/GitTest/ProjectClone
git submodule foreach --recursive git pull origin master

這個指令會遞迴走訪所有submodule,並執行git pull。
可以看到執行完畢後,專案中的submodulde內容已經更新為版本2。

與專案同步所有submodule

若不幸發生將submodule更新到最新版之後,反而因為相容性之類的問題導致build fail的情況,可能會想要將專案中的submodule全部還原到原先clone下來的狀態。
更精準的來說,就是還原到主專案原先搭配的submodule版本,此時可以透過submodule update來達成。

1
2
# ~/GitTest/ProjectClone
git submodule update --recursive --init

可以看到submodule已經還原回原先的版本1。

值得注意的是訊息中顯示checked out的hash值,實際上可以對應到submodule commit ID,意思就是當你在專案中加入submodule時,其實git是紀錄了submodule對應的commit ID。

除了還原的狀況外,在專案管理者更新了submodule後,你想要同步自己local端的submodule時也可以使用這個指令進行submodule的更新。

專案更新submodule時

前面提到加入submodule時,其實是紀錄了commit ID,因此要留意的是,若你是專案管理者,當更新submodule之後也要重新進行commit/push,如此才能確保專案紀錄的是對的submodule版本。

1
2
3
4
# ~/GitTest/Project
git submodule foreach --recursive git pull origin master
git commit -a -m 'update submodule'
git push

更新後的submodule內容為版本2。

此時,其他clone專案的使用者,可以透過submodule update來同步更新submodule。

1
2
3
# ~/GitTest/ProjectClone
git pull
git submodule update --recursive --init

同步submodule位置

最後一種狀況是如果submodule更改了repository位置,此時可以透過submodule sync來更新git的config檔內容。

例如將submodule更改位置為Submodule_new_location:

1
2
# ~/GitTest
mv Submodule Submodule_new_location

更新project中的submodule path:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# .gitmodules
[submodule "Submodule"]
path = Submodule
url = ../Submodule_new_location/ # 修改url

# .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = /Users/Chris/GitTest/remoteRepo
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[submodule "Submodule"]
url = /Users/Chris/GitTest/Submodule_new_location/ # 修改url

# .git/modules/Submodule/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
worktree = ../../../Submodule
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = /Users/Chris/GitTest/Submodule_new_location/ # 修改url
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master

# ~/GitTest/Project
git commit -a -m 'update submodule path'
git push

此時若其他原先clone下來的project要進行submodule的git pull會找不到路徑:

透過git submodule sync指令可以自動更新config檔內的路徑。

1
2
git pull
git submodule sync

可以看到config檔內的路徑已經更新,此時再進行submodule的git pull就不會有問題。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# .git/modules/Submodule/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
worktree = ../../../Submodule
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = /Users/Chris/GitTest/Submodule_new_location/ # 已被更新為新的路徑
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master

Git Submodule的使用
https://chris-suo.github.io/ChrisComplete/2022/07/02/Git-Submodule-Update/
Author
Chris Suo
Posted on
July 2, 2022
Licensed under