Git Submodule 命令使用與詳細教程

歡迎任何形式的轉載,轉載請保留原文連接:juejin.cn/post/694825…java

前言

爲啥要用 git submodule,以及爲啥不使用 git 倉庫直接嵌套的形式 ?git

  1. 當子項目是公有項目時,爲了方便引用,子項目會單領出一個倉庫來。這樣就牽扯到多倉庫的問題,即主項目是一個倉庫,子項目是另一個倉庫,這裏就存在倉庫嵌套管理的問題了
  2. 若是直接使用倉庫嵌套的方式,那麼就存在如下幾個問題與預期
    • 但願經過主項目自己,就能知道子項目的地址,而不是額外記錄
    • 但願主項目可以關聯子項目的提交版本,以便主項目切換不一樣分支時,保證對應的子項目提交版本正確,避免切換分支後沒法運行,以及子項目更新後,可以正常運行
    • 但願主項目可以關聯子項目的提交分支:額,這點仔細考慮後,有待磋商
  3. 若是使用倉庫直接嵌套的方式,那麼就必須額外記錄上述提到的子項目倉庫地址以及提交版本信息
  4. 而使用 git submodule,則主項目倉庫自己就記錄了上述信息,這也是爲啥要用的緣由

本文主要講解 git submodule 使用與教程,目的有如下幾點:github

  1. 爲方便平常查閱,收集經常使用操做以及對應命令
  2. 總結倉庫如何記錄 submodule 信息,以及得出的使用結論與注意事項
  3. 使用詳細教程:一方面提供教程,另外一方面爲上述總結與命令使用提供實踐支撐,同時也是本人作上述總結的過程

注:爲了方便查閱經常使用操做以及對應命令,文章章節順序非友好的學習順序,其中教程巨長放到最後了,若是教程以前的內容看不明白,建議過一遍教程shell

目錄

  • 前言
  • 目錄
  • 經常使用操做以及對應命令
  • 本地刪除 submodule、修改 submodule 名稱與倉庫地址
  • 關於倉庫相關總結
  • submodule 相關文件內容
  • 使用教程
    • 測試環境搭建
    • 添加 submodule
    • 刪除 submodule
    • 重命名 submodule 文件夾
    • 更改 submodule 遠程倉庫地址
    • 另外一個主項目(未添加 submodule)更新
      • 已有項目時,其餘人如何更新
      • 如何從新 clone 整個項目
    • 誤操做:誤把子模塊文件夾刪除了
    • 子模塊單分支狀況下修改更新
    • 主項目多分支,且不一樣分支間 submodule 有差別
    • 主項目和子模塊都是多分支,主項目切分支對子模塊分支的影響
  • 參考文獻與版本同步

經常使用操做以及對應命令

# 查看 submodules
# 結果的 hash 前不帶符號說明該 module 正常且提交版本同步(提交版本同步指主項目記錄提交版本與子模塊當前提交版本一致)
# 結果的 hash 前帶 - 號說明該 module 未初始化
# 結果的 hash 前帶 + 號說明該 module 版本未同步
git submodule
git submodule status
 # 初始化 modules,重複初始化無影響,例子中後跟 rxjava 爲指定初始化某個 module 的名稱(下同)
git submodule init
git submodule init rxjava
 # 版本未同步時,檢出 modules,保證檢出的版本與主項目匹配,但子 module 會建立臨時分支
git submodule update
git submodule update rxjava
 # 添加 submodule,例子中後跟 rxjava 爲該 module 名稱與目錄名
git submodule add https://github.com/ReactiveX/RxJava.git
git submodule add https://github.com/ReactiveX/RxJava.git rxjava
 # 強制添加 submodule(僅用於 git submodule 沒有正確的顯示某個 module)
git submodule add --force --name rxjava https://github.com/ReactiveX/RxJava.git
 # 從新 clone 項目(含 clone 全部子項目)方式一
git clone --recursive https://github.com/ReactiveX/RxJava.git
 # 從新 clone 項目(含 clone 全部子項目)方式二,依次執行如下命令
git clone https://github.com/ReactiveX/RxJava.git
git submodule init
git submodule update
 # 遍歷全部 submodule
# git submodule foreach 其餘命令,如:
git submodule foreach git pull
git submodule foreach ls -l
複製代碼

主項目拉取,且有新的 submodule(就是存在未初始化的)瀏覽器

git pull
git submodule init
git submodule update [新 module 名稱]
複製代碼

其餘說明:緩存

  • 子項目更新,直接使用 git pull 便可。若是想讓提交版本同步,主項目使用 git submodule update rxjava
  • 未初始化的,要先初始化,而後才能檢出。因此上述不帶選項的 git clone(方式二)要先初始化,再檢出,這樣才能正常使用
  • 關於 submodule 嵌套 submodule:沒啥問題,相關文件方面除了【.gitmodules】文件,其餘相關文件仍是在最外層的主項目的【.git】目錄裏
  • 新增 submodule 部分 IDE 工具須要新打開主項目才行,包括 IDEA

本地刪除 submodule、修改 submodule 名稱與倉庫地址

