Git 子模块 submodule 的使用

Git 子模块 submodule 的使用

背景

面对比较复杂的项目,我们有可能会将代码根据功能拆解成不同的子模块。主项目对子模块有依赖关系,却又并不关心子模块的内部开发流程细节。

这种情况下,通常不会把所有源码都放在同一个 Git 仓库中。

Git 通过子模块来解决这个问题。 子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。

使用流程

假设我们有两个项目,分别是 project-mainproject-sub-1,其中 project-main 代表主项目project-sub-1 代表子模块1

其中 project-main 的远程仓库地址为 https://github.com/username/project-main.git,而 project-sub-1 的远程仓库地址为 https://github.com/username/project-sub-1.git

接下来,我们希望在 project-main 中添加 project-sub-1 ,而又保持 project-sub-1 自身独立的版本控制。

创建 submodule

进入 project-main ,输入:

1
git submodule add <submodule_url>

终端中跳出这样的字样:

1
2
3
4
5
6
➜ git submodule add https://github.com/username/project-sub-1.git
正克隆到 '/path/to/project-main/project-sub-1'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
展开对象中: 100% (3/3), 完成.

此时项目仓库中会多出两个文件:.gitmodulesproject-sub-1

前者的内容是这样的,事实上就是子模块的相关信息;而后者那个文件,实际上保存的是子模块当前版本的版本号信息。

1
2
3
[submodule "project-sub-1"]
path = project-sub-1
url = https://github.com/username/project-sub-1.git

同时在 .git/config 文件中也会多出一些信息,在 .git/modules 文件夹下也会多出一份内容。

通常这个时候就会在主项目中使用 git commit -m 'add submodule xxx' 来进行一次提交,表示在新版本的主项目中引入了这个子模块1

拉取 submodule

在上述步骤创建子模块的过程中,会自动将相关代码克隆到对应路径,但对于后续使用者而言,对于主项目使用普通的 clone 操作并不会拉取到子模块中的实际代码。

如果希望子模块代码也获取到,一种方式是在克隆主项目的时候带上参数 --recurse-submodules,这样会递归地将项目中所有子模块的代码拉取。

若是已经单独拉取了主项目,则可以使用下面的命令来拉取子模块:

1
2
3
4
git submodule init
git submodule update
# 或者将上面的两条合成一步来使用
git submodule update --init

如果还要初始化、抓取并检出任何嵌套的子模块:

1
git submodule update --init --recursive

更新 submodule

对于子模块而言,子模块自己就是一个完整的 Git 仓库,按照正常的 Git 代码管理规范操作即可。

而对于主项目,主项目是与子项目仓库的其中一个版本绑定的,那么当子项目内容发生变化时,通常分为以下三种情况:

1. 本地主项目文件夹中的子模块发生未跟踪的变动

在开发环境中,若是直接修改了子模块文件夹中的内容,就会导致这种情况发生。

此时在主项目中使用 git status 就能看到子模块中尚未 commit 的变动,但要是在主项目中 git add/commit 是不会产生影响的。

1
2
3
4
5
6
7
8
9
➜ git status
位于分支 master
您的分支与上游分支 'origin/master' 一致。
尚未暂存以备提交的变更:
(使用 "git add <文件>..." 更新要提交的内容)
(使用 "git checkout -- <文件>..." 丢弃工作区的改动)
(提交或丢弃子模组中未跟踪或修改的内容)
修改: project-sub-1 (未跟踪的内容)
修改尚未加入提交(使用 "git add" 和/或 "git commit -a"

此时只需要进入子模块文件夹,按照子模块的版本提交就好了。

2. 本地的子模块有版本变化

在第一种情况中,将子模块的改动提交后便会出现这种情况。此时本地和远程仓库中的子模块都是更新后的版本,但主项目绑定的却是老版本的子模块。这时如果使用git status 就能看到:

1
2
3
4
5
6
7
8
➜ git status
位于分支 master
您的分支与上游分支 'origin/master' 一致。
尚未暂存以备提交的变更:
(使用 "git add <文件>..." 更新要提交的内容)
(使用 "git checkout -- <文件>..." 丢弃工作区的改动)
修改: project-sub-1 (新提交)
修改尚未加入提交(使用 "git add" 和/或 "git commit -a"

这时在主项目中 git add/commit 便可以更新主项目中 .gitmodules 中的子项目版本信息。

3. 远程仓库中的子模块有更新

在非本地子模块在将自己的版本更新后,推送到远程仓库中。这时本地的子模块并没有发生变化,主项目是不会受到任何影响的。

主项目虽然可以用 git submodule update 来更新子模块的代码,但这个命令会按照 .gitmodules 中主项目记录的版本进行更新。

但现在这种情况,主项目记录的子模块版本并没有变化, git submodule update 自然也不会将子模块更新到新版本。

此时,一般需要进入到子模块的目录主动拉取新版,再按照第二种情况进行操作。

也可以使用下面的命令,将所有子模块全部更新。

1
git submodule foreach 'git pull'

删除 submodule

按照当前的例子,从 project-main 中删除 project-sub-1,应该使用:

1
2
git submodule deinit project-sub-1
git rm project-sub-1

若由于本地的子模块有未提交的改动,则需要使用 --force 参数。

执行 git submodule deinit project-sub-1 命令的实际效果,是自动在 .git/config 中删除了以下内容:

1
2
[submodule "project-sub-1"]
url = https://github.com/username/project-sub-1.git

执行 git rm project-sub-1 的效果,是移除了 project-sub-1 文件夹,并自动在 .gitmodules 中删除了以下内容:

1
2
3
[submodule "project-sub-1"]
path = project-sub-1
url = https://github.com/username/project-sub-1.git

此时便完成了子模块的删除,提交代码就可以了。

如果要在删除了一个子模块后,打算在同一位置加入一个新的同名子模块,则需要运行:

1
rm -rf /path/to/project-sub

Git 子模块 submodule 的使用

https://djdog.cc/2023/Git-submodule/

作者

小岛秀儿

发布于

2023-08-01

更新于

2023-08-01

许可协议

评论