Git系列4:Git submodule使用總結(jié)

面對(duì)比較復(fù)雜的項(xiàng)目,我們有可能會(huì)將代碼根據(jù)功能拆解成不同的子模塊。主項(xiàng)目對(duì)子模塊有依賴關(guān)系,卻又并不關(guān)心子模塊的內(nèi)部開發(fā)流程細(xì)節(jié)。
這種情況下,通常不會(huì)把所有源碼都放在同一個(gè) Git 倉(cāng)庫(kù)中。
有一種比較簡(jiǎn)單的方式,是在當(dāng)前工作目錄下,將子模塊文件夾加入到 .gitignore 文件內(nèi)容中,這樣主項(xiàng)目就能夠無(wú)視子項(xiàng)目的存在。這樣做有一個(gè)弊端就是,使用主項(xiàng)目的人需要有一個(gè)先驗(yàn)知識(shí):需要在當(dāng)前目錄下放置一份某版本的子模塊代碼。
還有另外一種方式可供借鑒,可以使用 Git 的 submodule 功能,這也是接下來(lái)準(zhǔn)總結(jié)的內(nèi)容。
實(shí)際上 Git 工具的 submodule 功能就是建立了當(dāng)前項(xiàng)目與子模塊之間的依賴關(guān)系:子模塊路徑、子模塊的遠(yuǎn)程倉(cāng)庫(kù)、子模塊的版本號(hào)。

使用流程

假定我們有兩個(gè)項(xiàng)目:project-main 和 project-sub-1,其中 project-main 表示主項(xiàng)目,而 project-sub-1 表示子模塊項(xiàng)目。
其中 project-main 的遠(yuǎn)程倉(cāng)庫(kù)地址為 https://github.com/username/project-main.git,而 project-sub-1 的遠(yuǎn)程倉(cāng)庫(kù)地址為 https://github.com/username/project-sub-1.git。
接下來(lái),我們希望在 project-main 中添加 project-sub-1 ,而又保持 project-sub-1 自身獨(dú)立的版本控制。

1.創(chuàng)建 submodule

使用 git submodule add <submodule_url> 命令可以在項(xiàng)目中創(chuàng)建一個(gè)子模塊。
如:

git submodule add https://github.com/xjh22222228/project-sub-1.git

此時(shí)項(xiàng)目倉(cāng)庫(kù)中會(huì)多出兩個(gè)文件:.gitmodules 和 project-sub-1 。
前者的內(nèi)容是這樣的,事實(shí)上就是子模塊的相關(guān)信息;而后者那個(gè)文件,實(shí)際上保存的是子模塊當(dāng)前版本的版本號(hào)信息。

[submodule "project-sub-1"]
path = project-sub-1
url = xxxxxxxx

事實(shí)上,此時(shí)在 .git/config 文件中也會(huì)多出一些信息,在 .git/modules 文件夾下也會(huì)多出一份內(nèi)容。
新建的子模塊,對(duì)應(yīng)的都是master分支,可以使用下面的指令,比如切換到test分支

git config -f .gitmodules submodule.common.branch test

添加完子模塊之后記得一定要進(jìn)行一次推送,這樣子模塊的更新才能更新的遠(yuǎn)程倉(cāng)庫(kù)中,使用 git commit -m "add submodule xxx" 提交一次,表示引入了某個(gè)子模塊。提交后,在主項(xiàng)目倉(cāng)庫(kù)中,會(huì)顯示出子模塊文件夾,并帶上其所在倉(cāng)庫(kù)的版本號(hào)。


image.png

2.獲取 submodule