本地倉庫不廢棄的狀況下,對某個 submodule 進行刪除、修更名稱、修改倉庫地址操做,但除了修改倉庫地址,其餘人的遠程倉庫是基本完蛋了,須要從新 clonebash

刪除 submodulemarkdown

  • 第一步操做必須第一個執行,其餘操做無順序要求
  • 執行第一步前須要保證全部修改都已提交(這也是必須第一個執行該步驟的緣由,同時也是其餘人的本地倉庫基本完蛋的緣由)
  • 不想刪除,還原可使用 git reset --hard HEAD 命令,再次強調全部修改都已提交再操做
  • 第一步操做成功的驗證方式:git submodule 的結果沒有要刪除的
  • 非必須步驟不作也行,就是在重複添加相同 submodule 時,可能存在問題
# 假設 submodule 名稱爲 subA
# 1. 必須,git rm -r --cached subA
# 2. 必須,rm -rf subA/,或手動刪除對應目錄
# 3. 必須,修改【.gitmodules】文件內容:去除被刪除的 submodule 內容
# 4. 非必須,修改【.git/config】文件內容:去除被刪除的 submodule 內容
# 5. 非必須,刪除【.git/modules】目錄下對應 submodule 目錄
複製代碼

重命名 submodule 目錄工具

  • 沒有好辦法,全部的方法,本質都是先刪除,再從新添加
  • 之因此說本質,是由於都要執行 git rm -r --cached subA 命令,以及 git submodule add 命令
  • 如下操做是錯誤的:
    • 直接修改【.gitmodules】、【.git/config】內容
    • 直接刪除【.git/modules】目錄下對應 submodule 目錄

更改 submodule 遠程倉庫地址oop

  • 說明:這裏說的是修改只修改遠程倉庫地址,不修改子模塊名稱的狀況
  • 第一個步驟隻影響從新 clone 的結果
  • 其餘人的本地倉庫除了主項目更新外,還須要作第二步和第三步操做
# 1. 修改【.gitmodules】文件對應倉庫地址:隻影響遠程倉庫
# 2. 修改【.git/config】文件對應倉庫地址:額,改不改無所謂
# 3. 進入子模塊,使用 `git remote` 命令,更新遠程倉庫地址
複製代碼

關於倉庫相關總結

關於遠程倉庫

  • 遠程倉庫添加 submodule 先後,除了提交記錄與 HEAD 指證外,其餘文件並未發生變化,因此主項目的 submodule 記錄僅來源於主項目下的【.gitmodules】文件
  • 因此在不考慮本地倉庫的狀況下,刪除 submodule,修改 submodule 名稱與倉庫地址,都只要修改【.gitmodules】文件內容便可
  • 若是遠程倉庫使用的是真實的遠程倉庫,則使用瀏覽器訪問主項目時,就能夠發現主項目存在 submodule 目錄,並點擊該目錄能夠跳轉到 submodule 對應的遠程倉庫

關於本地倉庫

  • 主項目並不會記錄子模塊的對應的分支,只記錄子模塊的提交版本
  • 因此 git submodule update 檢出時,保證檢出的版本與主項目匹配,但子 module 會建立臨時分支
  • 主項目切換分支時,多餘的子模塊並不會被刪除(緣由是刪除目錄失敗),這點在提交時會體現,須要注意。此時
    • 【.gitmodules】文件正確,沒有多餘的子模塊信息
    • 【.git/config】文件、【.git/modules】依然保留另外一個子模塊信息。一方面說明這種切換分支不影響本地倉庫,另外一方也提升切換這種分支時的效率
  • 通常建議添加 submodule 時不另指定名字,這個是爲了起到如下做用
    • 切換分支後,誤把多餘子模塊上傳時,從新添加相同 submodule 時,報目錄已存在錯誤

submodule 相關文件內容

最主要的是主項目的【.gitmodules】文件,內容以下,覺得比較簡單就不解釋了

[submodule "subA"]
	path = subA
	url = /cygdrive/e/submodule/repo/subA.git
[submodule "subB"]
	path = subB
	url = /cygdrive/e/submodule/repo/subB.git
複製代碼

關於本地倉庫,對比添加 submodule 先後:

  1. 根目錄多出一個【.gitmodules】文件
  2. 子模塊文件夾多出一個【.git】文件,內容指向子模塊本地倉庫路勁
  3. 主項目【.git/config】文件多出 submodule 塊,這個是 git submodule init 後更新
  4. 主項目【.git】目錄多出【modules】目錄,裏面是子模塊原【.git】目錄內容,這個是 git submodule update 後出現
# 【子模塊/.git】文件
gitdir: ../.git/modules/subA
複製代碼
# 【.git/config】文件,下列[submodule "subA"]塊是多出來的
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
	ignorecase = true
[remote "origin"]
	url = /cygdrive/e/submodule/repo/main.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[submodule "subA"]
	active = true
	url = /cygdrive/e/submodule/repo/subA.git
[submodule "subB"]
	active = true
	url = /cygdrive/e/submodule/repo/subB.git
