文章目錄
前言
工作中碰到這樣一個場景,一個項目裏面的代碼分爲基礎代碼
和定製化代碼
,定製化代碼是針對不同客戶的,基礎代碼需要和定製化代碼分開管理,部署的時候是作爲一個項目一起跑的。
我們在這裏使用git submodule功能來嘗試解決代碼的管理問題。
功能介紹
submodule 目前對 git 倉庫拆分的已有實現之一。
它允許將一個Git倉庫作爲另一個Git倉庫的子目錄。能夠將另一個倉庫克隆到自己的項目中,同時還保持獨立的提交。
功能聽上去非常的牛逼,那我們趕緊來試用一下吧!
使用
準備工作
我們將在Github
上進行相關功能的操作,並在操作過程中時刻觀察倉庫狀態的變化,加深理解。
首先確保自己有GitHub的賬號哈~
在github新建兩個倉庫
很簡單,像這樣
我這裏直接使用github新建了一個倉庫作爲主倉庫。
依葫蘆畫瓢直接在新建兩個子倉庫,分別起名child1_repo
,child2_repo
(名字隨便起,自己認得就行)
我們把主倉庫(以下稱parent)clone下來,隨便提交點什麼,像這樣
同樣對子倉庫child1_repo(以下簡稱child1)和child2_repo(以下簡稱child2)進行一樣的操作。
將子倉庫添加至父倉庫
來到parent目錄下,執行
# []中爲子倉庫的git url
git submodule add [child1 url]
git submodule add [child2 url]
會出現如下提示:
我們來看看這條命令做了哪些事情:
- 首先,
child1
和child2
被克隆到了parent
目錄下 - 使用
git status
命令查看以下文件狀態
可以看到,除了兩個子倉庫外,還多了一個叫.gitmodules
的文件,這是一份子模塊與路徑的映射關係圖,git 根據這份文件去識別 submodule。現在查看一下文件內容:
[submodule "child1_repo"]
path = child1_repo
url = [email protected]:xxx/child1_repo.git
[submodule "child2_repo"]
path = child2_repo
url = [email protected]:xxx/child2_repo.git
一個子倉庫對應一個git url,清晰明瞭。
提交
我們將parent
下的改動進行提交,並push到遠程倉庫上
我們注意到,在commit了改動之後,除了常規的100644
之外(100代表regular file,644代表文件權限),還出現了160000
。
160000
代表 Git 中的一種特殊模式,它本質上意味着你是將一次提交記作一項目錄記錄
的,而非將它記錄成一個子目錄或者一個文件。
上面這句話是什意思呢,我們通過git diff
查看剛纔提交的信息
# child1_repo
Commit 8e182c74182adde49f2b6d192f2a85c50d87f538
Commit Message add child1
# child2_repo
Commit 64a14d2c3f22aee584071b93c2bda4ef5243e4a2
Commit Message add child2
可以看到,這兩個子倉庫在parent下commit的就是一次提交的信息,而git就把它們當作是一個目錄進行記錄的。
我們查看一下遠端倉庫的情況:
可以看到,兩個子倉庫均已經push到了parent
下,另外在child1
和child2
後面還跟着一串code,這是子倉庫的commitId的後綴,表示該子倉庫簽出時的版本(這個在下面解釋),這個code是不會顯示在克隆到本地的倉庫中的。
進入child1_repo @ 8e182c7
觀察一下倉庫的狀態
如圖所示,此時child1
處於一種遊離的狀態,在git頁面無無法新建和編輯文件
點擊編輯會提示:
you must be on a branch to make or propose changes to this file
我們點擊上圖畫圈的部分,將其切換到master分支後,我們就可以執行正常的git操作。
(注意:此時我們已經是在child1_repo
下進行操作的)
我們查看一下child1
的commit記錄
可以看到,child1
提交的commitId與parent
中的child1_repo @ 8e182c7
的後綴是一致的,因爲本地parent
子模塊在push時簽出的版本正是8e182c7
總結一下,對於child1
和child2
來說,只有它們的遠程 URL 會被記錄在父倉庫中,以及它們在主項目中的本地路徑和簽出的版本。
模擬多人協作
我們在git 頁面嘗試進入child1
,其下有一個文件,我們嘗試編輯這個文件,並將修改的內容提交,像這樣
再看看child1
的commit記錄,此時git head的指針已經指向了新的commitId:
這是一個標準的git操作流程。
我們返回到parent中查看一下child1
的狀態,並沒有變化,記錄依舊保持在第一次簽出時的版本。
拉取遠程子模塊的代碼到本地
子倉庫在遠程更改了,那麼我們本地如何進行同步呢(如果只要修改主倉庫的代碼,正常的git操作就可以了)
這裏以child1
爲例,進入parent
,執行
git submodule update --remote child1_repo
這條命令將會拉取child1_repo
中最新的提交,結果如下:
這裏,我們如果單純的執行git submodule update
,我們拉取的將是遠程parent
下最後一次簽出的子倉庫的版本。
另外一種更新子倉庫child1_repo
的方法就是直接進入child1_repo
目錄下,像任何普通的Git那樣進行操作即可。
本地子模塊修改提交到遠程
我們在本地的子模塊中進行了一些修改,需要進行提交,如何操作呢?
這裏依舊以child1_repo
爲例進行闡述,在上述拉取child1
最新的一次提交之後,我們本地相對於遠端的parent
已經有了改動,我們現在在本地再次進行一些修改,並進行提交。
我們在本地cd到child1_repo
目錄中瞅瞅~
首先,我們的修改是被捕捉到了,這說明我們是可以進行正常的git操作的。
觀察一下此時child1_repo
的狀態,它沒有指向任何一個分支,而是停留在一個稱作“遊離的 HEAD”的狀態,這意味着沒有本地工作分支(例如 “master” )跟蹤改動,你也就沒辦法提交代碼。
解決方案很簡單,使用git checkout branch
命令切換到某個分支就可以了
我們把之前修改commit一下,然後切換到master分支上。
我們嘗試將本地子倉庫的修改推送到遠程(上述操作存在一個問題,導致我本地的修改丟失了,記得先pull,我這裏重新修改提交了)
我們切回到parent,使用 add commit push 三連將代碼提交到遠端(記得先pull),完事我們查看下遠端的倉庫
看來已經正確的提交了:)
可能會出現的幺蛾子
Git對於子模塊的管理相對來說比較複雜。當然出現異常的情況也是不可避免的,我們來看看在使用git submodule的過程中可能會出哪些問題。
主模塊提交併推送了改動,而子模塊並沒有推送
如果我們在主倉庫中提交併推送但並不推送子模塊上的改動,其他人嘗試更新子模塊的人會遇到麻煩,因爲他們無法得到依賴的子模塊改動。那些改動只存在於我們本地的拷貝中。
我們嘗試一下上述過程,對child1_repo
做一些修改並提交,但不推送。
這裏我又添加了一句話,並提交了。我們回到parent
瞅瞅
好,parent
追蹤到了submodule的改動,現在我們把它提交併推送到遠程
成功了…看來git並不會主動幫你檢測子模塊的改動是否推送。
我們把本地的倉庫刪除,重新克隆一份下來。我們觀察一下目錄,所有子倉庫只有一個空的文件夾,這裏需要我們去git submodule init
初始化本地配置文件以及 git submodule update
拉取代碼。
直接報錯,無法更新了。
爲了確保這不會發生,你可以讓 Git 在推送到主項目前檢查所有子模塊是否已推送。
使用如下命令
git push --recurse-submodules=check # 如果子模塊沒有提交,會直接報錯
# or
git push --recurse-submodules=on-demand # 如果子模塊沒有提交,會嘗試提交,提交不成功同時會阻止主倉庫的推送
其他
其實問題還不少,暫時不一一復現了,主要出現的問題參考了這篇博客
另外還有別的可能出現的問題(e.g.將子目錄轉換成子模塊、git submodule update failed等),可以參考文章結尾給出的文檔
在有子模塊的項目中切換分支可能會造成麻煩
如果你創建一個新分支,在其中添加一個子模塊,之後切換到沒有該子模塊的分支上時,你仍然會有一個還未跟蹤的子模塊目錄,這時候如果不小心提交了這個子模塊(git commit -am “message”),就會有問題了。
提交和獲取的問題
對子模塊做了修改,需要先推送子模塊再主模塊,同時拉取的時候也需要先主模塊,再子模塊。
記得先切換分支
對子模塊做本地修改需要先檢出分支,否則有可能在 “遊離的 HEAD” 上做修改。
記得pull完了還得update一下
如果你的同事更新了 submodule,然後更新了父項目中依賴的版本號。你需要在 git pull
之後,調用 git submodule update
來更新 submodule 信息。這兒的坑在於,如果你 git pull
之後,忘記了調用 git submodule update
,那麼你極有可能再次把舊的submodule 依賴信息提交上去(使用 git submit -am "message"
或者 git add .
提交的人會遇到這種事)。
參考
git子模塊
git submodule(csdn)
子模塊 - Git Tower
git submodule updated failed