git submodule

文章目录

  • 环境
  • 准备
  • 用法
    • 添加子模块
      • 添加b
      • 添加c
      • 提交
      • 总结
    • 其它用户获取子模块
      • 其它
      • 总结
    • 更新子模块内容
      • 方式1:独立更新
        • 其它
      • 方式2:在主模块嵌套下更新
      • 总结
  • 总结
  • 参考

写的有点乱,凑合理解一下吧。另外常用命令总结一下:

  • git submodule add
  • git submodule init
  • git submodule update
  • git submodule foreach git pull
  • git clone xxx --recurse-submodules

环境

  • RHEL 9.4
  • git version 2.43.5

准备

在github里创建3个repository:

  • a :包含 a.txt
  • b :包含 b.txt
  • c :包含 c.txt

现在要把 a 作为主模块,并把 bc 作为其子模块,位于 a 的根目录下。

首先,新建目录 /root/test0822/test1 ,并克隆 a

[root@kai07221 test1]# git clone [email protected]:dukeding/a.git
Cloning into 'a'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (3/3), done.

进入 a 目录下,查看文件结构:

[root@kai07221 a]# tree
.
└── a.txt

0 directories, 1 file

先看一下现在的 .git 目录(一会儿要做对比):

[root@kai07221 a]# ls -a
.  ..  a.txt  .git

[root@kai07221 a]# ls .git
branches  config  description  HEAD  hooks  index  info  logs  objects  packed-refs  refs

准备就绪。

用法

添加子模块

添加b

现在来添加子模块。使用 git submodule add 命令,添加 b

[root@kai07221 a]# git submodule add [email protected]:dukeding/b.git
Cloning into '/root/test0822/test1/a/b'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (3/3), done.

此时,文件结构如下:

[root@kai07221 a]# tree
.
├── a.txt
└── b
    └── b.txt

1 directory, 2 files

可见,已经添加了子模块 b ,其内容都已经拉下来了。

不过,查看主模块此时的git状态:

[root@kai07221 a]# git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged ..." to unstage)
	new file:   .gitmodules
	new file:   b

可见,对于主模块来说,子模块还没有提交。此外,还多了一个 .gitmodules 文件:

[root@kai07221 a]# ls -a
.  ..  a.txt  b  .git  .gitmodules

其内容如下:

[root@kai07221 a]# cat .gitmodules
[submodule "b"]
	path = b
	url = git@github.com:dukeding/b.git

.gitmodules 文件记录了子模块 b 的基本信息。

另外,作为对比,在 .git 目录下,多了一个 modules 目录:

[root@kai07221 a]# ls .git
branches  config  description  HEAD  hooks  index  info  logs  modules  objects  packed-refs  refs

其中包含了子模块的详细信息:

[root@kai07221 a]# ls .git/modules/
b

注: b 是一个目录,和一般的 .git 目录结构一样,包含了repo的详细信息。

进到子模块查看:

[root@kai07221 b]# git remote -v
origin	git@github.com:dukeding/b.git (fetch)
origin	git@github.com:dukeding/b.git (push)
[root@kai07221 b]# git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
[root@kai07221 b]# git branch
* main

和一般的repo一样,并没什么不同。

添加c

同理,再添加 c

[root@kai07221 a]# git submodule add [email protected]:dukeding/c.git
Cloning into '/root/test0822/test1/a/c'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (3/3), done.

此时, .gitmodules 文件内容如下:

[root@kai07221 a]# cat .gitmodules
[submodule "b"]
	path = b
	url = git@github.com:dukeding/b.git
[submodule "c"]
	path = c
	url = git@github.com:dukeding/c.git

.git/modules 目录如下:

[root@kai07221 a]# ls .git/modules/
b  c

可见, .gitmodules 文件和 .git/modules 目录添加了 c 的信息。

提交

注意,对于主模块而言, bc 都还没提交:

[root@kai07221 a]# git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged ..." to unstage)
	new file:   .gitmodules
	new file:   b
	new file:   c

我们来提交一下,并push回去:

git commit -m "add b and c"
git push origin main

OK,至此,一切正常。

在github上:

git submodule_第1张图片

注意: bc 指向的是commit号,不是某个branch。

总结

  • 通过 git submodule add 命令来添加子模块。
  • 子模块的状态和branch都是正常的。
  • .gitmodules 文件和 .git/modules/<子模块> 目录包含了子模块的信息。
  • .gitmodules 和子模块需要commit。
  • 在主模块里,子模块指向的是某个commit号。

其它用户获取子模块

新建目录 /root/test0822/test2 (模拟另外一个用户)。

