git 子模組使用方法

Xsuns發表於2024-04-22

git 子模組使用方法

目錄
  • git 子模組使用方法
    • 什麼情況下使用子模組
    • 新增新的子模組
    • 克隆含有子模組的倉庫
    • 主倉庫更新子模組
    • 拉取更新了子模組的主倉庫
    • 在子模組上工作
    • 參考來源

什麼情況下使用子模組

如果想要在開發的專案中引入另外一個專案。那麼除了直接將專案檔案複製到主倉庫目錄下以外,還可以選擇一種更優雅的方式,即本文所述的git子模組。這種方式可以將引入專案按照其本身的倉庫狀態進行管理。當引入專案有變更,不管是修復bug,還是更新功能,都可以方便的將其變更拉取到本地倉庫中。

新增新的子模組

首先需要在主倉庫中執行新增指令:

$ git submodule add https://github.com/Xsuns/xsuns_git_submodule_sub.git

拉取完畢後,主倉庫將出現.gitmodules檔案和子倉庫的目錄(直接存入index中)。

$ git diff --cached 
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..13fa722
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "xsuns_git_submodule_sub"]
+       path = xsuns_git_submodule_sub
+       url = https://github.com/Xsuns/xsuns_git_submodule_sub.git
diff --git a/xsuns_git_submodule_sub b/xsuns_git_submodule_sub
new file mode 160000
index 0000000..ba5bf6d
--- /dev/null
+++ b/xsuns_git_submodule_sub
@@ -0,0 +1 @@
+Subproject commit ba5bf6dad82173cded0d4fafe5e72c4a1643f611

其中記錄的三行分別為子倉庫的名稱、本地目錄和拉取地址,例子中的名稱就是xsuns_git_submodule_sub, 本地目錄就是主倉庫根目錄下的./xsuns_git_submodule_sub

提交更改後,就完成了子模組的新增。

$ git commit -am '新增:增加子模組'
[main 2896730] 新增:增加子模組
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 xsuns_git_submodule_sub
$ git log -1
commit 28967303375194f32c92122b3e7e43dff2441d67 (HEAD -> main)
Author: Xsuns <lxyxsuns@gmail.com>
Date:   Mon Apr 22 20:10:58 2024 +0800

    新增:增加子模組

克隆含有子模組的倉庫

對於含有子模組的倉庫,需要使用遞迴克隆命令。

