git submodule

2,711 阅读3分钟

应用场景

比如工程中需要以源码级别来使用某个第三方依赖库,这个依赖库和宿主工程都是同一个人维护的。这个时候既想把它们当作两个独立的工程,又想合在一起使用,那就可以使用git submodule指令来满足这个需求。

使用方式

在宿主工程中引入第三方依赖
  • main-module 宿主工程
  • sub-module 子模块依赖
git submodule add [options] https://github.com/xxx/sub-module.git

文件结构变化:

  • package.json
  • sub-module
    • xxx
    • aaa
  • .gitmodules

新增了一个sub-module同名文件夹,和一个隐藏文件.gitmodules

.gitmodules内容

[submodule "sub-module"]
	path = sub-module
	url = https://github.com/xxx/sub-module.git

宿主工程将本地更新推送到远端:

  • sub-module@版本号
  • .gitmodules
  • xxxx

点击sub-module会跳到sub-module的代码仓库。

克隆含有子模块的宿主工程

直接克隆: git clone xxxx/main.git

  • sub-module
  • .gitmodules
  • xxx

或者加 --recurse-submodules 参数,直接拉取所有子模块

此时克隆的子模块,只是一个空的文件夹,没有任何子模块的代码。

拉取子模块代码
// 1. 初始化本地配置文件

git submodule init

// 2. 根据主模块记录的版本信息,拉取子模块内容
git submodule update

// 初始化+拉取子模块内容+拉取嵌套子模块内容
git submodule update --init --recursive
子模块产生了更新内容
  1. 当前子模块内部产生了未跟踪的内容变动->比如在宿主工程中直接改了子模块代码。

这里我改了子模块sub-module-2的内容,在main-module宿主工程中使用git status

➜  main-module git:(master) git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
  (commit or discard the untracked or modified content in submodules)

	modified:   sub-module-2 (modified content)

no changes added to commit (use "git add" and/or "git commit -a")

可以看到main-module可以捕捉到子模块的文件变动,但是使用git add/commit指令无法对变更内容产生影响。

如何将变更内容推送到sub-module-2的远端?

进入sub-module-2目录,再执行git add/commit推送到远端。

  1. 子模块远端产生了更新内容

这种情况通常是主模块和子模块分开维护,且二者更新频率不一致导致的(比较常见)。

这时由于主模块中.git/module记录的版本同子模块的版本保持一致,主模块就无法主动探测到子模块是否发生了变化。

如何去主动的更新子模块内容?

方案一:

进入子模块

cd sub-module-2
git pull

方案二:

暴力更新所有子模块内容,前提是子模块分支统一

// main-module
git submodule foreach 'git pull'
删除子模块
// 删除子模块文件内容
git submodule deinit sub-module

// 1. 删除sub-module文件夹
// 2. 删除.gitmodules文件中关于sub-module的配置
git rm --cached sub-module
其他常用的指令
  • status [--cached] [--recursive] [--] [<path>...]:查看子模块状态
  • summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]:插件子模块简要(模块与版本的对应情况)
  • sync [--recursive] [--] [<path>...]:根据给出的url,修改所有/指定模块的git url参数

可能遇到的问题

由有子模块的分支,切换到没有子模块的分支,发现有未关联的子模块文件夹。
// 切换分支时添加--recurse-submodules
git checkout --recurse-submodules master
之前拉取过子模块,再次拉取时报A git directory for 'sub-module' is found locally with remote(s):

方案一:走submodule删除机制 方案二:进入.git/module删除对应子模块。

总结

从功能上看:git submodule主要解决了代码分仓的问题,将一个工程拆分成多个工程各自独立维护。

从使用上看:git submodule的仓库很有manorepo风格,使用上感觉和lerna很类似,只不过lerna作为一个工具功能上要比git submodule强大很多,当然二者解决的问题不是一致的,不要混淆。