Git應用詳解第十講:Git子庫:submodule與subtree

第十講:Git子庫:submodule與subtree

前言
一箇中大型項目每每會依賴幾個模塊,git提供了子庫的概念。能夠將這些子模塊存放在不一樣的倉庫中,經過submodule或subtree實現倉庫的嵌套。
Git應用詳解第十講:Git子庫:submodule與subtreegit

1、
submodule

submodule:子模塊的意思,表示將一個版本庫做爲子庫引入到另外一個版本庫中:
Git應用詳解第十講:Git子庫:submodule與subtreegithub

image-20200408224205125
1.引入子庫
須要使用以下命令:ide

git submodule add 子庫地址 保存目錄
好比:url

git submodule add git@github.com:AhuntSun/git_child.git mymodule

執行上述命令會將地址對應的遠程倉庫做爲子庫,保存到當前版本庫的mymodule目錄下:
Git應用詳解第十講:Git子庫:submodule與subtree3d

隨後查看當前版本庫的狀態:
Git應用詳解第十講:Git子庫:submodule與subtree指針

image-20200329203048016
能夠發現新增了兩個文件。查看其中的.gitmodules文件:
Git應用詳解第十講:Git子庫:submodule與subtree日誌

image-20200329203507411
能夠看到當前文件的路徑和子模塊的url,隨後將這兩個新增文件「添加」、「提交」並「推送」。在當前倉庫git_parent對應的遠程倉庫中多出了兩個文件:
Git應用詳解第十講:Git子庫:submodule與subtreecode

image-20200329203746236
其中mymodule文件夾上的3bd7f76 對應的是「子倉庫」git_child中的「最新提交」:
Git應用詳解第十講:Git子庫:submodule與subtreeserver

image-20200329203905051
點擊mymodule文件夾,會自動跳轉到「子倉庫」中:
Git應用詳解第十講:Git子庫:submodule與subtreeblog

image-20200329203957392
經過上述分析,能夠得出結論:兩個倉庫已經關聯起來了,而且倉庫git_child爲倉庫git_parent的子倉庫;

2.同步子庫變化
「當被依賴的子版本庫發生變化時」:在子版本庫git_child中新增文件world.txt並提交到遠程倉庫:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200329204252524
這個時候依賴它的父版本庫git_parent要如何感知這一變化呢?

方法一
這個時候git_parent只須要進入存放子庫git_child的目錄mymodule,執行git pull就能將子版本庫git_child的更新拉取到本地:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330102106961
方法二
當父版本庫git_parent依賴的「多個子版本庫」都發生變化時,能夠採用以下方法遍歷更新全部子庫:首先回到版本庫主目錄,執行如下指令:

git submodule foreach git pull

該命令會「遍歷」當前版本庫所依賴的「全部」子版本庫,並將它們的更新「拉取」到父版本庫git_parent:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330102642607
拉取完成後,查看狀態,發現mymodule目錄下文件發生了變化,因此須要執行一次添加、提交、推送操做:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330102914556
3.複製父版本庫
若是將使用了submodule添加依賴了子庫的父版本庫git_parent,克隆一份到本地的話。在克隆出來的新版本庫git_parent2中,原父版本庫存放依賴子庫的目錄雖在,可是內容不在:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330103417911
進入根據git_parent複製出來的倉庫git_parent2,會發現mymodule目錄爲空:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330103502848
「解決方法」:可採用多條命令的分步操做,也能夠經過參數將多步操做進行合併。

分步操做
這是在執行了clone操做後的額外操做,還須要作兩件事:

手動初始化submodule:

git submodule init
手動拉取依賴的子版本庫;:

git submodule update --recursive
Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330103803762
執行完兩步操做後,子版本庫中就有內容了。由此完成了git_parent的克隆;

合併操做
分步操做相對繁瑣,還能夠經過添加參數的方式,將多步操做進行合併。經過如下指令基於git_parent克隆一份git_parent3:

git clone git@github.com:AhuntSun/git_parent.git git_parent3 --recursive

Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330104210732
--recursive表示遞歸地克隆git_parent依賴的全部子版本庫。

4.刪除子版本庫
git沒有提供直接刪除submodule子庫的命令,可是咱們能夠經過其餘指令的組合來達到這一目的,分爲三步:

將submodule從版本庫中刪除:

git rm --cache mymodule
Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330105131697

git rm的做用爲刪除版本庫中的文件,並將這一操做歸入暫存區;


將submodule從工做區中刪除;
Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330105226923
最後將.gitmodules目錄刪除;
Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330105542069
完成三步操做後,再進行添加,提交,推送便可完成刪除子庫的操做:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330105614793
2、
subtree

1.簡介
subtree與submodule的做用是同樣的,可是subtree出現得比submodule晚,它的出現是爲了彌補submodule存在的問題:

