文章目录
- 环境
- 准备
- 用法
- 添加子模块
- 添加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
作为主模块,并把 b
和 c
作为其子模块,位于 a
的根目录下。
首先,新建目录 /root/test0822/test1
,并克隆 a
:
[root@kai07221 test1]# git clone git@github.com: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 git@github.com: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 = 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 git@github.com: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
的信息。
提交
注意,对于主模块而言, 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 git@github.com: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 = git@github.com:dukeding/b.git
[submodule "c"]
path = c
url = git@github.com:dukeding/c.git
但并没有 .git/modules
目录。
要pull b
和 c
的内容,需要运行 git submodule init
和 git 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上看到的是一致的。
现在,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 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 init
和git submodule update
来获取子模块内容(获取的是指定的commit号,参见github)。 - 子模块处于detached状态,要把它切换到某个branch(但是要小心,参见下面更新子模块)。
更新子模块内容
方式1:独立更新
子模块是独立的git repo,其更新跟一般git repo并无差异。
在github里,更新 b
repo里面 b.txt
文件(和主模块无关,是独立更新),并提交。
- 已存在用户(test1和test2):在主模块下,
git pull origin main
和git 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 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,就不会有问题。其步骤为:
- 克隆主模块
git submodule init
和git submodule update
更新子模块- 子模块的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