前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >理解Git Submodules

理解Git Submodules

作者头像
ayqy贾杰
发布2019-06-12 14:58:07
2.5K0
发布2019-06-12 14:58:07
举报
文章被收录于专栏:黯羽轻扬黯羽轻扬

一.背景

经常面临一些场景,想要把大代码库(repo)拆分成多个小的repo,例如:

  • 现有代码库体积庞大,且模块管理混乱,经常容易错改别人的东西
  • 某个模块需要单独构建,比如jQuery项目中的React试点、Node项目中的纯前端部分、Electron项目中的UI部分等等
  • 某个模块是黑盒依赖项,开发中仅依赖其构建后的版本,比如框架类库等

针对诸如此类的情况,一般有3种解决方案:

  • npm package:把依赖项拆出去作为npm package,代码库随之独立出去
  • monorepo:单repo体积庞大没关系,分模块管理好就行
  • git submodules:把依赖项拆分到多个独立repo,作为主repo的submodule

npm package

npm package的优势在于成熟的管理依赖机制,规范且易用,缺点是主项目只能通过package版本号获取独立模块的更新,在主项目需要与子模块联调的场景就会非常麻烦:

代码语言:javascript
复制
主项目:调不通啊
子模块:有点问题,我改一下...改版本号-构建-发布npm package
主项目:更新依赖,再试...还是调不通啊
子模块:还有点问题...

频繁发版比较蠢,可以本地修改构建再拷贝一份过去,但还是有些麻烦。当然,通常可以通过mock接口或数据把联调依赖拆解开,但有时候mock全套API成本比较高,而且假的势必没有真的好用

monorepo

monorepo主张不拆分repo,而是在单repo里统一管理各个模块的构建流程、版本号等等,并且鼓励改别人的代码

这在模块边界清晰、owner明确的项目中很合适(如React、Babel等),但实际应用中,业务repo很难保持清晰的模块边界与依赖关系,此时monorepo就变得理想化了

git submodules

git submodules提供了一种类似于npm package的依赖管理机制,包括添加、删除、更新依赖项等功能,区别在于前者所管理的依赖是子模块的源码,后者管理的是子模块的构建产物。在这一点上,git submodules与monorepo一致(都关心子模块的源码)

这样主项目需要与子模块频繁联调时的麻烦就不复存在了,因为主项目拉取到的submodules都是完整repo,可以直接修改-构建-提交

二.submodules与monorepo

从结构上看,submodules项目的主repo与monorepo很像,相当于把monorepo里的各模块抽离到了独立repo,仅记录主repo所依赖的各模块版本号(commit hash形式)

具体的,monorepo在单repo里存放所有子模块源码(packages/xxx/src),例如:

代码语言:javascript
复制
react/
 packages/
   react-dom/
     /src
   react-reconciler/
     /src
   ...

submodules只在主repo里存放所有子模块“索引”(repo url + branch name + commit hash),例如:

代码语言:javascript
复制
# 主repo的.gitmodules文件
[submodule "react-dom"]
   path = packages/react-dom
   url = https://github.com/facebook/react.git
   branch = master
[submodule "react-reconciler"]
   path = packages/react-reconciler
   url = https://github.com/facebook/react.git
   branch = master
...

主repo里只保留对应的空目录作为子模块的“坑”,并不存其放源码:

代码语言:javascript
复制
react/
 packages/
   react-dom/        # 空目录
   react-reconciler/ # 空目录

拉取所有submodules依赖后,实际目录结构如下:

代码语言:javascript
复制
react/
 packages/
   react-dom/
     /src
   react-reconciler/
     /src
   ...

主repo并不追踪子模块源码,仅记录其版本号(commit hash形式):

代码语言:javascript
复制
# 输出空,表示不追踪子模块src
$ git ls-tree -r master | grep packages/react-dom/src# 查看主repo里被git追踪的子模块坑位
$ git ls-tree -r master | grep ' commit'
160000 commit 3edf340cee50fd4bc918a0a95b438a30447ae042 packages/react-dom
160000 commit 373f207b09a7bf900fa82c3188aeefdc9ce6146c packages/react-reconciler
...

P.S.git ls-tree的输出格式含义,见Output Format

三.具体用法

git submodule命令用来管理子模块:

代码语言:javascript
复制
$ git submodule --help
git-submodule - Initialize, update or inspect submodules
# 初始化
git submodule init
# 增
git submodule add
# 删
git submodule deinit
# 改(版本控制)
git submodule update

添加子模块依赖

代码语言:javascript
复制
$ cd ./react
# 添加依赖
$ git submodule add -b master https://github.com/path-to/react-dom.git src/packages/react-dom

