(文中提到的倉庫特指git倉庫)
1. 背景介紹
開發(fā)中可能會遇到這樣的情況:
- 項(xiàng)目依賴一個(gè)library
- 這個(gè)library在多個(gè)項(xiàng)目中都要用到(符合封裝復(fù)用的原則)
- 我需要根據(jù)需求修改library的內(nèi)容
- 不同項(xiàng)目依賴的library可能是不同版本
如果把library的文件直接放在當(dāng)前項(xiàng)目的倉庫中,會導(dǎo)致一個(gè)問題:不同項(xiàng)目同步library的更新會變成一場災(zāi)難(耗費(fèi)大量時(shí)間且易出錯(cuò))。
于是,submodule 誕生了:
submodule提供了一種能力:在一個(gè)倉庫中,允許以“子目錄”的形式存放另一個(gè)倉庫,同時(shí)兩個(gè)倉庫獨(dú)立提交。
(“子目錄”打引號是因?yàn)椴⒉皇钦嬲淖幽夸?
回到上面的需求:
- library作為單獨(dú)的倉庫以submodule的形式引入到不同的項(xiàng)目中
- 一個(gè)項(xiàng)目更新了library,其他項(xiàng)目可以方便地更新
- 不同的項(xiàng)目可以方便地維護(hù)不同的library版本
...
2. 各個(gè)場景的用法
2.1 給倉庫添加submodule
git submodule add url moduleName
2.2 clone一個(gè)有submodule的倉庫
2.2.1常規(guī)操作
如果clone的倉庫有submodule,是不能直接使用的,需要運(yùn)行以下兩條命令來初始化。
git submodule init
git submodule update
2.2.2 submodule中嵌套了submodule
上面的操作不會初始化嵌套的submodule,如果遇到嵌套的情況,你當(dāng)然可以cd到對應(yīng)目錄,再次執(zhí)行g(shù)it submodule init和git submodule update,還有一個(gè)便捷操作:
git submodule update --init --recursive
2.3 更新遠(yuǎn)程submodule
submodule會經(jīng)常更新, update 是submodule非常重要的操作,它的作用是:
Update the registered submodules to match what the superproject expects by cloning missing submodules, fetching missing commits in submodules and updating the working tree of the submodules.
可見,根據(jù)命令的參數(shù),update的功能非常豐富:
- clone本地缺失的submodule
- fetch(submodule中)本地缺失的commits
- 更新工作空間(就是當(dāng)前目錄中看到的代碼)
更新遠(yuǎn)程submodule的基礎(chǔ)操作是
git submodule update --remote
以下是各種場景的用法
2.3.1 遠(yuǎn)程添加了新的submodule
git submodule update --init --remote
2.3.2 遠(yuǎn)程submodule有更新,本地沒更新
git submodule update --remote
2.3.3 遠(yuǎn)程submodule有更新,本地也有更新
現(xiàn)在是這篇文章最重要的部分,因?yàn)檫@個(gè)場景是submodule常見場景中最讓人困惑的,如果你直接按照上面的操作(git submodule update --remote), 會發(fā)現(xiàn)本地的提交不見了。
想要解決這個(gè)問題,就要知道git submodule update --remote到底做了什么:
- 對于每一個(gè)submodule, git 會fetch其遠(yuǎn)程master分支(*可配置)的最新commit。
- 拉取到新的commit后,checkout到最新的commit(此時(shí)HEAD指針未指向任何分支,即submodule處于游離狀態(tài)-detached)
綜上,默認(rèn)狀態(tài)下,所有的submodule都處于游離狀態(tài), 新增本地提交后也是游離狀態(tài),這時(shí)執(zhí)行g(shù)it submodule update --remote,submodule被checkout到遠(yuǎn)程master分支(*可配置)最新的commit, 于是本地的提交從工作空間中消失了(如下圖)。
了解到原因,就知道如何解決更新submodule時(shí)的游離問題以及本地修改丟失的問題:
(1) 給submodule 切分支
cd到submodule的目錄下,手動checkout到具體的分支下
(2) update時(shí)添加 --merge或--rebase
git submodule update --remote --merge
Tip1: 關(guān)于配置submodule的分支
操作git submodule update --remote fetch的分支是可以配置的(默認(rèn)是master),配置方式是:
git config .gitmodules submodule.<submoduleName>.branch <branch>
也可以改直接改.gitmodules這個(gè)配置文件
# in file .gitmodules
[submodule "module/D"]
path = module/D
url = git@xxx.git
branch = test//改成對應(yīng)的分支名
Tip2: 別慌,你的東西丟不了
剛接觸submodule的時(shí)候,很多朋友看到自己的本地提交丟失就慌了,因?yàn)楦鱾€(gè)記錄中好像都找不到剛才的內(nèi)容。這里介紹一個(gè)利器: reflog(顧名思義: ref的log,因?yàn)間it中的HEAD以及分支都是ref指針,我們通過插件HEAD的log就能知道剛剛“丟失”的commit)
# cd to submodule directory
git reflog HEAD
3. 一些好用的設(shè)置項(xiàng)
3.1 push倉庫時(shí),確保修改的submodule已經(jīng)push
團(tuán)隊(duì)協(xié)作時(shí),有一種很常見的錯(cuò)誤,自己提交倉庫時(shí),忘了提交submodule的commit,導(dǎo)致同事無法更新到submodule最新的內(nèi)容(因?yàn)樗辉谀愕谋镜貍}庫)。為了避免這種錯(cuò)誤,可以在push時(shí)添加git push --recurse-submodules=check幫助檢查:
git push --recurse-submodules=check
或者修改配置,一勞永逸。
git config push.recurseSubmodules check
3.2 更好看的設(shè)置項(xiàng)
默認(rèn)的設(shè)置項(xiàng),看不到submodule修改的內(nèi)容,可以修改配置:
git config status.submodulesummary 1
3.3 這些命令也太長了吧
上文中最重要的命令應(yīng)該是git submodule update --remote
了,本來就挺長的,在加一個(gè)--merge
或--rebase
就更長了,這也太長了吧,手都敲酸了。。。別急,git alias
(git別名機(jī)制)來了:
git config alias.supdate 'submodule update --remote --merge'
這樣設(shè)置后就可以用git supdate
代替git submodule update --remote --merge
操作了。
4. 一些需要注意的東西
4.1 對于模型的理解
- 雖然工作空間中看到的submodule是一個(gè)個(gè)子目錄,但submodule不是以子目錄的形式存放在主倉庫中的(這也是前文“子目錄”加引號的原因),這一點(diǎn)比較違反直覺。
- 在底層的存儲上,所有的submodule也是存在主倉庫的.git目錄中。