> ## Content Index
> Fetch the complete content index at: https://djdog.cc/llms.txt
> Use this file to discover other available public pages before exploring further.

# Git 子模块 submodule 的使用
- URL: https://djdog.cc/blog/2023/git-submodule/
- Published: 2023-08-01T03:01:42.000Z
- Updated: 2024-10-22T04:00:00.000Z
- Description: git submodule 命令的基础知识和使用中容易遇见的坑
- Author: DJDOG
- Tags: 开发工具, Git, #toc

## 背景

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

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

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

## 使用流程

假设我们有两个项目，分别是 `project-main` 和 `project-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` ，输入：

```bash
git submodule add <submodule_url>

```

终端中跳出这样的字样：

```bash
➜ 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), 完成.

```

此时项目仓库中会多出两个文件：`.gitmodules` 和 `project-sub-1`。

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

```bash
[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`，这样会递归地将项目中所有子模块的代码拉取。

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

```bash
git submodule init
git submodule update
# 或者将上面的两条合成一步来使用
git submodule update --init

```

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

```bash
git submodule update --init --recursive

```

### 更新 submodule

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

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

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

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

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

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

```

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

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

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

```bash
➜ 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` 自然也不会将子模块更新到新版本。

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

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

```bash
git submodule foreach 'git pull'

```

### 删除 submodule

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

```bash
git submodule deinit project-sub-1
git rm project-sub-1

```

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

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

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

```

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

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

```

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

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

```bash
 rm -rf /path/to/project-sub

```

[ Github:mkrl/misbrands](https://github.com/mkrl/misbrands?tab=CC0-1.0-1-ov-file&ref=djdog.cc)