git中submodule的用法解析

1,086 阅读6分钟

简介

为了方便项目的统一管理,paas平台采用的是pnpm的多包管理机制,目录结构如下client编辑器的sdk、demo编辑器的功能展示、servernest中间层。采用这种方式就会存在代码回滚和commit紊乱的痛点。
image.png
packages下的项目没有单独的进行commit的管理,导致我只想回滚client时会把demoserver的编写的代码也进行回滚。为了解决此问题引入了git submodule子模块的概念,来分别管理clientdemoserver的commit记录,他们也依旧保留在主项目里,代码结构不变。 接下来就来讲解一下submodule的使用

用法

创建

首先我们现在git创建子项目git-submodule-1, 然后采用git submodule add git子仓库在主项目git-submodule引入这个子项目。同时会生成.gitmoudles文件记录子模块之间的映射

# 1. submodule_url表示子模块的地址
# 2. path在存储库中添加子模块的路径
git submodule add <submodule_url> <path>

git submodule add https://github.com/AKclown/git-submodule-1.git

image.png 紧接着我们将主代码仓库push到远程仓库里 image.png

子模块的初始化

在上面我们通过git submodule add ~会同时把~仓库的代码clone下来,但是当其他用户在本地直接git clone 主仓库时是不会同步clone子仓库的代码的。 image.png 我在别的目录clone主仓库代码,可以看到子仓库的README.md并没有clone下来,如下两种方式来clone子仓库代码

方式一

git submodule init  用来初始化本地配置文件
git submodule update 则从该项目中抓取所有数据并检出父项目中列出的合适的提交

image.png 通过如上两个命令就可以将子仓库的代码clone下来了。那么接下来就来分析一下上面两条命令的含义

  1. git submodule init是将.gitmodules的映射复制到./.git/config文件里 image.png git submodule init ~ 还可以指定特定子模块,而不是全部 image.png
  2. git submodule update更新子模块代码到本地。git submodule update ~也可以指定对应的那个子模块
    补充: git submodule update --init 将 git submodule init 和 git submodule update 合并成一步

方式二
在克隆主仓库时,追加--recurse-submodules参数, 它就会自动初始化并更新仓库中的每一个子模块, 包括可能存在的嵌套子模块

git clone git@github.com:AKclown/git-submodule.git --recurse-submodules

同步.gitmodules的配置到config

git submodule sync 命令的作用是同步子模块配置。具体来说,它会将 .git/config 中的子模块 URL 等配置项与 .gitmodules 文件中的配置保持一致。该命令特别有用当你在 .gitmodules 文件中修改了子模块的 URL 或其他配置时,可以使用它来同步这些更改到 Git 的本地配置中

  • 同步子模块 URL
    当你修改了 .gitmodules 文件中某个子模块的远程仓库 URL(比如从 HTTPS 改为 SSH URL),git submodule sync 会确保 .git/config 文件中的子模块配置也更新为新的 URL。

  • 修复子模块配置不一致
    在一些情况下,.gitmodules 文件和 Git 的本地配置文件 .git/config 可能会不同步,比如你手动编辑 .gitmodules 文件或拉取了其他开发者对子模块配置的更改。运行 git submodule sync 可以让本地配置和 .gitmodules 文件保持一致

指定子模块追踪git分支

当我们执行git submodule update --remote会默认更新并检出子模块仓库的 master 分支,有时候我们的主分支并不是master而是main分支,因此可以通过如下命令来修改子模块跟踪仓库的分支

git config -f .gitmodules submodule.packages/git-submodule-1.branch main

# 更新并检出子模块仓库 `main`分支的最新commit
git submodule update --remote

# 当运行不带--remote标志
# git submodule update 仍会将子模块更新为父存储库中指定的提交哈希,而不是分支上的最新提交 ()
git submodule update

当执行git submodule update --remote时,如果子仓库聚焦的分支为feat/ak而不是main分支时会自动切换到main最新的commit上(分离头指针) image.png image.png image.png

当执行不带--remotegit submodule update命令时,就会更新为父存储库中指定的提交哈希。 TIPS: 如果当前子仓库聚焦分支的commit hash父存储库中指定的提交哈希则分支不变。如果子仓库聚焦分支的commit hash高于父存储库中指定的提交哈希则会被切换“分离头指针”(detached HEAD)

在远程创建一个新的commit记录,然后在本地子项目通过git pull拉去最新的commit记录。此时本地项目main分支的commit记录为a398d但是主仓库记录子仓库的commit hash3b16df image.png 执行git submodule update会被切换到3b16df“分离头指针”而不是保持在最新的a398d记录下 image.png 注意了,防止你的代码丢失 image.png

工作流

跟我们日常的git操作一样
在子仓库中

  1. 执行git pull origin main拉取代码
  2. git checkout -b ~创建功能分支
  3. git add/commit/push 提交代码 更新子模块1readme文件,可以并且进行push可以看到在主仓库会生成两个文件git-submodule-1git-submodule-2.这两个文件记录当前子仓库的最新commitId如下图所示 image.png git-submodule-2更新了readme但还未提交,他的commitId会标记为dirty草稿的意思 image.png Tips: 有了这两个文件辅助,我们在进行代码发布版本时,推送一次主仓库是不是就知道了此次version子仓库最新提交commitID

拉取子模块远程更新

  1. 拉取git-submodule-1仓库更新
cd git-submodule-1
git pull origin main

2. 拉取所有子模块更新. foreach => 遍历

> git submodule foreach 'git pull origin main'

删除子模块

有时候我们想要卸载子模块,可以通过如何命令git submodule deinit ~

git submodule deinit packages/git-submodule-1

image.png 可以看出.gitmodules文件还存在子模块映射,以及packages目录下也还存在git-submodule-1代码文件。执行如下代码,即可清除它们

rm -rf .git/modules/packages/git-submodule-1
git rm packages/git-submodule-1

当我们子模块代码有更新还未提交时,如果我们想强制删除子模块,只需要添加--force参数即可

git submodule deinit -f git-submodule-2

image.png

总结

通过上面的代码,我们学会了submodule的基础用法。在有使用到多包的场景也许能够更好的管理每个仓库的commit信息。最终仓库代码,子模块在主仓库以链接的形式展现 image.png

参考文献

  1. Git-工具-子模块
  2. How to Create a React Typescript Monorepo with Git Submodules