複製代碼
$ ll .git/modules/
總用量 8
drwxrwxr-x+ 1 Administrator None 0 4月   2 15:41 subA/
drwxrwxr-x+ 1 Administrator None 0 4月   2 15:41 subB/
複製代碼

使用教程

如下是 git submodule 詳細使用教程

  • 一來介紹 git submodule 相關使用
  • 二來介紹各個階段本地倉庫與遠程倉庫的變化

測試環境搭建

爲方便驗證與測試,這裏提供測試環境搭建過程

  • 以 【#】開頭,後續跟註釋,下同
  • 以 【$】開頭,後續跟命令,下同
  • 其餘爲命令結果,下同

建立(本地性質的)遠程倉庫

# 建立(本地性質的)遠程倉庫與本地倉庫目錄
$ mkdir repo
$ mkdir project

# 進入遠程倉庫目錄,創建主項目遠程倉庫(main.git)以及,若干個子模塊遠程倉庫(subA.git)
$ cd repo
$ git --git-dir=main.git init --bare
$ git --git-dir=subA.git init --bare
$ git --git-dir=subB.git init --bare

$ ll
總用量 16
drwxrwxr-x+ 1 Administrator None 0 4月   2 14:53 main.git/
drwxrwxr-x+ 1 Administrator None 0 4月   2 14:53 subA.git/
drwxrwxr-x+ 1 Administrator None 0 4月   2 14:53 subB.git/

# 獲取遠程倉庫地址,用來 clone 項目
$ cd main.git
$ pwd
/cygdrive/e/submodule/repo/main.git

# 同理其餘子模塊遠程倉庫地址
/cygdrive/e/submodule/repo/subA.git
/cygdrive/e/submodule/repo/subB.git
複製代碼

遠程倉庫初始提交,目的爲了讓每一個遠程倉庫都有分支,以及提交記錄

# 在 repo 目錄下 clone 代碼
$ cd ../
$ pwd
/cygdrive/e/submodule/repo

$ git clone main.git
$ ll
總用量 16
drwxrwxr-x+ 1 Administrator None 0 4月   2 15:21 main/
drwxrwxr-x+ 1 Administrator None 0 4月   2 14:53 main.git/
drwxrwxr-x+ 1 Administrator None 0 4月   2 14:53 subA.git/
drwxrwxr-x+ 1 Administrator None 0 4月   2 14:53 subB.git/

# 建立任意文件,並作初始化提交,並推送到遠程
$ cd main
$ touch init
$ git add .
$ git commit -m "main init"
$ git push
$ git branch
* master

$ git log --oneline
468894f (HEAD -> master, origin/master) main init

# 到這裏上述 main.git 遠程倉庫就有一個 master 分支,以及一個提交記錄了

# 既然已近作好推送了,就能夠刪除這個用來作初始化的臨時本地倉庫了
$ cd ..
$ rm -rf main

# 其餘遠程倉庫,按上述同操做便可,如下爲各個遠程倉庫首次提交
468894f (HEAD -> master, origin/master) main init
5ae8df5 (HEAD -> master, origin/master) subA init
6bf24b8 (HEAD -> master, origin/master) subB init
複製代碼

複製主項目遠程倉庫,用來對比添加 submodule 先後,遠程倉庫的變化

添加 submodule

從遠程倉庫 clone 到項目目錄下的本地倉庫

# 主項目 clone,多 clone 一個是爲了對比
$ cd ../project
$ git clone /cygdrive/e/submodule/repo/main.git
$ git clone /cygdrive/e/submodule/repo/main.git main_copy
複製代碼

添加子 module(如下簡稱子模塊)

# 往主項目裏添加 submodule,注:一個一個來,以便分開 commit,方便後續演示
$ cd main
$ git submodule add /cygdrive/e/submodule/repo/subA.git
正克隆到 '/cygdrive/e/submodule/project/main/subA'...
完成。
warning: .gitmodules 中的 LF 將被 CRLF 替換。
在工做區中該文件仍保持原有的換行符

# 添加後結果
$ ll
總用量 0
-rw-rw-r--+ 1 Administrator None 0 4月   2 15:37 init
drwxrwxr-x+ 1 Administrator None 0 4月   2 15:37 subA/

$ git status
位於分支 master
您的分支與上游分支 'origin/master' 一致。

要提交的變動:
  (使用 "git reset HEAD <文件>..." 以取消暫存)

        新文件:   .gitmodules
        新文件:   subA

$ ll -a subA/
總用量 1
drwxrwxr-x+ 1 Administrator None  0 4月   2 15:37 ./
drwxrwxr-x+ 1 Administrator None  0 4月   2 15:38 ../
-rw-rw-r--+ 1 Administrator None 29 4月   2 15:37 .git
-rw-rw-r--+ 1 Administrator None  0 4月   2 15:37 init

# 主項目提交本次添加 submodule 的結果
$ git add .
$ git commit -m "添加 submodule:subA"
[master 7ada136] 添加 submodule:subA
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 subA

# 同理添加另外一個 submodule,最後提交記錄與 submodule 記錄以下
$ git log --oneline
31287e4 (HEAD -> master) 添加 submodule:subB
7ada136 添加 submodule:subA
468894f (origin/master, origin/HEAD) main init

