Git 子模块 submodule 的使用
背景
面对比较复杂的项目,我们有可能会将代码根据功能拆解成不同的子模块。主项目对子模块有依赖关系,却又并不关心子模块的内部开发流程细节。
这种情况下,通常不会把所有源码都放在同一个 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
,输入:
1 | git submodule add <submodule_url> |
终端中跳出这样的字样:
1 | ➜ git submodule add https://github.com/username/project-sub-1.git |
此时项目仓库中会多出两个文件:.gitmodules
和 project-sub-1
。
前者的内容是这样的,事实上就是子模块的相关信息;而后者那个文件,实际上保存的是子模块当前版本的版本号信息。
1 | [submodule "project-sub-1"] |
同时在 .git/config
文件中也会多出一些信息,在 .git/modules
文件夹下也会多出一份内容。
通常这个时候就会在主项目中使用 git commit -m 'add submodule xxx'
来进行一次提交,表示在新版本的主项目
中引入了这个子模块1
。
拉取 submodule
在上述步骤创建子模块的过程中,会自动将相关代码克隆到对应路径,但对于后续使用者而言,对于主项目使用普通的 clone
操作并不会拉取到子模块中的实际代码。
如果希望子模块代码也获取到,一种方式是在克隆主项目的时候带上参数 --recurse-submodules
,这样会递归地将项目中所有子模块的代码拉取。
若是已经单独拉取了主项目,则可以使用下面的命令来拉取子模块:
1 | git submodule init |
如果还要初始化、抓取并检出任何嵌套的子模块:
1 | git submodule update --init --recursive |
更新 submodule
对于子模块而言,子模块自己就是一个完整的 Git 仓库,按照正常的 Git 代码管理规范操作即可。
而对于主项目,主项目是与子项目仓库的其中一个版本绑定的,那么当子项目内容发生变化时,通常分为以下三种情况:
1. 本地主项目文件夹中的子模块发生未跟踪的变动
在开发环境中,若是直接修改了子模块文件夹中的内容,就会导致这种情况发生。
此时在主项目中使用 git status
就能看到子模块中尚未 commit
的变动,但要是在主项目中 git add/commit
是不会产生影响的。
1 | ➜ git status |
此时只需要进入子模块文件夹,按照子模块的版本提交就好了。
2. 本地的子模块有版本变化
在第一种情况中,将子模块的改动提交后便会出现这种情况。此时本地和远程仓库中的子模块都是更新后的版本,但主项目绑定的却是老版本的子模块。这时如果使用git status
就能看到:
1 | ➜ git status |
这时在主项目中 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 | git submodule deinit project-sub-1 |
若由于本地的子模块有未提交的改动,则需要使用 --force
参数。
执行 git submodule deinit project-sub-1
命令的实际效果,是自动在 .git/config
中删除了以下内容:
1 | [submodule "project-sub-1"] |
执行 git rm project-sub-1
的效果,是移除了 project-sub-1
文件夹,并自动在 .gitmodules
中删除了以下内容:
1 | [submodule "project-sub-1"] |
此时便完成了子模块的删除,提交代码就可以了。
如果要在删除了一个子模块后,打算在同一位置加入一个新的同名子模块,则需要运行:
1 | rm -rf /path/to/project-sub |
Git 子模块 submodule 的使用