如何使用子模塊和子樹來管理 Git 項(xiàng)目
使用子模塊和子樹來幫助你管理多個(gè)存儲(chǔ)庫中共有的子項(xiàng)目。
如果你參與了開源項(xiàng)目的開發(fā),那么你很可能已經(jīng)用了 Git 來管理你的源碼。你可能遇到過有很多依賴和/或子項(xiàng)目的項(xiàng)目。你是如何管理它們的?
對(duì)于一個(gè)開源組織,要實(shí)現(xiàn)社區(qū)和產(chǎn)品的單一來源文檔和依賴管理比較棘手。文檔和項(xiàng)目往往會(huì)碎片化和變得冗余,這致使它們很難維護(hù)。
必要性
假設(shè)你想把單個(gè)項(xiàng)目作為一個(gè)存儲(chǔ)庫內(nèi)的子項(xiàng)目,傳統(tǒng)的方法是把該項(xiàng)目復(fù)制到父存儲(chǔ)庫中,但是,如果你想要在多個(gè)父項(xiàng)目中使用同一個(gè)子項(xiàng)目呢?如果把子項(xiàng)目復(fù)制到所有父項(xiàng)目中,當(dāng)有更新時(shí),你都要在每個(gè)父項(xiàng)目中做修改,這是不太可行的。這會(huì)導(dǎo)致父項(xiàng)目中的冗余和數(shù)據(jù)不一致,使更新和維護(hù)子項(xiàng)目變得很困難。
Git 子模塊和子樹
如果你可以用一條命令把一個(gè)項(xiàng)目放進(jìn)另一個(gè)項(xiàng)目中,會(huì)怎樣呢?如果你隨時(shí)可以把一個(gè)項(xiàng)目作為子項(xiàng)目添加到任意數(shù)目的項(xiàng)目中,并可以同步更新修改呢?Git 提供了這類問題的解決方案:Git 子模塊和 Git 子樹。創(chuàng)建這些工具的目的是以更加模塊化的水平來支持共用代碼的開發(fā)工作流,旨在 Git 存儲(chǔ)庫源碼管理(SCM)與它下面的子樹之間架起一座橋梁。
生長在桑樹上的櫻桃樹
下面是本文要詳細(xì)介紹的概念的一個(gè)真實(shí)應(yīng)用場景。如果你已經(jīng)很熟悉樹形結(jié)構(gòu),這個(gè)模型看起來是下面這樣:
Tree with subtrees
Git 子模塊是什么?
Git 在它默認(rèn)的包中提供了子模塊,子模塊可以把 Git 存儲(chǔ)庫嵌入到其他存儲(chǔ)庫中。確切地說,Git 子模塊指向子樹中的某次提交。下面是我 Docs-test GitHub 存儲(chǔ)庫中的 Git 子模塊的樣子:
Git submodules screenshot
文件夾@提交 Id 格式表明這個(gè)存儲(chǔ)庫是一個(gè)子模塊,你可以直接點(diǎn)擊文件夾進(jìn)入該子樹。名為 .gitmodules
的配置文件包含所有子模塊存儲(chǔ)庫的詳細(xì)信息。我的存儲(chǔ)庫的 .gitmodules
文件如下:
Screenshot of .gitmodules file
你可以用下面的命令在你的存儲(chǔ)庫中使用 Git 子模塊:
克隆一個(gè)存儲(chǔ)庫并加載子模塊
克隆一個(gè)含有子模塊的存儲(chǔ)庫:
$ git clone --recursive <URL to Git repo>
如果你之前已經(jīng)克隆了存儲(chǔ)庫,現(xiàn)在想加載它的子模塊:
$ git submodule update --init
如果有嵌套的子模塊:
$ git submodule update --init --recursive
下載子模塊
串行地連續(xù)下載多個(gè)子模塊是很枯燥的工作,所以 clone
和 submodule update
會(huì)支持 --jobs
(或 -j
)參數(shù):
例如,想一次下載 8 個(gè)子模塊,使用:
$ git submodule update --init --recursive -j 8
$ git clone --recursive --jobs 8 <URL to Git repo>
拉取子模塊
在運(yùn)行或構(gòu)建父項(xiàng)目之前,你需要確保依賴的子項(xiàng)目都是最新的。
拉取子模塊的所有修改:
$ git submodule update --remote
使用子模塊創(chuàng)建存儲(chǔ)庫:
向一個(gè)父存儲(chǔ)庫添加子樹:
$ git submodule add <URL to Git repo>
初始化一個(gè)已存在的 Git 子模塊:
$ git submodule init
你也可以通過為 submodule update
命令添加 --update
參數(shù)在子模塊中創(chuàng)建分支和追蹤提交:
$ git submodule update --remote
更新子模塊的提交
上面提到過,一個(gè)子模塊就是一個(gè)指向子樹中某次提交的鏈接。如果你想更新子模塊的提交,不要擔(dān)心。你不需要顯式地指定最新的提交。你只需要使用通用的 submodule update
命令:
$ git submodule update
就像你平時(shí)創(chuàng)建父存儲(chǔ)庫和把父存儲(chǔ)庫推送到 GitHub 那樣添加和提交就可以了。
從一個(gè)父存儲(chǔ)庫中刪除一個(gè)子模塊
僅僅手動(dòng)刪除一個(gè)子項(xiàng)目文件夾不會(huì)從父項(xiàng)目中移除這個(gè)子項(xiàng)目。想要?jiǎng)h除名為 childmodule
的子模塊,使用:
$ git rm -f childmodule
雖然 Git 子模塊看起來很容易上手,但是對(duì)于初學(xué)者來說,有一定的使用門檻。
Git 子樹是什么?
Git 子樹,是在 Git 1.7.11 引入的,讓你可以把任何存儲(chǔ)庫的副本作為子目錄嵌入另一個(gè)存儲(chǔ)庫中。它是 Git 項(xiàng)目可以注入和管理項(xiàng)目依賴的幾種方法之一。它在常規(guī)的提交中保存了外部依賴信息。Git 子樹提供了整潔的集成點(diǎn),因此很容易復(fù)原它們。
如果你參考 GitHub 提供的子樹教程來使用子樹,那么無論你什么時(shí)候添加子樹,在本地都不會(huì)看到 .gittrees
配置文件。這讓我們很難分辨哪個(gè)是子樹,因?yàn)樗鼈兛雌饋砗芟衿胀ǖ奈募A,但是它們卻是子樹的副本。默認(rèn)的 Git 包中不提供帶 .gittrees
配置文件的 Git 子樹版本,因此如果你想要帶 .gittrees
配置文件的 git-subtree 命令,必須從 Git 源碼存儲(chǔ)庫的 /contrib/subtree 文件夾 下載 git-subtree。
你可以像克隆其他常規(guī)的存儲(chǔ)庫那樣克隆任何含有子樹的存儲(chǔ)庫,但由于在父存儲(chǔ)庫中有整個(gè)子樹的副本,因此克隆過程可能會(huì)持續(xù)很長時(shí)間。
你可以用下面的命令在你的存儲(chǔ)庫中使用 Git 子樹。
向父存儲(chǔ)庫中添加一個(gè)子樹
想要向父存儲(chǔ)庫中添加一個(gè)子樹,首先你需要執(zhí)行 remote add
,之后執(zhí)行 subtree add
命令:
$ git remote add remote-name <URL to Git repo>
$ git subtree add --prefix=folder/ remote-name <URL to Git repo> subtree-branchname
上面的命令會(huì)把整個(gè)子項(xiàng)目的提交歷史合并到父存儲(chǔ)庫。
向子樹推送修改以及從子樹拉取修改
$ git subtree push-all
或者
$ git subtree pull-all
你應(yīng)該使用哪個(gè)?
任何工具都有優(yōu)缺點(diǎn)。下面是一些可能會(huì)幫助你決定哪種最適合你的特性:
- Git 子模塊的存儲(chǔ)庫占用空間更小,因?yàn)樗鼈冎皇侵赶蜃禹?xiàng)目的某次提交的鏈接,而 Git 子樹保存了整個(gè)子項(xiàng)目及其提交歷史。
- Git 子模塊需要在服務(wù)器中可訪問,但子樹是去中心化的。
- Git 子模塊大量用于基于組件的開發(fā),而 Git 子樹多用于基于系統(tǒng)的開發(fā)。
Git 子樹并不是 Git 子模塊的直接可替代項(xiàng)。有明確的說明來指導(dǎo)我們?cè)撌褂媚姆N。如果有一個(gè)歸屬于你的外部存儲(chǔ)庫,使用場景是向它回推代碼,那么就使用 Git 子模塊,因?yàn)橥扑痛a更容易。如果你有第三方代碼,且不會(huì)向它推送代碼,那么使用 Git 子樹,因?yàn)槔〈a更容易。