「第一」:submodule不能在父版本庫中修改子版本庫的代碼,只能在子版本庫中修改,是單向的;
「第二」:submodule沒有直接刪除子版本庫的功能;
而subtree則能夠實現雙向數據修改。官方推薦使用subtree替代submodule。

2.建立子庫
首先建立兩個版本庫:git_subtree_parent和git_subtree_child而後在git_subtree_parent中執行git subtree會列出該指令的一些常見的參數:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330112616987
3.創建關聯
首先須要給git_subtree_parent添加一個子庫git_subtree_child:

「第一步」:添加子庫的遠程地址:

git remote add subtree-origin git@github.com:AhuntSun/git_subtree_child.git

添加完成後,父版本庫中就有兩個遠程地址了:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330113223780
這裏的subtree-origin就表明了遠程倉庫git_subtree_child的地址。

「第二步」:創建依賴關係:

git subtree add --prefix=subtree subtree-origin master --squash
 //其中的--prefix=subtree能夠寫成:--p subtree 或 --prefix subtree

該命令表示將遠程地址爲subtree-origin的,子版本庫上master分支的,文件克隆到subtree目錄下;


注意:是在某一分支(如master)上將subtree-origin表明的遠程倉庫的某一分支(如master)做爲子庫拉取到subtree文件夾中。可切換到其餘分支重複上述操做,也就是說子庫的實質就是子分支。


--squash是可選參數,它的含義是「合併,壓縮」的意思。

若是不增長這個參數,則會把遠程的子庫中指定的分支(這裏是master)中的提交一個一個地拉取到本地再去建立一個合併提交;
若是增長了這個參數,會將遠程子庫指定分支上的屢次提交合並壓縮成一次提交再拉取到本地,這樣拉取到本地的,遠程子庫中的,指定分支上的,歷史提交記錄就沒有了。
Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330114203889
拉取完成後,父版本庫中會增添一個subtree目錄,裏面是子庫的文件,至關於把依賴的子庫代碼拉取到了本地:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330114316257
此時查看一下父版本庫的提交歷史:
Git應用詳解第十講:Git子庫:submodule與subtree
會發現其中沒有子庫李四的提交信息,這是由於--squash參數將他的提交壓縮爲一次提交,並由父版本庫張三進行合併和提交。因此父版本庫多出了兩次提交。

隨後,咱們在父版本庫中進行一次推送:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330114730534
結果遠程倉庫中多出了一個存放子版本庫文件的subtree目錄,而且徹底脫離了版本庫git_subtree_child,僅僅是屬於父版本庫git_subtree_parent的一個目錄。而不像使用submodule那樣,是一個點擊就會自動跳轉到依賴子庫的「指針」:

subtree的遠程父版本庫:
Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330115004586
submodule的遠程父版本庫:
Git應用詳解第十講:Git子庫:submodule與subtree
image-20200329203746236
即submodule與subtree子庫的區別爲:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200408224805624
4.同步子庫變化
在子庫中建立一個新文件world並推送到遠程子庫:
Git應用詳解第十講:Git子庫:submodule與subtree
在父庫中經過以下指令更新依賴的子庫內容:

git subtree pull --prefix=subtree subtree-origin master --squash

Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330115726052
此時查看一下提交歷史:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330115755340
發現沒有子庫李四的提交信息,這都是--squash的做用。子庫的修改交由父庫來提交。

5.參數--squash
該參數的做用爲:防止子庫指定分支上的提交歷史污染父版本庫。好比在子庫的master分支上進行了三次提交分別爲:a、b、c,並推送到遠程子庫。

首先,複習一下合併分支時遵循的「三方合併」原則:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200408003842196
當提交4和6須要合併的時候,git會先尋找兩者的公共父提交節點,如圖中的2,而後在提交2的基礎上進行二、四、6的三方合併,合併後獲得提交7。

父倉庫執行pull操做時:若是添加參數--squash,就會把遠程子庫master分支上的這三次提交合併爲一次新的提交abc;隨後再與父倉庫中子庫的master分支進行合併,又產生一次提交X。整個pull的過程一共產生了五次提交,以下圖所示:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200420103912282
「存在的問題」:

因爲--squash指令的合併操做,會致使遠程master分支上的合併提交abc與本地master分支上的最新提交2,找不到公共父節點,從而合併失敗。同時push操做也會出現額外的問題。

「最佳實踐:要麼所有操做都使用--squash指令,要麼所有操做都不使用該參數,這樣就不會出錯。」

「錯誤示範」:

爲了驗證,從新建立兩個倉庫A和B,並經過subtree將B設置爲A的子庫。此次全程都沒有使用參數--squash,重複上述操做:

首先,修改子庫文件;
而後,經過下列指令,在不使用參數--squash的狀況下,將遠程子庫A變化的文件拉取到本地:

