Git Submodule 實踐

概述

什么是子模塊

有種情況我們經(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 文件一樣受到(通過)版本控制。 它會和該項目的其他部分一同被拉取推送纵势。 這
克隆該項目的人知道去哪獲得子模塊的原因踱阿。


screenshot_01.png

screenshot_02.png

克隆含有子模塊的項目

接下來我們將會克隆一個含有子模塊的項目。 當你在克隆這樣的項目時钦铁,默認會包含該子模塊目錄软舌,但其中還沒有任何文件:

$ 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 文件


screenshot_03.png

.gitmodules 文件中設(shè)置子模塊 path、url础锐、branch 信息


screenshot_04.png

Step 2

VSCode > Source Control 中可以看到“主項目”和“子模塊”嗓节,可確認子模塊分支或切換至其他分支。


screenshot_06.png

Step 3

安裝依賴之后執(zhí)行子模塊初始化命令 git submodule init && git submodule update --remote


screenshot_05.png

[圖片上傳中...(screenshot_07.png-37c03e-1586431824290-0)]

Step 4

子模塊目錄下出現(xiàn)對應文件


screenshot_07.png

Step 5

子模塊的變更記錄可以在子模塊的 Source Control 中確認

08

主項目中只標記子模塊有發(fā)生變化皆警,但并不顯示具體內(nèi)容


screenshot_08.png

Step 6

gitlab中會用特殊的圖標標記子模塊目錄拦宣,點擊可移動至對應項目的對應提交

10

其他

注意事項

初始狀態(tài)為該分支最近一次提交

11

需切換至對應分支

12

一些建議

  • 分支名以版本號命名,如 0.0.1
  • 為了可以正承判眨克隆項目鸵隧,開發(fā)者需要同時擁有主項目和子模塊的權(quán)限
  • 但是為了限制隨意修改代碼,只賦予不需要修改子模塊代碼的開發(fā)者reporter權(quán)限
  • 可單獨創(chuàng)建打包項目意推,發(fā)布npm包提供給第三方

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末豆瘫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子菊值,更是在濱河造成了極大的恐慌外驱,老刑警劉巖育灸,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異略步,居然都是意外死亡描扯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門趟薄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人典徊,你說我怎么就攤上這事杭煎。” “怎么了卒落?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵羡铲,是天一觀的道長。 經(jīng)常有香客問我儡毕,道長也切,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任腰湾,我火速辦了婚禮雷恃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘费坊。我一直安慰自己倒槐,他們只是感情好,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布附井。 她就那樣靜靜地躺著讨越,像睡著了一般。 火紅的嫁衣襯著肌膚如雪永毅。 梳的紋絲不亂的頭發(fā)上把跨,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音沼死,去河邊找鬼着逐。 笑死,一個胖子當著我的面吹牛漫雕,可吹牛的內(nèi)容都是我干的滨嘱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼浸间,長吁一口氣:“原來是場噩夢啊……” “哼太雨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起魁蒜,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤囊扳,失蹤者是張志新(化名)和其女友劉穎吩翻,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锥咸,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡狭瞎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了搏予。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熊锭。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖雪侥,靈堂內(nèi)的尸體忽然破棺而出碗殷,到底是詐尸還是另有隱情,我是刑警寧澤速缨,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布锌妻,位于F島的核電站,受9級特大地震影響旬牲,放射性物質(zhì)發(fā)生泄漏仿粹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧达址,春花似錦、人聲如沸毒涧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽契讲。三九已至,卻和暖如春滑频,著一層夾襖步出監(jiān)牢的瞬間捡偏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工峡迷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留银伟,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓绘搞,卻偏偏與公主長得像彤避,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子夯辖,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容