上述步驟在創(chuàng)建子模塊的過(guò)程中,會(huì)自動(dòng)將相關(guān)代碼克隆到對(duì)應(yīng)路徑,但對(duì)于后續(xù)使用者而言,對(duì)于主項(xiàng)目使用普通的 clone 操作并不會(huì)拉取到子模塊中的實(shí)際代碼。
如果希望子模塊代碼也獲取到,一種方式是在克隆主項(xiàng)目的時(shí)候帶上參數(shù) --recurse-submodules,這樣會(huì)遞歸地將項(xiàng)目中所有子模塊的代碼拉取。
git clone xxxxxxx --recurse-submodules
此時(shí) project-main/project-sub-1 文件夾是有內(nèi)容的,并且固定在某個(gè) Git 提交的版本上。
另外一種可行的方式是,在當(dāng)前主項(xiàng)目中執(zhí)行:

git submodule init
git submodule update

則會(huì)根據(jù)主項(xiàng)目的配置信息,拉取更新子模塊中的代碼。
拉取指定子模塊的更新內(nèi)容,如子模塊project-sub-1

git submodule update --remote project-sub-1

切換分支或者克隆父項(xiàng)目后,所有的子模塊分支都指向 detached head, 為了修復(fù)這個(gè)問(wèn)題,直接在根項(xiàng)目執(zhí)行:

git submodule foreach -q --recursive 'git checkout $(git config -f $toplevel/.gitmodules submodule.$name.branch || echo master)'

執(zhí)行去除detached head操作指令后需要切換到我們需要的分支中,先修改gitmodules的文件分支記錄

git config -f .gitmodules submodule.common.branch test

3.子模塊內(nèi)容的更新

對(duì)于子模塊而言,并不需要知道引用自己的主項(xiàng)目的存在。對(duì)于自身來(lái)講,子模塊就是一個(gè)完整的 Git 倉(cāng)庫(kù),按照正常的 Git 代碼管理規(guī)范操作即可。
對(duì)于主項(xiàng)目而言,子模塊的內(nèi)容發(fā)生變動(dòng)時(shí),通常有三種情況:
(1)當(dāng)前項(xiàng)目下子模塊文件夾內(nèi)的內(nèi)容發(fā)生了未跟蹤的內(nèi)容變動(dòng);
(2)當(dāng)前項(xiàng)目下子模塊文件夾內(nèi)的內(nèi)容發(fā)生了版本變化;
(3)當(dāng)前項(xiàng)目下子模塊文件夾內(nèi)的內(nèi)容沒變,遠(yuǎn)程有更新;

情況1:子模塊有未跟蹤的內(nèi)容變動(dòng)

對(duì)于第1種情況,通常是在開發(fā)環(huán)境中,直接修改子模塊文件夾中的代碼導(dǎo)致的。
此時(shí)在主項(xiàng)目中使用 git status 能夠看到關(guān)于子模塊尚未暫存以備提交的變更,但是于主項(xiàng)目而言是無(wú)能為力的,使用 git add/commit 對(duì)其也不會(huì)產(chǎn)生影響。
在此情景下,通常需要進(jìn)入子模塊文件夾,按照子模塊內(nèi)部的版本控制體系提交代碼。
當(dāng)提交完成后,主項(xiàng)目的狀態(tài)則進(jìn)入了情況2,即當(dāng)前項(xiàng)目下子模塊文件夾內(nèi)的內(nèi)容發(fā)生了版本變化。

情況2:子模塊有版本變化
當(dāng)子模塊版本變化時(shí),在主項(xiàng)目中使用 git status 查看倉(cāng)庫(kù)狀態(tài)時(shí),會(huì)顯示子模塊有新的提交
在這種情況下,可以使用 git add/commit 將其添加到主項(xiàng)目的代碼提交中,實(shí)際的改動(dòng)就是那個(gè)子模塊 文件 所表示的版本信息,代碼示例:

-Subproject commit ace977071f94f4f88935f9bb9a33ac0f8b4ba935
+Subproject commit 7097c4887798b71cee360e99815f7dbd1aa17eb4

通常當(dāng)子項(xiàng)目更新后,主項(xiàng)目修改其所依賴的版本時(shí),會(huì)產(chǎn)生類似這種情景的 commit 提交信息。

情況3:子模塊遠(yuǎn)程有更新

