概述
什么是子模塊
有種情況我們經(jīng)常會遇到:某個工作中的項目需要包含并使用另一個項目。 也許是第三方庫,或者你獨立開發(fā)的滔灶,用于多個父項目的庫送膳。 現(xiàn)在問題來了:你想要把它們當做兩個獨立的項目员魏,同時又想在一個項目中使用另一個。
我們舉一個例子叠聋。 假設(shè)你正在開發(fā)一個網(wǎng)站然后創(chuàng)建了 Atom 訂閱撕阎。 你決定使用一個庫,而不是寫自己的 Atom 生成代碼碌补。 你可能不得不通過 CPAN 安裝或 Ruby gem 來包含共享庫中的代碼虏束,或者將源代碼直接拷貝到自己的項目中。 如果將這個庫包含進來厦章,那么無論用何種方式都很難定制它镇匀,部署則更加困難,因為你必須確保每一個客戶端都包含該庫袜啃。 如果將代碼復制到自己的項目中汗侵,那么你做的任何自定義修改都會使合并上游的改動變得困難。
Git 通過子模塊來解決這個問題。 子模塊允許你將一個 Git 倉庫作為另一個 Git 倉庫的子目錄晰韵。 它能讓你將另一個倉庫克隆到自己的項目中发乔,同時還保持提交的獨立。
應用場景
- 適用于多個工程存在大量重復代碼或邏輯的場景
- 開發(fā)中的依賴庫
特點
- 如果是使用成熟的雪猪、長期不迭代的庫的話栏尚,和引用npm包沒有區(qū)別
- 但對于處在開發(fā)階段的庫來說,省去了頻繁發(fā)版的麻煩
- 可以不用考慮編譯邏輯
- 使用typescript的項目可以不用考慮 d.ts 文件
- 對于使用方來說省去了頻繁更新依賴的麻煩
- 可以直接查看源碼
開始使用子模塊
我們將要演示如何在一個被分成一個主項目與幾個子項目的項目上開發(fā)浪蹂。
我們首先將一個已存在的 Git 倉庫添加為正在工作的倉庫的子模塊抵栈。 你可以通過在 git submodule add 命令后面加上想要跟蹤的項目的相對或絕對 URL 來添加新的子模塊。 在本例中坤次,我們將會添加一個名為 “DbConnector” 的庫古劲。
$ git submodule add https://github.com/chaconinc/DbConnector
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
默認情況下,子模塊會將子項目放到一個與倉庫同名的目錄中缰猴,本例中是 “DbConnector”产艾。 如果你想要放到其他地方,那么可以在命令結(jié)尾添加一個不同的路徑滑绒。
.gitmodules文件
首先應當注意到新的 .gitmodules 文件闷堡。 該配置文件保存了項目 URL 與已經(jīng)拉取的本地目錄之間的映射:
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
如果有多個子模塊,該文件中就會有多條記錄疑故。 要重點注意的是杠览,該文件也像 .gitignore 文件一樣受到(通過)版本控制。 它會和該項目的其他部分一同被拉取推送纵势。 這
克隆該項目的人知道去哪獲得子模塊的原因踱阿。
克隆含有子模塊的項目
接下來我們將會克隆一個含有子模塊的項目。 當你在克隆這樣的項目時钦铁,默認會包含該子模塊目錄软舌,但其中還沒有任何文件:
$ git clone https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
$ cd MainProject
$ ls -la
total 16
drwxr-xr-x 9 schacon staff 306 Sep 17 15:21 .
drwxr-xr-x 7 schacon staff 238 Sep 17 15:21 ..
drwxr-xr-x 13 schacon staff 442 Sep 17 15:21 .git
-rw-r--r-- 1 schacon staff 92 Sep 17 15:21 .gitmodules
drwxr-xr-x 2 schacon staff 68 Sep 17 15:21 DbConnector
-rw-r--r-- 1 schacon staff 756 Sep 17 15:21 Makefile
drwxr-xr-x 3 schacon staff 102 Sep 17 15:21 includes
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 scripts
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 src
$ cd DbConnector/
$ ls
其中有 DbConnector 目錄,不過是空的牛曹。 你必須運行兩個命令:git submodule init 用來初始化本地配置文件佛点,而 git submodule update 則從該項目中抓取所有數(shù)據(jù)并檢出父項目中列出的合適的提交。
$ git submodule init
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
$ git submodule update
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'
現(xiàn)在 DbConnector 子目錄是處在和之前提交時相同的狀態(tài)了黎比。
如果你不想在子目錄中手動抓取與合并超营,那么還有種更容易的方式。 運行 git submodule update --remote阅虫,Git 將會進入子模塊然后抓取并更新糟描。
$ git submodule update --remote DbConnector
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
3f19983..d0354fc master -> origin/master
Submodule path 'DbConnector': checked out 'd0354fc054692d3906c85c3af05ddce39a1c0644'
克隆 + 初始化
不過還有更簡單一點的方式。 如果給 git clone 命令傳遞 --recurse-submodules 選項书妻,它就會自動初始化并更新倉庫中的每一個子模塊, 包括可能存在的嵌套子模塊。
$ git clone --recurse-submodules https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'
拓展
發(fā)布子模塊改動
如果我們在主項目中提交并推送但并不推送子模塊上的改動躲履,其他嘗試檢出我們修改的人會遇到麻煩见间, 因為他們無法得到依賴的子模塊改動。那些改動只存在于我們本地的拷貝中工猜。
為了確保這不會發(fā)生米诉,你可以讓 Git 在推送到主項目前檢查所有子模塊是否已推送。 git push 命令接受可以設(shè)置為 “check” 或 “on-demand” 的 --recurse-submodules 參數(shù)篷帅。 如果任何提交的子模塊改動沒有推送那么 “check” 選項會直接使 push 操作失敗史侣。
$ git push --recurse-submodules=check
The following submodule paths contain changes that can
not be found on any remote:
DbConnector
Please try
git push --recurse-submodules=on-demand
or cd to the path and use
git push
to push them to a remote.
如你所見,它也給我們了一些有用的建議魏身,指導接下來該如何做惊橱。 最簡單的選項是進入每一個子模塊中然后手動推送到遠程倉庫,確保它們能被外部訪問到箭昵,之后再次嘗試這次推送税朴。 如果你想要對所有推送都執(zhí)行檢查,那么可以通過設(shè)置 git config push.recurseSubmodules check 讓它成為默認行為家制。
另一個選項是使用 “on-demand” 值正林,它會嘗試為你這樣做。
$ git push --recurse-submodules=on-demand
Pushing submodule 'DbConnector'
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 3), reused 0 (delta 0)
To https://github.com/chaconinc/DbConnector
c75e92a..82d2ad3 stable -> stable
Counting objects: 2, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 266 bytes | 0 bytes/s, done.
Total 2 (delta 1), reused 0 (delta 0)
To https://github.com/chaconinc/MainProject
3d6d338..9a377d1 master -> master
如你所見颤殴,Git 進入到 DbConnector 模塊中然后在推送主項目前推送了它觅廓。 如果那個子模塊因為某些原因推送失敗,主項目也會推送失敗涵但。
子模塊遍歷
有一個 foreach 子模塊命令杈绸,它能在每一個子模塊中運行任意命令。 如果項目中包含了大量子模塊贤笆,這會非常有用蝇棉。
例如,假設(shè)我們想要開始開發(fā)一項新功能或者修復一些錯誤芥永,并且需要在幾個子模塊內(nèi)工作篡殷。 我們可以輕松地保存所有子模塊的工作進度。
$ git submodule foreach 'git stash'
Entering 'CryptoLibrary'
No local changes to save
Entering 'DbConnector'
Saved working directory and index state WIP on stable: 82d2ad3 Merge from origin/stable
HEAD is now at 82d2ad3 Merge from origin/stable
然后我們可以創(chuàng)建一個新分支埋涧,并將所有子模塊都切換過去板辽。
$ git submodule foreach 'git checkout -b featureA'
Entering 'CryptoLibrary'
Switched to a new branch 'featureA'
Entering 'DbConnector'
Switched to a new branch 'featureA'
有用的別名
你可能想為其中一些命令設(shè)置別名,因為它們可能會非常長而你又不能設(shè)置選項作為它們的默認選項棘催。 我們在 Git 別名 介紹了設(shè)置 Git 別名劲弦, 但是如果你計劃在 Git 中大量使用子模塊的話,這里有一些例子醇坝。
$ git config alias.sdiff '!'"git diff && git submodule foreach 'git diff'"
$ git config alias.spush 'push --recurse-submodules=on-demand'
$ git config alias.supdate 'submodule update --remote --merge'
這樣當你想要更新子模塊時可以簡單地運行 git supdate邑跪,或 git spush 檢查子模塊依賴后推送。
從子目錄切換到子模塊
另一個主要的告誡是許多人遇到了將子目錄轉(zhuǎn)換為子模塊的問題。 如果你在項目中已經(jīng)跟蹤了一些文件画畅,然后想要將它們移動到一個子模塊中砸琅,那么請務(wù)必小心,否則 Git 會對你發(fā)脾氣轴踱。 假設(shè)項目內(nèi)有一些文件在子目錄中症脂,你想要將其轉(zhuǎn)換為一個子模塊。 如果刪除子目錄然后運行 submodule add淫僻,Git 會朝你大喊:
$ rm -Rf CryptoLibrary/
$ git submodule add https://github.com/chaconinc/CryptoLibrary
'CryptoLibrary' already exists in the index
你必須要先取消暫存 CryptoLibrary 目錄诱篷。 然后才可以添加子模塊:
$ git rm -r CryptoLibrary
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
現(xiàn)在假設(shè)你在一個分支下做了這樣的工作。 如果嘗試切換回的分支中那些文件還在子目錄而非子模塊中時——你會得到這個錯誤:
$ git checkout master
error: The following untracked working tree files would be overwritten by checkout:
CryptoLibrary/Makefile
CryptoLibrary/includes/crypto.h
...
Please move or remove them before you can switch branches.
Aborting
你可以通過 checkout -f 來強制切換雳灵,但是要小心棕所,如果其中還有未保存的修改,這個命令會把它們覆蓋掉细办。
$ git checkout -f master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
當你切換回來之后橙凳,因為某些原因你得到了一個空的 CryptoLibrary 目錄,并且 git submodule update 也無法修復它笑撞。 你需要進入到子模塊目錄中運行 git checkout . 來找回所有的文件岛啸。 你也可以通過 submodule foreach 腳本來為多個子模塊運行它。
要特別注意的是茴肥,近來子模塊會將它們的所有 Git 數(shù)據(jù)保存在頂級項目的 .git 目錄中坚踩,所以不像舊版本的 Git,摧毀一個子模塊目錄并不會丟失任何提交或分支瓤狐。
刪除子模塊
# 逆初始化模塊瞬铸,執(zhí)行后可發(fā)現(xiàn)模塊目錄被清空
$ git submodule deinit <name>
# 清除暫存
$ git rm --cached <name>
# 查詢暫存區(qū)當前的內(nèi)容
$ git ls-files --stage | grep 160000
# 刪除目錄
$ rm -rf <name>
# 刪除項目中子模塊設(shè)置
$ vi .git/config 刪掉submodule信息
$ rm -rf .git/modules/<name>
實踐
Step 1
根目錄下創(chuàng)建 .gitmodules 文件
.gitmodules 文件中設(shè)置子模塊 path、url础锐、branch 信息
Step 2
VSCode > Source Control 中可以看到“主項目”和“子模塊”嗓节,可確認子模塊分支或切換至其他分支。
Step 3
安裝依賴之后執(zhí)行子模塊初始化命令 git submodule init && git submodule update --remote
[圖片上傳中...(screenshot_07.png-37c03e-1586431824290-0)]
Step 4
子模塊目錄下出現(xiàn)對應文件
Step 5
子模塊的變更記錄可以在子模塊的 Source Control 中確認
主項目中只標記子模塊有發(fā)生變化皆警,但并不顯示具體內(nèi)容
Step 6
gitlab中會用特殊的圖標標記子模塊目錄拦宣,點擊可移動至對應項目的對應提交
其他
注意事項
初始狀態(tài)為該分支最近一次提交
需切換至對應分支
一些建議
- 分支名以版本號命名,如 0.0.1
- 為了可以正承判眨克隆項目鸵隧,開發(fā)者需要同時擁有主項目和子模塊的權(quán)限
- 但是為了限制隨意修改代碼,只賦予不需要修改子模塊代碼的開發(fā)者reporter權(quán)限
- 可單獨創(chuàng)建打包項目意推,發(fā)布npm包提供給第三方