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:首发博客