$ git submodule
 5ae8df5a1fd55d31b0fc7e8bdf7d02fb7591f4bd subA (heads/master)
 6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638 subB (heads/master)
複製代碼

對比 main 和 main_copy(沒有 submodule)可知

  1. 根目錄多出一個【.gitmodules】文件
  2. 子模塊文件夾多出一個【.git】文件,內容指向子模塊本地倉庫路勁
  3. 主項目【.git/config】文件多出 submodule 塊,這個是 git submodule init 後更新
  4. 主項目【.git】目錄多出【modules】目錄,裏面是子模塊原【.git】目錄內容,這個是 git submodule update 後出現
$ cat .gitmodules
[submodule "subA"]
        path = subA
        url = /cygdrive/e/submodule/repo/subA.git
[submodule "subB"]
        path = subB
        url = /cygdrive/e/submodule/repo/subB.git

$ cat subA/.git
gitdir: ../.git/modules/subA

$ cat .git/config
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        ignorecase = true
[remote "origin"]
        url = /cygdrive/e/submodule/repo/main.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master
[submodule "subA"]
        url = /cygdrive/e/submodule/repo/subA.git
        active = true
[submodule "subB"]
        url = /cygdrive/e/submodule/repo/subB.git
        active = true

$ ll .git/modules/
總用量 8
drwxrwxr-x+ 1 Administrator None 0 4月   2 15:41 subA/
drwxrwxr-x+ 1 Administrator None 0 4月   2 15:41 subB/
複製代碼

若是遠程倉庫使用的是真實的遠程倉庫,則使用瀏覽器訪問主項目時,就能夠發現主項目存在 submodule 目錄,並點擊該目錄能夠跳轉到 submodule 對應的遠程倉庫

到此對比遠程倉庫添加 submodule 先後,能夠發現除了提交記錄與 HEAD 指證外,其餘文件並未發生變化,因此主項目的 submodule 記錄僅取決於主項目下的【.gitmodules】文件內容

刪除 submodule

刪除 submodule 的方式有兩種

  1. 只修改【.gitmodules】文件,而後推送遠程倉庫,再從新 clone,本來地倉庫不要了
  2. 本地倉庫正常刪除 submodule,而後推送遠程倉庫,本來地倉庫依然可使用,但其餘人的本地倉庫是基本完蛋了

方式一:只修改【.gitmodules】文件,而後推送遠程倉庫,再從新 clone,本來地倉庫不要了

  • 解釋:由於從添加 submodule 的結果可知,遠程倉庫的 submodule 記錄僅取決於【.gitmodules】文件內容,與其餘無關,因此該方式可行
  • 步驟:
    1. 修改【.gitmodules】文件內容:去除被刪除的 submodule 內容
    2. 刪除對應 submodule 文件夾,這個是防止從新 clone 時多出一個空文件夾
    3. 從新提交併推送遠程倉庫
    4. 使用 git clone 新開一個本地倉庫
# 原【.gitmodules】內容
[submodule "subA"]
	path = subA
	url = /cygdrive/e/submodule/repo/subA.git
[submodule "subB"]
	path = subB
	url = /cygdrive/e/submodule/repo/subB.git

# 刪除後【.gitmodules】內容
[submodule "subA"]
	path = subA
	url = /cygdrive/e/submodule/repo/subA.git
複製代碼