会在主repo创建一个src/packages/react-dom空目录,作为子模块的坑位。实际上,add过程主要发生了3件事:

  • clone一份子模块repo到主repo的git缓存目录里,例如.git/modules/src/packages/react-dom
  • 创建坑位空目录,并把子模块repo的最新commit hash与之关联
  • 在主repo根目录按需创建.gitmodules文件,记录子模块repo地址(url),分支名(branch)以及坑位路径(path

然后提交这些子模块配置:

代码语言:javascript
复制
$ git add ./src/packages/react-dom ./.gitmodules
$ git commit -m "build: add react-dom submodule"
$ git push origin master

接下来本地拉取子模块完成初始化:

代码语言:javascript
复制
# 初始化子模块
$ git submodule update --init

会把子模块repo clone到src/packages/react-dom目录下,实际发生了2件事:

  • 检查缓存是否存在clone好的子模块repo(比如clone来的主repo并没add过,就不存在缓存),按需clone
  • 在子模块repo根目录创建.git/config,记录其repo地址(url

初始化子模块

在clone含有submodules的repo后,要进行初始化:

代码语言:javascript
复制
# 创建一些本地配置
$ git submodule init
# 拉取各子模块repo
$ git submodule update --init

也可以在clone主repo时,通过--recursive选项也能完成上面两步工作:

代码语言:javascript
复制
$ git clone git://gihub.com/path-to/main-repo.git --recursive

拉取子模块更新

更新所有子模块:

代码语言:javascript
复制
$ git submodule foreach submodule update --remote

只有一个子模块的话,不必foreach

代码语言:javascript
复制
$ git submodule update --remote

会拉取子模块对应分支的最新代码,如有更新,占位目录的git状态会发生变化:

代码语言:javascript
复制
$ git status
modified:   src/packages/react-dom (new commits)

实际上是commit hash发生了变化:

代码语言:javascript
复制
$ $ git diff
diff --git a/src/packages/react-dom b/src/packages/react-dom
index 3edf340cee..d056efbc62 160000
--- a/src/packages/react-dom
+++ b/src/packages/react-dom
@@ -1 +1 @@
-Subproject commit 3edf340cee50fd4bc918a0a95b438a30447ae042
+Subproject commit d056efbc62cbf976b4ef83e70d7019fba4506e85

P.S.submodules里的commit hash相当于npm package的dependencies版本号

控制依赖项版本

想要更新主repo所依赖的子模块版本的话,提交这个commit hash变更:

代码语言:javascript
复制
$ git add src/packages/react-dom
$ git commit -m "build: update react-dom submodule"
$ git push origin master

否则不加--remote选项滚回当前依赖版本

代码语言:javascript
复制
$ git submodule update

改动子模块代码

子模块是独立repo,正常操作即可:

代码语言:javascript
复制
$ cd ./packages/react-dom
# 注意切分支,通常是detached状态
$ git checkout master
$ git add .
$ git commit -m 'feat: xxx'
$ git push origin master

之后,主repo就能通过git submodule update --remote拉取到最新版本,再由主repo决定是否要升级其依赖的子模块版本

P.S.关于submodules的更多用法,见7.11 Git Tools – Submodules

四.常见问题

子模块分支处于detached状态

每次执行git submodule update --remote后,子模块会处于detached状态,例如:

代码语言:javascript
复制
$ cd ./packages/react-dom
$ git branch
* (HEAD detached at ac4d1fc)
 master

设计如此,没有太好的解决办法

It’s also important to realize that a submodule reference within the host repository is not a reference to a specific branch of that submodule’s project, it points directly to a specific commit (or SHA1 reference), it is not a symbolic reference such as a branch or tag. In technical terms, it’s a detached HEAD pointing directly to the latest commit as of the submodule add.

因此在改动子项目代码之前,需要手动切换到master分支:

代码语言:javascript
复制
$ git checkout master
$ git add .
$ git commit -m 'feat: xxx'
$ git push origin master

本地子模块缓存

当子模块repo发生迁移时,进行git submodule add可能会遇到本地缓存的问题:

代码语言:javascript
复制
$ git submodule add ssh://XXX.XXX.XXX.XXX:XXXXX/opt/git/fdf.git projets/fdf
A git directory for 'projets/fdf' is found locally with remote(s): origin ssh://git@XXX.XXX.XXX.XXX:XXXXX/opt/git/fdf.git If you want to reuse this local git directory instead of cloning again from ssh://XXX.XXX.XXX.XXX:XXXXX/opt/git/fdf.git use the '--force' option. If the local git directory is not the correct repo or you are unsure what this means choose another name with the '--name' option.

需要先删掉原配置(第2第3步),再本地缓存的子模块信息(第1第4步):

代码语言:javascript
复制
# 1.删掉git缓存及物理文件
$ git rm --cached path_to_submodule
$ rm -rf path_to_submodule# 2.删掉.gitmodules里该子模块的相关配置
$ vi .gitmodules
[submodule "path_to_submodule"]
   path = path_to_submodule
   url = https://github.com/path_to_submodule# 3.删掉.git/config里该子模块相关配置
$ vi .git/config
[submodule "path_to_submodule"]
   url = https://github.com/path_to_submodule# 4.删掉子模块缓存
$ rm -rf .git/modules/path_to_submodule

清理完成之后重新git submodule add即可

P.S.第4步中,子模块的缓存位置可以通过如下命令查看:

代码语言:javascript
复制
$ cat path_to_submodule/.git
gitdir: ../.git/modules/path_to_submodule

P.S.更多常见问题,见Using Git Submodules

参考资料

  • GETTING GIT SUBMODULE TO TRACK A BRANCH
  • 7.11 Git Tools – Submodules
  • Using Git Submodules
  • Git submodule add: “a git directory is found locally” issue
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-10-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端向后 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.背景
    • npm package
      • monorepo
        • git submodules
        • 二.submodules与monorepo
        • 三.具体用法
          • 添加子模块依赖
            • 初始化子模块
              • 拉取子模块更新
                • 控制依赖项版本
                  • 改动子模块代码
                  • 四.常见问题
                    • 子模块分支处于detached状态
                      • 本地子模块缓存
                        • 参考资料
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档