通常來(lái)講,主項(xiàng)目與子模塊的開發(fā)不會(huì)恰好是同時(shí)進(jìn)行的。通常是子模塊負(fù)責(zé)維護(hù)自己的版本升級(jí)后,推送到遠(yuǎn)程倉(cāng)庫(kù),并告知主項(xiàng)目可以更新對(duì)子模塊的版本依賴。
在這種情況下,主項(xiàng)目是比較茫然的。
之前曾經(jīng)提到,主項(xiàng)目可以使用 git submodule update 更新子模塊的代碼,但那是指 當(dāng)前主項(xiàng)目文件夾下的子模塊目錄內(nèi)容 與 當(dāng)前主項(xiàng)目記錄的子模塊版本 不一致時(shí),會(huì)參考后者進(jìn)行更新。
但如今這種情況下,后者 當(dāng)前主項(xiàng)目記錄的子模塊版本 還沒有變化,在主項(xiàng)目看來(lái)當(dāng)前情況一切正常。
此時(shí),需要讓主項(xiàng)目主動(dòng)進(jìn)入子模塊拉取新版代碼,進(jìn)行升級(jí)操作。
通常流程是:

cd project-sub-1
git pull origin master

子模塊目錄下的代碼版本會(huì)發(fā)生變化,轉(zhuǎn)到情況2的流程進(jìn)行主項(xiàng)目的提交。
當(dāng)主項(xiàng)目的子項(xiàng)目特別多時(shí),可能會(huì)不太方便,此時(shí)可以使用 git submodule 的一個(gè)命令 foreach 執(zhí)行:

git submodule foreach 'git pull origin master'

情況匯總

終上所述,可知在不同場(chǎng)景下子模塊的更新方式如下:

  • 對(duì)于子模塊,只需要管理好自己的版本,并推送到遠(yuǎn)程分支即可;
  • 對(duì)于父模塊,若子模塊版本信息未提交,需要更新子模塊目錄下的代碼,并執(zhí)行 commit 操作提交子模塊版本信息;
  • 對(duì)于父模塊,若子模塊版本信息已提交,需要使用 git submodule update ,Git 會(huì)自動(dòng)根據(jù)子模塊版本信息更新所有子模塊目錄的相關(guān)代碼。

4.刪除子模塊

根據(jù)官方文檔的說(shuō)明,應(yīng)該使用 git submodule deinit 命令卸載一個(gè)子模塊。這個(gè)命令如果添加上參數(shù) --force,則子模塊工作區(qū)內(nèi)即使有本地的修改,也會(huì)被移除。

git submodule deinit project-sub-1
git rm project-sub-1

執(zhí)行 git submodule deinit project-sub-1 命令的實(shí)際效果,是自動(dòng)在 .git/config 中刪除了以下內(nèi)容:

[submodule "project-sub-1"]
url = xxxxxx

執(zhí)行 git rm project-sub-1 的效果,是移除了 project-sub-1 文件夾,并自動(dòng)在 .gitmodules 中刪除了以下內(nèi)容:

[submodule "project-sub-1"]
path = project-sub-1
url = xxxxxx

此時(shí),主項(xiàng)目中關(guān)于子模塊的信息基本已經(jīng)刪除(雖然貌似 .git/modules 目錄下還有殘余)
接下來(lái)可以提交代碼:

git commit -m "delete submodule project-sub-1"

如果項(xiàng)目移除掉原來(lái)的子模塊 可以去.git文件中刪除相關(guān)的配置文件,.git文件夾中的modules記錄的就是子模塊的信息

總結(jié)

當(dāng)項(xiàng)目比較復(fù)雜,部分代碼希望獨(dú)立為子模塊進(jìn)行版本控制時(shí),可以使用 git submodule 功能。
使用 git submodule 功能時(shí),主項(xiàng)目倉(cāng)庫(kù)并不會(huì)包含子模塊的文件,只會(huì)保留一份子模塊的配置信息及版本信息,作為主項(xiàng)目版本管理的一部分。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容