方式二:本地倉庫正常刪除 submodule,而後推送遠程倉庫,本來地倉庫依然可使用

  • 本地倉庫按如下 5 個步驟執行,完成後推送到遠程倉庫便可
    • 注:除了第 1 步必須先執行,後續步驟隨意
  1. 必須,清除 submodule 緩存記錄

    • 執行前須要保證全部修改都已提交(這也是必須第一個執行該步驟的緣由,同時也是其餘人的本地倉庫基本完蛋的緣由)
    • 執行該命令不影響任何文件內容
    • 不想刪除,還原可使用 git reset --hard HEAD 命令,再次強調全部修改都已提交再操做
    • 該步驟成功的驗證方式:git submodule 結果沒有要刪除的
    $ git rm -r --cached subA
    rm 'subA'
    
    # 上述命令結果驗證
    $ git submodule
     5ae8df5a1fd55d31b0fc7e8bdf7d02fb7591f4bd subA (heads/master)
    複製代碼
  2. 必須,刪除 submodule 文件夾,即 subA 文件夾,對應命令以下,不過建議手動刪除

    $ rm -rf subA/
    複製代碼
  3. 必須,修改【.gitmodules】文件內容:去除被刪除的 submodule 內容

    # 原內容
    [submodule "subA"]
    	path = subA
    	url = /cygdrive/e/submodule/repo/subA.git
    [submodule "subB"]
    	path = subB
    	url = /cygdrive/e/submodule/repo/subB.git
    
    # 刪除後內容
    [submodule "subA"]
    	path = subA
    	url = /cygdrive/e/submodule/repo/subA.git
    複製代碼
  4. 非必須,修改【.git/config】文件內容:去除被刪除的 submodule 內容

    • 不刪除也行,隻影響本地,不影響遠程倉庫。也就在重複添加相同 submodule 時,可能存在問題
    # 原內容
    [core]
    	repositoryformatversion = 0
    	filemode = true
    	bare = false
    	logallrefupdates = true
    	ignorecase = true
    [submodule]
    	active = .
    [remote "origin"]
    	url = /cygdrive/e/submodule/repo/main.git
    	fetch = +refs/heads/*:refs/remotes/origin/*
    [branch "master"]
    	remote = origin
    	merge = refs/heads/master
    [submodule "subA"]
    	url = /cygdrive/e/submodule/repo/subA.git
    [submodule "subB"]
    	url = /cygdrive/e/submodule/repo/subB.git
    
    # 刪除後內容
    [core]
    	repositoryformatversion = 0
    	filemode = true
    	bare = false
    	logallrefupdates = true
    	ignorecase = true
    [submodule]
    	active = .
    [remote "origin"]
    	url = /cygdrive/e/submodule/repo/main.git
    	fetch = +refs/heads/*:refs/remotes/origin/*
    [branch "master"]
    	remote = origin
    	merge = refs/heads/master
    [submodule "subA"]
    	url = /cygdrive/e/submodule/repo/subA.git
    複製代碼
  5. 非必須,刪除【.git/modules】目錄下對應 submodule 目錄

    • 不刪除也行,隻影響本地,不影響遠程倉庫。也就在重複添加相同 submodule 時,可能存在問題

重命名 submodule 文件夾

沒有好辦法,全部的方法,本質都是先刪除,再從新添加

  • 之因此說本質,是由於都要執行 git rm -r --cached subA 命令,以及 git submodule add 命令

如下操做是錯誤的:

  • 直接修改【.gitmodules】、【.git/config】內容
  • 直接刪除【.git/modules】目錄下對應 submodule 目錄

更改 submodule 遠程倉庫地址

這裏說的是修改只修改遠程倉庫地址,不修改子模塊名稱的狀況

背景:原來的 /cygdrive/e/submodule/repo/subA.git 遠程倉庫遷移到 /cygdrive/e/submodule/subA.git

更改步驟說明

  • 第一個步驟隻影響從新 clone 的結果
  • 其餘人的本地倉庫除了主項目更新外,還須要作第二步和第三步操做
# 1. 修改【.gitmodules】文件對應倉庫地址:隻影響遠程倉庫
# 2. 修改【.git/config】文件對應倉庫地址:額,改不改無所謂
# 3. 進入子模塊,使用 `git remote` 命令,更新遠程倉庫地址
複製代碼

另外一個主項目(未添加 submodule)更新

這裏演示多人共同開發主項目時,其中一人添加了 submodule

  1. 其餘人如何更新
  2. 其餘人如何從新 clone 整個項目

1. 已有項目時,其餘人如何更新

pull 主項目,並查看日誌

$ cd main_copy
$ git pull
$ git log --oneline
31287e4 (HEAD -> master, origin/master, origin/HEAD) 添加 submodule:subB
7ada136 添加 submodule:subA
468894f main init
複製代碼

查看 submodule 記錄、子模塊內容、【.gitmodules】文件、【.git/config】文件、【.git/modules】目錄、子模塊日誌

$ git submodule
-5ae8df5a1fd55d31b0fc7e8bdf7d02fb7591f4bd subA
-6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638 subB

$ ll -a subA subB
subA:
總用量 0
drwxrwxr-x+ 1 Administrator None 0 4月   2 17:36 ./
drwxrwxr-x+ 1 Administrator None 0 4月   2 17:36 ../

subB:
總用量 0
drwxrwxr-x+ 1 Administrator None 0 4月   2 17:36 ./
drwxrwxr-x+ 1 Administrator None 0 4月   2 17:36 ../

$ cat .gitmodules
[submodule "subA"]
        path = subA
        url = /cygdrive/e/submodule/repo/subA.git
[submodule "subB"]
        path = subB
        url = /cygdrive/e/submodule/repo/subB.git

$ cat .git/config
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        ignorecase = true
[remote "origin"]
        url = /cygdrive/e/submodule/repo/main.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master

$ ll .git/modules
/bin/ls: 沒法訪問'.git/modules': No such file or directory

$ cd subA
$ git log --oneline
31287e4 (HEAD -> master, origin/master, origin/HEAD) 添加 submodule:subB
7ada136 添加 submodule:subA
468894f main init
$ cd ..
複製代碼

如上,發現如下幾個問題

  • git submodule 的結果前面帶[-]號
  • 【.gitmodules】文件正常
  • 【.git/config】文件、【.git/modules】目錄並無 submodule 信息
  • 子模塊日誌不正確,顯示的是主項目日誌

這些問題都是由於子模塊並無初始化、檢出致使的

  • git submodule 的結果前面帶[-]號就是表示該 submodule 未初始化
# 初始化所有 submodule,重複初始化無反應
$ git submodule init
子模組 'subA'(/cygdrive/e/submodule/repo/subA.git)已對路徑 'subA' 註冊
子模組 'subB'(/cygdrive/e/submodule/repo/subB.git)已對路徑 'subB' 註冊

$ git submodule init
$ git submodule init subA
$ git submodule
 5ae8df5a1fd55d31b0fc7e8bdf7d02fb7591f4bd subA
 6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638 subB

# 檢出全部 submodule,重複檢出無反應
$ git submodule update
正克隆到 '/cygdrive/e/submodule/project/main_copy/subA'...
完成。
正克隆到 '/cygdrive/e/submodule/project/main_copy/subB'...
完成。
Submodule path 'subA': checked out '5ae8df5a1fd55d31b0fc7e8bdf7d02fb7591f4bd'
Submodule path 'subB': checked out '6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638'

$ git submodule update
$ git submodule update subA
$ git submodule
 5ae8df5a1fd55d31b0fc7e8bdf7d02fb7591f4bd subA
 6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638 subB

$ cd subA
$ git log --oneline
5ae8df5 (HEAD, origin/master, origin/HEAD, master) subA init

cd ..
複製代碼

通過初始化和檢出,子模塊就能夠正常使用了

  • 初始化只會更新【.git/config】。【.git/modules】目錄並無更新,因此子模塊的日誌仍是不對的,必須檢出才能用
  • 初始化後 git submodule 的結果前面就不帶任何符號了

2. 如何從新 clone 整個項目

兩種方式,一種是是直接 clone 項目,而後初始化、檢出

$ git clone /cygdrive/e/submodule/repo/main.git main_new
正克隆到 'main_new'...
完成。

$ git submodule init
子模組 'subA'(/cygdrive/e/submodule/repo/subA.git)已對路徑 'subA' 註冊
子模組 'subB'(/cygdrive/e/submodule/repo/subB.git)已對路徑 'subB' 註冊

$ git submodule update
正克隆到 '/cygdrive/e/submodule/project/main_new/subA'...
完成。
正克隆到 '/cygdrive/e/submodule/project/main_new/subB'...
完成。
Submodule path 'subA': checked out '5ae8df5a1fd55d31b0fc7e8bdf7d02fb7591f4bd'
Submodule path 'subB': checked out '6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638'
複製代碼

另一種是 clone 時添加 --recursive 選項直接一步到位

$ git clone --recursive /cygdrive/e/submodule/repo/main.git main_new_02
正克隆到 'main_new'...
完成。
子模組 'subA'(/cygdrive/e/submodule/repo/subA.git)已對路徑 'subA' 註冊
子模組 'subB'(/cygdrive/e/submodule/repo/subB.git)已對路徑 'subB' 註冊
正克隆到 '/cygdrive/e/submodule/project/main_new/subA'...
完成。
正克隆到 '/cygdrive/e/submodule/project/main_new/subB'...
完成。
Submodule path 'subA': checked out '5ae8df5a1fd55d31b0fc7e8bdf7d02fb7591f4bd'
Submodule path 'subB': checked out '6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638'
複製代碼

誤操做:誤把子模塊文件夾刪除了

初始化,以及檢出以後,誤把子模塊刪除。這裏先說下結論

  • 此時缺失子模塊,並不會對主項目形成影響,包括 diff 等
  • 此時想要恢復,不能手動建立文件夾再手動 pull 代碼。只能從新檢出,也就是執行 git submodule update,但這種檢出會建立一個臨時分支
  • 從結果可知,主項目並不會記錄子模塊的對應的分支,只記錄子模塊的提交版本
# 缺失子模塊,不影響主項目 diff
$ rm -rf subB/
$ git status
位於分支 master
您的分支與上游分支 'origin/master' 一致。

無文件要提交,乾淨的工做區

# 缺失子模塊,一樣不影響主項目 submodule 記錄
$ git submodule
 d8346a82a2ba13077d7f25124239ac0d3db5920c subA (heads/master)
 6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638 subB

# 缺失子模塊,重複初始化不起做用
$ git submodule init

$ ll
總用量 0
-rw-rw-r--+ 1 Administrator None 0 4月   2 17:04 init
drwxrwxr-x+ 1 Administrator None 0 4月   2 17:26 subA/

# 缺失子模塊,無效且錯誤作法:新建對應文件夾,而後在對應文件夾裏 pull,從結果能夠看出日誌並非子模塊的日誌
$ mkdir subB
$ cd subB/

$ gitlog
* 0e3354b 2021-04-02 18:42:00 |  (HEAD -> other, origin/master, origin/HEAD, master)submodule subA 提交記錄更更新  <jefshi>
* 31287e4 2021-04-02 17:10:54 | 添加 submodule:subB  <jefshi>
* 7ada136 2021-04-02 17:05:20 | 添加 submodule:subA  <jefshi>
* 468894f 2021-04-02 15:24:36 | main init  <jefshi>

# 缺失子模塊,從新檢出,但子模塊是建立一個臨時分支
$ git submodule update
Submodule path 'subB': checked out '6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638'

$ ll
總用量 0
-rw-rw-r--+ 1 Administrator None 0 4月   2 17:04 init
drwxrwxr-x+ 1 Administrator None 0 4月   2 17:26 subA/
drwxrwxr-x+ 1 Administrator None 0 4月   2 20:50 subB/

$ cd subB
$ git branch
  master
* (頭指針分離於 6bf24b8)

$ cd ..
複製代碼

子模塊單分支狀況下修改更新

進入子模塊目錄進行修改並提交

$ cd main/subA
$ touch first
$ git add .
$ git commit -m "subA:第一次修改"
$ git push

$ git log --oneline
d8346a8 (HEAD -> master) subA:第一次修改
5ae8df5 (origin/master, origin/HEAD) subA init
複製代碼

主項目查看差別,提交併查看 submodule。這裏先說下結論

  • 主項目的 git submodule 的結果前面帶[+]號,表示主項目記錄的提交版本與對應 submodule 的提交記錄不一致
  • 提交版本未同步,則主項目存在差別,能夠從新提交來同步提交版本。若是已近同步,則不須要從新提交(也無法提交)
# 子模塊提交記錄同步前
$ git submodule
+d8346a82a2ba13077d7f25124239ac0d3db5920c subA (heads/master)
 6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638 subB (heads/master)

$ cd ..
$ git status
位於分支 master
您的分支與上游分支 'origin/master' 一致。

還沒有暫存以備提交的變動:
  (使用 "git add <文件>..." 更新要提交的內容)
  (使用 "git checkout -- <文件>..." 丟棄工做區的改動)

        修改:     subA (新提交)

$ git add .
$ git commit -m "submodule subA 提交記錄更更新"
[master 0e3354b] submodule subA 提交記錄更更新
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git submodule
 d8346a82a2ba13077d7f25124239ac0d3db5920c subA (heads/master)
 6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638 subB (heads/master)

$ git push
複製代碼

另外一個主項目(main_copy)從遠程倉庫更新上述操做。這裏先說下結論

  • 主項目下 git pull 只會更新主項目,不會更新子模塊,反過來也是如此
  • 更新子模塊方式一:git submodule update
    • 說明:遍歷子模塊,依次檢出
    • 優勢:子模塊的提交版本是正確的
    • 缺點:子模塊建立臨時分支,不影響子模塊原有分支
  • 更新子模塊方式二:git submodule foreach git pull
    • 說明:遍歷子模塊,依次執行 git pull 操做
    • 優勢:子模塊不會建立臨時分支
    • 缺點:只會更新到最新,致使子模塊的版本不必定與主項目使用的版本一致
  • 通常來說,子模塊是單分支,使用方式二,多分支,看狀況吧
  • 形成上述結果的緣由在於主項目只記錄子模塊的提交版本,不記錄子模塊的提交分支
# 更新前,主項目是無感知的
cd ../main_copy
$ git submodule
 5ae8df5a1fd55d31b0fc7e8bdf7d02fb7591f4bd subA (heads/master)
 6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638 subB (heads/master)

# 更新後,能夠看到提交版本不一致
$ git pull
$ git log --oneline
0e3354b (HEAD -> master, origin/master, origin/HEAD) submodule subA 提交記錄更更新
31287e4 添加 submodule:subB
7ada136 添加 submodule:subA
468894f main init

$ git submodule
+5ae8df5a1fd55d31b0fc7e8bdf7d02fb7591f4bd subA (heads/master)
 6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638 subB (heads/master)

# 更新子模塊方式一
$ git submodule update
Submodule path 'subA': checked out 'd8346a82a2ba13077d7f25124239ac0d3db5920c'

$ cd subA
$ git branch
  master
* (頭指針分離於 d8346a8)

# 子模塊還原
$ git checkout master
$ git reset --hard HEAD
$ cd ..

# 更新子模塊方式二
$ git submodule foreach git pull
進入 'subA'
更新 5ae8df5..d8346a8
Fast-forward
 first | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 first
進入 'subB'
來自 /cygdrive/e/submodule/repo/subB
 + 4f6ad1c...6bf24b8 master     -> origin/master  (強制更新)
已是最新的。

$ cd subA
$ git log --oneline
d8346a8 (HEAD -> master, origin/master, origin/HEAD) subA:第一次修改
5ae8df5 subA init

cd ..
複製代碼

關於主項目記錄的提交版本與子模塊不一致討論,不一致有如下幾種

  • 主項目記錄的提交版本落後於子模塊:這個就是子模塊提交,主項目未提交場景
  • 主項目記錄的提交版本,高於子模塊:這個就是主項目更新,子模塊未更新場景
  • 主項目記錄的提交版本,以及子模塊的提交版本,均落後於子模塊的遠程分支的提交版本:這個就是另外一個本地倉庫未更更新場景

主項目多分支,且不一樣分支間 submodule 有差別

新建一個分支,使 master 分支含有兩個 submodule,另外一個分支只含有一個

$ git branch
* master

$ git log --oneline
0e3354b (HEAD -> master, origin/master, origin/HEAD) submodule subA 提交記錄更更新
31287e4 添加 submodule:subB
7ada136 (onlySubA) 添加 submodule:subA
468894f main init

$ git submodule
 d8346a82a2ba13077d7f25124239ac0d3db5920c subA (heads/master)
 6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638 subB (heads/master)

$ git checkout -b onlySubA 7ada136
$ git log --oneline
7ada136 (HEAD -> onlySubA) 添加 submodule:subA
468894f main init

$ git branch
  master
* onlySubA

$ git submodule
+d8346a82a2ba13077d7f25124239ac0d3db5920c subA (heads/master)
複製代碼

這裏 master 有兩個 submodule,onlySubA 分支只有一個 submodule。接下來看下 onlySubA 分支的提交差別,以及一些相關文件。

這裏先說下結論,能夠看出在 onlySubA 分支上

  • 從 master 切到 onlySubA 分支上時,多餘的子模塊文件夾並不會自動刪除(看切換分支時的提示是刪除文件夾失敗致使)。這點和不是使用 submodule,單純使用兩個 git 倉庫嵌套相同
  • 提交時,由於多餘的子模塊並無被刪除,因此依然會顯示多餘的子模塊文件夾,這個須要注意
  • 【.gitmodules】文件正確,沒有多餘的子模塊信息
  • 【.git/config】文件、【.git/modules】依然保留另外一個子模塊信息。一方面說明這種切換分支不影響本地倉庫,另外一方也提升切換這種分支時的效率
$ git branch
  master
* onlySubA

$ git status
位於分支 onlySubA
還沒有暫存以備提交的變動:
  (使用 "git add <文件>..." 更新要提交的內容)
  (使用 "git checkout -- <文件>..." 丟棄工做區的改動)

        修改:     subA (新提交)

未跟蹤的文件:
  (使用 "git add <文件>..." 以包含要提交的內容)

        subB/

$ cat .gitmodules
[submodule "subA"]
        path = subA
        url = /cygdrive/e/submodule/repo/subA.git

$ cat .git/config
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        ignorecase = true
[remote "origin"]
        url = /cygdrive/e/submodule/repo/main.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master
[submodule "subA"]
        url = /cygdrive/e/submodule/repo/subA.git
        active = true
[submodule "subB"]
        url = /cygdrive/e/submodule/repo/subB.git
        active = true

$ ll .git/modules/
總用量 8
drwxrwxr-x+ 1 Administrator None 0 4月   2 20:35 subA/
drwxrwxr-x+ 1 Administrator None 0 4月   2 20:33 subB/
複製代碼

因爲切分支時多餘的子模塊文件夾並不會被刪除。若是此時誤操做把多餘的子模塊文件夾給提交了,分析下以後從新添加相同子模塊的 submodule 的結果。這裏先說下結論

  • 誤把不須要的子模塊提交後,添加相同子模塊的 submodule 操做失敗(由於文件夾已存在)
  • 由於操做失敗的緣由是文件夾重名,因此通常建議添加 submodule 時不另指定名字
# 背景
$ git status
位於分支 onlySubA
未跟蹤的文件:
  (使用 "git add <文件>..." 以包含要提交的內容)

        subB/

# 誤操做
$ git add .
$ git commit -m "誤把不須要的子模塊提交"

$ git status
位於分支 onlySubA
無文件要提交,乾淨的工做區

# 添加相同的子模塊失敗
$ git submodule add /cygdrive/e/submodule/repo/subB.git
'subB' already exists in the index
複製代碼

主項目和子模塊都是多分支,主項目切分支對子模塊分支的影響

背景:

  • 主項目兩個分支:master、other
  • 子模塊 subA 兩個分支:master、subOther
  • 子模塊的兩個分支指向不一樣的提交版本
  • 主項目分支對應
    • master 對應子模塊的 master
    • other 對應子模塊的 subOther
# 背景:主項目兩個分支,以及分支指向的提交記錄(other 指向 31287e4)
$ git branch
* master
  other

$ git log --oneline
0e3354b (HEAD -> master, origin/master, origin/HEAD) submodule subA 提交記錄更更新
31287e4 (other) 添加 submodule:subB
7ada136 添加 submodule:subA
468894f main init

# 背景:子模塊 subA 兩個分支,以及分支指向的提交記錄
$ cd subA
$ git branch
* master
  subOther

$ git log --oneline
d8346a8 (HEAD -> master, origin/master, origin/HEAD) subA:第一次修改
5ae8df5 (subOther) subA init

$ cd ..
複製代碼

上述主項目和子模塊都是 master 分支,如今主項目切換到 other 分支,觀察子模塊分支狀況,這裏先說下結論

  • 主項目切分支操做影響不到子模塊
  • 一樣的,子模塊切分支也影響不到子模塊,這個自行測試
# 主項目切分支
$ git branch
* master
  other

$ git checkout other
M       subA
切換到分支 'other'

$ git submodule
+d8346a82a2ba13077d7f25124239ac0d3db5920c subA (heads/master)
 6bf24b87a83ee2621e9b9aaf93b6d0f4f2b81638 subB (heads/master)

# 主項目切分支後,子模塊分支狀況
$ cd subA/
$ git branch
* master
  subOther
複製代碼

參考文獻與版本同步

參考文獻:

博客同步版本:

  • 2021-04-07:首發博客
相關文章
相關標籤/搜索