首先克隆 a

git clone git@github.com:dukeding/a.git

进入 a 目录下,查看文件结构:

[root@kai07221 a]# tree
.
├── a.txt
├── b
└── c

2 directories, 1 file

可见,虽然有 bc 目录,但都是空目录。

此时有 .gitmodules 文件(这是test1用户提交的):

[root@kai07221 a]# ls -a
.  ..  a.txt  b  c  .git  .gitmodules
[root@kai07221 a]# cat .gitmodules
[submodule "b"]
	path = b
	url = git@github.com:dukeding/b.git
[submodule "c"]
	path = c
	url = git@github.com:dukeding/c.git

但并没有 .git/modules 目录。

要pull bc 的内容,需要运行 git submodule initgit submodule update 命令:

[root@kai07221 a]# git submodule init
Submodule 'b' (git@github.com:dukeding/b.git) registered for path 'b'
Submodule 'c' (git@github.com:dukeding/c.git) registered for path 'c'
[root@kai07221 a]# git submodule update
Cloning into '/root/test0822/test2/a/b'...
Cloning into '/root/test0822/test2/a/c'...
Submodule path 'b': checked out 'f11a1936c3b276885e50fefe96231025ef9881b6'
Submodule path 'c': checked out 'b504d5b012c468fb3d7363bd080b1b83d80823b8'

注意,commit号跟我们在github上看到的是一致的。

现在,bc 就有内容了:

[root@kai07221 a]# tree
.
├── a.txt
├── b
│   └── b.txt
└── c
    └── c.txt

2 directories, 3 files

看上去一切OK,但是要小心,此时进入 b ,查看状态:

[root@kai07221 b]# git status
HEAD detached at f11a193
nothing to commit, working tree clean

可见其状态是detached。查看其branch可见:

[root@kai07221 b]# git branch
* (HEAD detached at f11a193)
  main

也就是说,现在子模块并不在某一个branch上,而是在一个commit号上(参见github)。

切换到 main branch:

[root@kai07221 b]# git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.

现在状态就OK了:

[root@kai07221 b]# git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

回到 a ,状态仍然OK(因为 b 切换branch时,并没有实际内容的修改):

[root@kai07221 a]# git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

c 也同理。

其它

那么有人可能会想,如果不在主模块里使用 git submodule update ,而是直接在子模块里pull,会怎么样?

经实测,这样做无效,也不报错,但是什么也pull不下来, b 目录下仍然是空的。

这是因为没有初始化子模块,所以即使在 b 目录下,仍然认为是在主模块里, b 目录只是一个普通的空目录:

[root@kai07221 b]# git remote -v
origin	git@github.com:dukeding/a.git (fetch)
origin	git@github.com:dukeding/a.git (push)

回到主模块( a 目录下),先 git submodule init 一下,然后再回来子模块, git remote -v 得到的仍然是a。只有 git submodule update 之后,得到的才是b。

总结

  • 克隆主模块时,不会获取子模块内容(子模块只是一个普通空目录,且仍然属于主模块)。
  • 需要 git submodule initgit submodule update 来获取子模块内容(获取的是指定的commit号,参见github)。
  • 子模块处于detached状态,要把它切换到某个branch(但是要小心,参见下面更新子模块)。

更新子模块内容

方式1:独立更新

子模块是独立的git repo,其更新跟一般git repo并无差异。

在github里,更新 b repo里面 b.txt 文件(和主模块无关,是独立更新),并提交。

git submodule_第2张图片

  • 已存在用户(test1和test2):在主模块下, git pull origin maingit submodule update 都无法更新子模块。这是因为主模块无法感知到子模块的变化。
  • 新用户:也同理。新用户获取的子模块,其内容仍然是旧的。

其实这很容易理解。因为主模块里记录的子模块commit号没变(参见github)。

要想更新子模块内容,以test1用户为例,需要先到子模块里,pull一下:

[root@kai07221 b]# git pull origin main
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (3/3), 882 bytes | 882.00 KiB/s, done.
From github.com:dukeding/b
 * branch            main       -> FETCH_HEAD
   f11a193..61bfea2  main       -> origin/main
Updating f11a193..61bfea2
Fast-forward
 b.txt | 1 +
 1 file changed, 1 insertion(+)

然后回到主模块,可见其状态发生了变化:

[root@kai07221 a]# git status
On branch main
Your branch is up to date with 'origin/main'.

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

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

这是因为主模块记录的子模块信息和实际的子模块不一致了,换句话说,主模块感知到子模块发生了变化。

现在,就可以更新主模块了:

git add b
git commit -m "a update b"
git push origin main

这样,记录的子模块信息就更新了。github上的commit号如下:

git submodule_第3张图片

现在,test2用户来获取更新。

在test2用户的主模块下做pull操作:

[root@kai07221 a]# git pull origin main
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 2 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (2/2), 295 bytes | 295.00 KiB/s, done.
From github.com:dukeding/a
 * branch            main       -> FETCH_HEAD
   8d89f97..4102acd  main       -> origin/main
Fetching submodule b
From github.com:dukeding/b
   f11a193..61bfea2  main       -> origin/main
Updating 8d89f97..4102acd
Fast-forward
 b | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

但此时 b.txt 内容并不会更新。前面说过,对主模块的操作不会影响子模块。pull所得到的,是主模块所记录的子模块信息的更新。换句话说,对于test2用户,现在主模块所记录的子模块信息,和实际的子模块不一致了。

git submodule update 操作,令二者保持一致:

[root@kai07221 a]# git submodule update
Submodule path 'b': checked out '61bfea2af95a91c2e06f9d616675ab734107ac37'

可见, b 指向了commit号 61bfea2af95a91c2e06f9d616675ab734107ac37 (主模块所记录,参见github)

此时, b.txt 的内容就更新了。

和前面类似,此时进入到 b 目录,status变成detached:

[root@kai07221 b]# git status
HEAD detached at 61bfea2
nothing to commit, working tree clean

但这时一定要小心,如果切换到 main branch:

[root@kai07221 b]# git checkout main
Previous HEAD position was 61bfea2 Update b.txt
Switched to branch 'main'
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

因为 main 是一个已存在的branch,且没有更新(仍然指向 f11a193... 那个commit号),切到该branch,会导致 b.txt 的内容变旧。(从上面输出结果也可以看到 Your branch is behind 'origin/main' by 1 commit 。)

而且主模块的status会不OK(因为记录的子模块信息和实际子模块内容不一致了)。此时如果在主模块下运行 git submodule update ,又会把子模块更新到 61bfea2... ……二者可以循环往复。

解决方法是,在子模块下运行 git pull 命令:

[root@kai07221 b]# git pull origin main
From github.com:dukeding/b
 * branch            main       -> FETCH_HEAD
Updating f11a193..61bfea2
Fast-forward
 b.txt | 1 +
 1 file changed, 1 insertion(+)

现在, b.txt 内容更新了,主模块和子模块的状态也都OK了。

其它

如果不运行 git submodule update ,而是分别在主模块和子模块做pull,是否可行呢?

经测试,这么做也OK。

所以,看起来 git submodule update 的作用,就是让子模块和“主模块所记录的子模块信息”保持一致。

当然,如果是一个新的用户test3,就不会有问题。其步骤为:

  1. 克隆主模块
  2. git submodule initgit submodule update 更新子模块
  3. 子模块的status不OK,到子模块下切换branch到main即可

方式2:在主模块嵌套下更新

在test1用户下,在 a/b 目录下更新 b.txt ,提交并push。

回到主模块 a ,查看状态:

[root@kai07221 a]# git status
On branch main
Your branch is up to date with 'origin/main'.

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

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

可见,至此,和方式1一样,后面的操作不再赘述。

总结

  • 在外部(远程)更新子模块,主模块不会感知。
  • 在内部(本地)更新子模块,主模块才会感知。
  • git submodule update 并不会更新某个branch,而是更新到了一个commit上,所以子模块会变成detached状态。如果要切回某个branch,注意代码是不是最新的(如果是本身已有的branch,考虑pull一下,确保获取最新内容)。

总结

  • 在主模块里使用 git submodule add 命令添加子模块
  • 在主模块里记录了子模块所指向的commit号
  • 更新子模块时内容,需要考虑同时更新主模块(更新其所记录的子模块信息)
  • 其它用户更新主模块时(比如pull操作),不会更新子模块内容,但会更新所记录的子模块信息(如果记录的信息有变化,将会导致二者不一致)
  • 使用 git submodule update 来更新子模块内容,其本质是按照所记录的子模块信息(commit号)来更新,更新后,子模块指向该commit号
  • 接上条,子模块指向某commit号,其状态是detached
  • 也可以直接在子模块里直接操作(比如pull)
  • 如果是新克隆的主模块,需要先运行 git submodule init 来初始化子模块,然后使用 git submodule update 来更新子模块内容

参考

  • https://blog.csdn.net/qq_38880380/article/details/123288706
  • https://blog.csdn.net/Java0258/article/details/108532507

你可能感兴趣的:(git)