0


git submodule

文章目录

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

  • 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

作为主模块,并把

b

c

作为其子模块,位于

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 <file>..." 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 = [email protected]: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    [email protected]:dukeding/b.git (fetch)
origin    [email protected]: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 = [email protected]:dukeding/b.git
[submodule "c"]
    path = c
    url = [email protected]:dukeding/c.git

.git/modules

目录如下:

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

可见,

.gitmodules

文件和

.git/modules

目录添加了

c

的信息。

提交

注意,对于主模块而言,

b

c

都还没提交:

[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 <file>..." 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上:

在这里插入图片描述

注意:

b

c

指向的是commit号,不是某个branch。

总结

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

其它用户获取子模块

新建目录

/root/test0822/test2

(模拟另外一个用户)。

首先克隆

a

git clone [email protected]:dukeding/a.git

进入

a

目录下,查看文件结构:

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

2 directories, 1 file

可见,虽然有

b

c

目录,但都是空目录。

此时有

.gitmodules

文件(这是test1用户提交的):

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

但并没有

.git/modules

目录。

要pull

b

c

的内容,需要运行

git submodule init

git submodule update

命令:

[root@kai07221 a]# git submodule init
Submodule 'b'([email protected]:dukeding/b.git) registered for path 'b'
Submodule 'c'([email protected]: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上看到的是一致的。

现在,

b

c

就有内容了:

[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    [email protected]:dukeding/a.git (fetch)
origin    [email protected]: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

文件(和主模块无关,是独立更新),并提交。

在这里插入图片描述

  • 已存在用户(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 <file>..." to update what will be committed)(use "git restore <file>..." 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号如下:

在这里插入图片描述

现在,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 mainFrom 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 <file>..." to update what will be committed)(use "git restore <file>..." 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

本文转载自: https://blog.csdn.net/duke_ding2/article/details/141437889
版权归原作者 蓝黑2020 所有, 如有侵权,请联系我们删除。

“git submodule”的评论:

还没有评论