git subtree pull --prefix=subtree subtree-origin master

Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330141920474
此時查看提交歷史:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330142000915
能夠看到子庫兒子的提交信息污染了父版本庫的提交信息,驗證了上述的結論。

因此要麼都使用該指令,要麼都不使用才能避免錯誤;若是不須要子庫的提交日誌,推薦使用--squash指令。


「補充」:echo 'new line' >> test.txt:表示在test.txt文件末尾追加文本new line;若是是一個>表示替換掉test.txt內的所有內容。


6.修改子庫
subtree的強大之處在於,它能夠在父版本庫中修改依賴的子版本庫。如下爲演示:

進入父版本庫存放子庫的subtree目錄,修改子庫文件child.txt,並推送到遠程父倉庫:

Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330121429186
此時遠程父版本庫中存放子庫文件的subtree目錄發生了變化,可是獨立的遠程子庫git_subtree_child並無發生變化。

「修改獨立的遠程子庫」:

可執行如下命令,同步地修改遠程子版本庫:

git subtree push --prefix=subtree subtree-origin master
以下圖所示,父庫中的子庫文件child.txt新增的child2內容,同步到了獨立的遠程子庫中:

Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330125911158
「修改獨立的本地子庫」:

回到本地子庫git_subtree_child,將對應的遠程子庫進行的修改拉取到本地進行合併同步:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330144044823
由此不管是遠程的仍是本地的子庫都被修改了。


實際上使用subtree後,在外部看起來父倉庫和子倉庫是一個總體的倉庫。執行clone操做時,不會像submodule那樣須要遍歷子庫來單獨克隆。而是能夠將整個父倉庫和它所依賴的子庫當作一個總體進行克隆。


存在的問題
父版本庫拉取遠程子庫進行更新同步會出現的問題:

「子倉庫第一次修改」:

經歷了上述操做,本地子庫與遠程子庫的文件達到了同步,其中文件child.txt的內容都是child~4。在此基礎上本地子庫爲該文件添加child5~6:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330145702019
而後推送到遠程子庫。

「父倉庫第一次拉取」:

隨後父版本庫經過下述指令,拉取遠程子庫,與本地父倉庫git_subtree_parent中的子庫進行同步:

git subtree pull --p subtree subtree-origin master --squash
結果出現了合併失敗的狀況:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330145839093
咱們查看衝突產生的文件:

Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330145922152
發現父版本庫中的子庫與遠程子庫內容上並沒有衝突,可是卻發生了衝突,這是爲何呢?

探究衝突產生的緣由以前咱們先解決衝突,先刪除多餘的內容:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330150141430
隨後執行git add命令和git commit命令標識解決了衝突:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330150312944
Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330150406317
解決完衝突後將該文件推送到獨立的遠程子庫,發現文件並無發生更新,也就是說git認爲咱們並無解決衝突:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330150747452
「子倉庫第二次修改與父倉庫第二次拉取」:

再次修改本地子庫的文件並推送到對應的遠程倉庫,父版本庫再次將遠程子庫更新的文件拉取到本地進行同步:
Git應用詳解第十講:Git子庫:submodule與subtree

image-20200330151140092
此次卻成功了!爲何一樣的操做,有的時候成功有的時候失敗呢?

解決方案
緣由出如今--squash指令中。實際上,--squash指令把子庫中的提交信息合併了,致使父倉庫在執行git pull操做時找不到公共的父節點,從而致使即便文件沒有衝突的內容,也會出現合併衝突的狀況。其實不使用--squash也會有這種問題,問題的根本緣由仍然是「三方合併時找不到公共父節點」。咱們打開gitk:

Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330154944300
從圖中不難看出,當使用subtree時,子庫與父庫之間是沒有公共節點的,因此時常會由於找不到公共節點而出現合併衝突的狀況,此時只須要解決衝突,手動合併便可。


不使用subtree時,普通的版本庫中的各分支總會有一個公共節點:

Git應用詳解第十講:Git子庫:submodule與subtree
image-20200330160206258

「再次強調」:使用--squash指令時必定要當心,要麼都使用它,要麼都不使用。

7.抽離子庫
git subtree split
當開發過程當中出現某些子庫徹底能夠複用到其餘項目中時,咱們但願將它獨立出來。

「方法一」:能夠手動將文件拷貝出來。缺點是,這樣會丟失關於該子庫的提交記錄;「方法二」:使用git subtree split指令,該指令會把關於獨立出來的子庫的每次提交都記錄起來。可是,這樣存在弊端:好比該獨立子庫爲company.util,當一次提交同時修改了company.util和company.server兩個子庫時。經過上述命令獨立出來的子庫util只會記錄對自身修改的提交,而不會記錄對company.server的修改,這樣在別人看來此次提交就只修改了util,這是不完整的。

相關文章
相關標籤/搜索