git 子模块的使用姿势

由于需要在项目中内嵌另一个项目的代码,而两个项目是由各自的 git 仓库分别管理的,手动复制更新异常繁琐,因而可以使用 git 的子模块来解决该问题。

引入子模块

在父 git 仓库下添加配置文件 .gitmodules,文件内容配置子模块的相关配置:

1
2
3
4
[submodule "src/b"]
path = src/b
url = ../../org/b.git
branch = embed

如上述示例,该配置文件指定子模块 b,以及在父仓库下检出的路径。分支是可选的,这样在初始化的时候会检出特定分支。

这里需要注意的一点,url 采用相对路径,是因为在项目中使用了 gitlab 的 ci,在父仓库需要检出子模块的时候,需要有子仓库的权限,查阅 gitlab 的官方文档,需要采用相对路径的配置。如果不需要跑 CI,那么就无所谓了,这里的 url 可以使任何合法的 git 仓库地址。

初始化

在其他项目成员拉代码的时候,需要执行如下命令来初始化子模块仓库:

1
git clone  --recursive http://git/a.git

传递 —recursive 参数,在 clone 代码的时候就会初始化仓库中定义的子模块。如果子模块是在后续已克隆代码后引入的,那么需要执行:

1
git submodule update --init --recursive

处理冲突

父仓库中的子模块的当前状态同父仓库中的某一个 commit 绑定,比如在父仓库的 develop 分支,子模块的 id 为 a,master 分支子模块的 id 为 b。那么,从 develop 分支切换到 master 分支时,分支上记录的当前子模块的 commit id 就会发生变化,因而会显示子模块仓库下的文件有更改需要提交。这个时候需要执行如下命令,来检出当前分支记录的子模块状态:

1
git submodule update

同理,在更新代码后,如果子模块在远程仓库被更新,那么拉下最新的代码后就需要重新检出相应的子模块代码。这未免繁琐,项目中的其他成员如果有子模块冲突,没有执行该命令,那么极有可能导致子模块的 commit id 被覆盖,造成子模块回退。

所以,有两种方案,一种是给 git 取别名,替换之前使用的 pull 和 checkout 等命令,在其中增加子模块的更新操作。我认为这种方案不合理,有点蹩脚。

更合适的方案是使用 hook,在特定操作后自动执行。由于是在前端项目,可以方便的使用 husky 来添加 hook,这里其实只需要添加两个 hook,一个是在 merge 操作后,另一个是在 checkout 操作后,这两个操作都代表当前的代码状态发生里变化,意味着子模块的 commit id 有可能被更新,需要检出特定的子模块状态。我的配置如下:

1
2
3
4
5
6
"husky": {
"hooks": {
"post-merge": "git submodule update",
"post-checkout": "git submodule update"
}
},

放在 package.json 中,这样便可保证子模块更新的执行。

子模块协作

关于子模块在父项目中进行子模块的代码更新,在当前的项目中并没有使用到,也是不被允许的操作,暂时没有更好的想法,有需要的时候实践再分享下更好的解决方案。

-EOF-

本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明出处! © 雨落
沉淀,分享。