Git submodule 速成
简单记录一下 git submodule 的用法,注意不是 subtree。
添加子模块
可以使用 submodule 功能添加子模块仓库
1 | git submodule add <子模块仓库的url> |
默认会在主仓库下创建一个与子模块仓库同名的文件夹,用于存储子模块的所有内容。 也可以指定子模块使用的文件夹
1 | git submodule add <子模块仓库的url> subdir |
子模块所使用的仓库url最好是公开可访问的,并且最好没有修改推送权限,只能单向接受远程更新的推送,这样最省事。
克隆含子模块的仓库
在克隆一个含有子模块的git仓库时,默认对主仓库的
git clone
命令不会把子模块也拉取下拉,只会得到一个包含子模块信息的.gitsubmodules
文件和子模块对应的空目录,这是考虑到实际用途中某些子模块可能是可选项而非必选项。
可以在主仓库中使用下面的命令进行子模块的初始化和更新
1 | git submodule init |
这两个命令可以合起来
1 | git submodule update --init |
如果存在多个子模块,上述命令默认对所有子模块进行,但是我们也可以指定只对某个子模块执行操作,例如
1 | git submodule init <submodule的文件夹的相对路径> |
当然,如果已知主仓库中存在子模块,并且全部需要拉取下拉,直接在git clone
命令中加上如下选项即可
1 | git clone --recurse-submodules <主项目Git仓库地址> |
子模块底层逻辑
一个被视作子模块的git仓库并不知道它被视作子仓库,这种父子关系只由主仓库单向维护。
在子模块的远程仓库中不会体现主仓库的任何信息,但是在本地有一些区别:
子模块不会使用单独的 .git/
文件夹存储数据,而是会共用主仓库的 .git/
文件夹,在其中新建一个子模块的对应目录。
具体实现是:子模块目录下存储一个特殊的 .git
文本文件,其中的内容形如 1
gitdir: ../.git/modules/subdemo
也就是指向主仓库.git/
文件下的对应目录。
git 对于含子模块的仓库,主要通过一个特殊的 .gitsubmodule
文本文件来管理,其中记录了所有子模块的核心信息(名称,路径,url,可能还有分支信息),内容形如
1 | [submodule "subdemo"] |
这个文件需要被主仓库纳入正常的版本管理中。
主仓库其实也不会过多关注子模块的细节,它所知道的除了
.gitsubmodule
中的信息,还有当前子模块中所处的版本节点信息,以及现在的子模块中是否存在更改,仅此而已。在主模块的远程仓库中,对应子模块的子目录其实只是一个链接,指向子模块远程仓库中的某个提交版本,并没有冗余地存储子模块的数据。
在子模块之外的目录中执行 git
命令,默认都是对主仓库进行的,并不会过多关注子模块的细节,只是显示子模块的整体状态(除了
git submodule xxx
形式的命令)。在子模块的目录中执行的git命令则是对子模块仓库进行的。
在子模块之外的目录中通过 git status
查看,会发现 git
对子模块所在的整个目录有特殊处理,并不会关注其中的细节。
更新子模块
更新子模块的方式其实很简单:直接进入子模块对应的目录,此时所有的git命令都是对子模块进行的,正常在本地修改,然后提交推送,或者直接拉取远程的更新即可。
在本地主动更新子模块其实是不太合理的:主仓库作为子模块的使用者,对于子模块保持只读状态即可,不应该越俎代庖地负责对子模块主动更新,只要被动地接受远程仓库的更新即可。
由于主仓库记录了子仓库对应的提交版本,一旦子仓库发生任何版本更新,主仓库中就会显示子模块进行了更改(但是默认不显示细节),我们也需要更新主仓库,让它记录最新的子仓库版本。
同样的逻辑,如果远程的主仓库进行了更新,它所记录的子模块版本可能与本地不同,在本地主仓库执行git pull
时也会显示出来,但是这只是主仓库所记录的子模块版本号的变动,主仓库不会主动为我们拉取子模块的更新,只会显示存在差异。
除此之外,git submodules
命令还支持在主仓库中直接对子仓库拉取远程的更新,例如
1 | git submodule update --remote |
但是这种操作很麻烦,它似乎会主动追踪master分支,要进行相应的配置或者加上--rebase
选项等,否则很容易导致子模块仓库的HEAD在更新之后处于游离状态,还是直接进入子目录处理比较安全省事。
必须谨慎处理主仓库和子模块仓库的更新和远程同步,否则可能出现错误,最好保持子模块的版本稳定,不要对其频繁更新。
移除子模块
删除子模块的步骤比较复杂,需要处理各个地方残留的配置,至少包括如下步骤:(可能还有残留)
rm -rf subdir/
在文件系统中删除子模块目录及源码vim .gitmodules
删除项目目录下.gitmodules 文件中对应子模块的相关条目vim .git/config
删除仓库级配置文件中子模块相关条目rm .git/modules/xxx
删除.git/
中的子模块对应目录
然后将修改提交即可。