$ git clone --recurse-submodules https://github.com/Xsuns/xsuns_git_submodule_main.git
Cloning into 'xsuns_git_submodule_main'...
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0
Receiving objects: 100% (6/6), done.
Submodule 'xsuns_git_submodule_sub' (https://github.com/Xsuns/xsuns_git_submodule_sub.git) registered for path 'xsuns_git_submodule_sub'
Cloning into 'xsuns_git_submodule_sub' ...
remote: Enumerating objects: 6, done.        
remote: Counting objects: 100% (6/6), done.        
remote: Compressing objects: 100% (2/2), done.        
remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0        
Receiving objects: 100% (6/6), done.
Submodule path 'xsuns_git_submodule_sub': checked out 'ba5bf6dad82173cded0d4fafe5e72c4a1643f611'

如果忘記了,也無需刪除原倉庫,直接使用指令就可以遞迴更新。

ps. --recursive 可以遞迴更新子模組中巢狀的子模組,如果不使用此選項,會導致僅更新直接關聯的子模組。

$ git submodule update --init --recursive
Submodule 'xsuns_git_submodule_sub' (https://github.com/Xsuns/xsuns_git_submodule_sub.git) registered for path 'xsuns_git_submodule_sub'
Cloning into 'xsuns_git_submodule_sub' ...
Submodule path 'xsuns_git_submodule_sub': checked out 'ba5bf6dad82173cded0d4fafe5e72c4a1643f611'

主倉庫更新子模組

如果只想要同步子模組預設的最新分支,那麼只需要以下指令即可。

ps. 如果想要更改預設子模組遠端分支,可以在.gitmodule中子模組對應位置增加branch = target

$ git submodule update --remote
Submodule path 'xsuns_git_submodule_sub': checked out 'ed54345e540890f5ebd258327757de805f5e028c'
$ git diff --submodule
Submodule xsuns_git_submodule_sub ba5bf6d..ed54345:
  > 修改:readme文件標題錯誤

同樣地,如果要將子模組變更儲存到主倉庫中,需要在主倉庫進行提交。

$ git commit -am '新增:更新子模組'
[main c057e0e] 新增:更新子模組
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log -p --submodule -1
commit c057e0e5dea6e369801c2fce168c62438a9e7c69 (HEAD -> main)
Author: Xsuns <lxyxsuns@gmail.com>
Date:   Mon Apr 22 20:50:13 2024 +0800

    新增:更新子模組

Submodule xsuns_git_submodule_sub ba5bf6d..ed54345:
  > 修改:readme文件標題錯誤

如果你想更加自由,也可以進入子模組的目錄中執行git fetchgit merge進行拉取。

拉取更新了子模組的主倉庫

對於主倉庫的協作者,當主倉庫更新了子模組,僅僅使用git pull是不夠的,它僅能修改主倉庫中記錄的子倉庫版本,不能自動檢出對應的版本。

$ git pull
Updating 2896730..c057e0e
Fast-forward
 xsuns_git_submodule_sub | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   xsuns_git_submodule_sub (new commits)

no changes added to commit (use "git add" and/or "git commit -a")

因此還需要如下指令。

$ git submodule update --init --recursive 
Submodule path 'xsuns_git_submodule_sub': checked out 'ed54345e540890f5ebd258327757de805f5e028c'

ps. init可以在主倉庫增加了新的子模組時自動初始化並拉取,recursive則可以更新子模組的巢狀子模組。

還有一種特殊情況是,主倉庫.gitmodules中子模組的url發生了改變,你需要更新主倉庫引用,可以在git submodule update呼叫同步指令。

$ git submodule sync --recursive
Synchronizing submodule url for 'xsuns_git_submodule_sub'
$ git submodule update --init --recursive

ps. 同樣地,如果你在本地修改了.gitmodules中的url,也要執行此同步指令

在子模組上工作

個人經驗來說,不建議在開發主倉庫的過程中,同步對子模組進行修改,特別是來自第三方的庫。因為本地子模組改動如果無法推送到子模組倉庫,會導致其他人拉取主倉庫時無法拉取到子模組的修改。這時唯一的解決辦法就只有對子模組倉庫進行fork,並修改子模組urlfork後的倉庫,然後去維護此倉庫。

但如果你一定要這樣做,首先你需要進入子倉庫中檢出一個工作分支,因為在前幾個章節的操作後,子模組將屬於遊離態,不屬於任何分支。

$ git status
HEAD detached at ed54345
nothing to commit, working tree clean
$ git checkout main 
Switched to branch 'main'

後續對子模組進行更新,需要加上選項--merge或者--rebase,如果忘記的話,子模組會重新變為遊離態,此時只要在子模組目錄下再次檢出工作分支進行手動merge或者rebase即可。

$ git submodule update --remote --merge
Updating ba5bf6d..ed54345
Fast-forward
 readme.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Submodule path 'xsuns_git_submodule_sub': merged in 'ed54345e540890f5ebd258327757de805f5e028c'

如果你有許可權將子模組推送到對應倉庫,你可以在子模組目錄下直接進行推送,然後在推送主倉庫時使用選項--recurse-submodules,將其設定為check,這樣主倉庫推送時會進行檢查,在子倉庫改動未推送時推送失敗。

$ git push --recurse-submodules=check
The following submodule paths contain changes that can
not be found on any remote:
  xsuns_git_submodule_sub

Please try

        git push --recurse-submodules=on-demand

or cd to the path and use

        git push

to push them to a remote.

fatal: Aborting.

推送失敗的提示中也有提到將選項--recurse-submodules設定為on-demandgit將自動嘗試推送子模組。

$ git push --recurse-submodules=on-demand 
Pushing submodule 'xsuns_git_submodule_sub'
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 301 bytes | 301.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/Xsuns/xsuns_git_submodule_sub.git
   ed54345..2631874  main -> main
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 32 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 271 bytes | 271.00 KiB/s, done.
Total 2 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/Xsuns/xsuns_git_submodule_main.git
   c057e0e..6718be9  main -> main

如果你在本地修改了子模組,遠端主倉庫的子模組也被人修改了,即子模組歷史出現分叉。此時git pull拉取主倉庫時將無法順利合併.

$ git pull --recurse-submodules 
Fetching submodule xsuns_git_submodule_sub
Failed to merge submodule xsuns_git_submodule_sub
CONFLICT (submodule): Merge conflict in xsuns_git_submodule_sub
Automatic merge failed; fix conflicts and then commit the result.
$ git diff
diff --cc xsuns_git_submodule_sub
index 7ab5f6c,9670894..0000000
--- a/xsuns_git_submodule_sub
+++ b/xsuns_git_submodule_sub

這時就需要進入子倉庫去進行手動合併,首先要根據第二個SHA1建立一個新的分支去合併。

$ cd xsuns_git_submodule_sub
$ git branch try-merge 9670894
$ git merge try-merge 
Auto-merging readme.md
CONFLICT (content): Merge conflict in readme.md
Automatic merge failed; fix conflicts and then commit the result.

此時得到了一個一般的合併衝突,然後就按照常規方式解決即可

$ git commit -am '合併:保留所有更改'
[main b342081] 合併:保留所有更改

$ cd ..

$ git diff
diff --cc xsuns_git_submodule_sub
index 7ab5f6c,9670894..0000000
--- a/xsuns_git_submodule_sub
+++ b/xsuns_git_submodule_sub
@@@ -1,1 -1,1 +1,1 @@@
- Subproject commit 7ab5f6cd6823c28650eb6f4c50c0d41686ba6f7e
 -Subproject commit 96708949bbc90b7b721880546ac6e82d500ac80a
++Subproject commit b342081a9666aab88e3141c115bb79c1894446b9

$ git commit -am '合併:上游更改'
[main 7385bba] 合併:上游更改

參考來源

[1] Scott Chacon, Ben Straub. Pro Git Book 2nd Edition

[2] 主倉庫

[3] 子模組倉庫

相關文章