公共模块管理之 Git Submodule 使用总结
公共模块管理之 Git Submodule 使用总结
Write By CS逍遥剑仙
我的主页: csxiaoyao.com
GitHub: github.com/csxiaoyaojianxian
Email: sunjianfeng@csxiaoyao.com
QQ: 1724338257
1. 公共模块管理:npm
or git-submodule
?
在企业级项目开发中,对于较复杂的项目,不可避免地会引用一些公共基础库,或是将代码拆解成公共模块和多个子模块进行管理,主项目工程中的子模块需要对公共模块有依赖关系,却又不必关心公共模块内部的开发流程细节,若直接将公共代码复制到项目中显然是不合适的,因为不方便更新维护。关于公共模块的管理有很多成熟的实践,常见的有 npm
和 git submodule
两类方式:
1.1 npm 等包管理工具
前端开发者对 Node.js 的包管理工具 npm
应该再熟悉不过了,此外,Java 的 Maven
, php 的 composer
等包管理工具皆同理,有效解决了原先需要插件依赖时,手动在网上搜索下载包代码复制到项目目录,自己管理使用的黑暗模式,大大提升了工程化效率。
以世界上最好的语言 JavaScript 的伴侣 npm 为例,开发者编写一个公共模块,作为 npm package 发布,不仅可在自己的项目间复用,还可以贡献到开源社区,使更多的开发者受益。使用者使用起来也极为简单,一条命令搞定:
$ npm install <moduleName>
1.2 git 子模块管理工具 submodule
git submodule
能够在项目主工程中添加子工程模块,而又保持子工程独立的版本控制,和 npm 极简的体验相比,git submodule 有一定的学习成本,对于初学者并不友好。
其实在 git submodule 之前,我们也许都曾有过相似的经历:开发一个新项目,需要用到团队的公共库,但是又不想把公共代码提交到自己的项目时,会考虑在当前工作目录下,将公共模块文件夹加入到 .gitignore
文件中或是 svn 的忽略文件列表,这样本地能够正常调试的同时,每次提交都能够忽略公共代码。但这样做的弊端是,使用该项目的人需要有一个先验知识(一般写在 README.md 说明文件中):需要在当前目录下放置一份某版本的公共模块代码。而 git submodule 实现的就是自动维护主项目和子项目之间的依赖关系。
虽然 git submodule 需要一定的学习成本,但也有其不可取代的优势:
- 更方便的主工程调试:由于子模块源码直接暴露在主项目工程下,更加便于主项目工程的调试运行;
- 更方便的子模块调试:在某些场景下,子模块需要在主工程项目中频繁调试迭代,由于
npm
包在主工程的忽略文件列表node_modules
中,只能切换到独立的子模块工程中开发,而git submodule
子模块的整个工程都直接在主工程下目录下,直接在主工程中调试子模块提交更新即可; - 更方便的版本权限控制:有一种场景,公共子模块需要频繁迭代,且必须在主工程下调试,但不希望公共子模块的开发者提交对主工程的修改,此时直接关闭该用户主工程的提交权限即可。例如,在最近的 UI 自助化项目中,为了避免开发 UI 组件的开发者提交在主工程中的随意编写的测试代码,只要将组件库独立为 git submodule,同时不开放 UI 组件开发者的主工程提交权限,就能够轻松解决问题。
2. git submodule 操作指引
2.1 创建 submodule
git 工具的 submodule
其实是建立了当前项目与子模块间的依赖关系:子模块路径、子模块远程仓库、子模块版本号。
创建子模块只需一条命令:
$ git submodule add <submodule_url> <submodule_local_path>
此时,项目中会多出两个文件:.gitmodules
和子模块项目文件夹,以及在 .git/config
文件和 .git/modules
文件夹下也会多出相关内容。在此期间,git 做了3件事情:
- 记录引用的仓库
- 记录主项目中 submodules 的目录位置
- 记录引用 submodule 的 commit id
创建完 submodule 后执行提交命令:
$ git commit -m "add submodule"
提交后,在主项目仓库中,会显示出子模块文件夹,并附带其所在仓库的版本号,如:foo @ abcd1234
。
2.2 获取 submodule
使用 git submodule add
命令会自动拉取子工程项目代码到指定目录,但其他开发者获取主项目代码时,使用 git clone
命令是不会拉取到子项目的代码的,必须运行两条命令:
$ git submodule init # 初始化本地配置文件
$ git submodule update # 检出对应的 commit id 的子项目
也可以在 clone 命令中添加 --recurse-submodules
或 --recursive
参数递归拉取子模块代码。
$ git clone --recursive /path/to/repos/foo.git
git help 解释:
--recursive, --recurse-submodules
After the clone is created, initialize all submodules within, using their default settings. This is equivalent to running git
submodule update --init --recursive immediately after the clone is finished. This option is ignored if the cloned repository
does not have a worktree/checkout (i.e. if any of --no-checkout/-n, --bare, or --mirror is given)
2.3 更新 submodule
由于子工程保持独立的版本控制,直接按照 git 的方式更新即可,但对于主工程,子模块代码可能有四类更新:
2.3.1 子项目本地修改未提交
本地子项目下内容发生了未跟踪的变动,可能是有意或无意(如编译产生)的,此时在主项目中虽然会显示该子项目有未跟踪的内容修改,但不会列出差异,并且主项目所有的 git add
和 git commit
操作都不会对子项目产生影响。此时若需要提交子项目修改,需要进入子项目文件夹再执行版本提交操作,完成后进入下文 2.3.2 中的状态。
2.3.2 子项目本地修改并提交新版本
本地子项目有版本更新,此时在主项目中使用 git status
查看仓库状态时,会显示子项目有新的提交,可以在主项目中使用 git add/commit
命令提交修改。值得注意的是,此时主项目修改的是其依赖的子项目的版本,而非完整变更代码,即引用的子项目的 commit id
。
2.3.3 子项目远程更新,主项目已更新 commit id
此时在主项目中执行 git pull
后会自动同步主项目中的子项目 commit id
依赖,由于主项目已知子项目更新,只需要执行 submodule 更新命令便可将落后的子项目更新到指定的版本。
$ git submodule update
2.3.4 子项目远程更新,主项目未更新 commit id
在多人协作开发时,主项目与子项目的开发往往是异步进行的,子项目升级后子项目远程仓库更新并告知主项目可以更新对子项目的版本依赖。由于当前主项目记录的子项目版本还未变化,因此主项目执行 git submodule update
也不会更新,此时需要从主项目主动进入子项目执行 git pull
主动拉取新版代码,回到前面的 2.3.2 状态,再更新 commit id
同步到主项目中。
2.4 删除 submodule
首先,使用 git submodule deinit
命令卸载子模块:
# --force 参数将同时删除子模块工作区内的修改
$ git submodule deinit <submodule-name>
然后,在主项目下删除对应的子模块工程目录,接着删除 .gitmodules
和 .git/config
配置文件下的相关条目,再删除 .git/module/
下的子模块目录,最后执行:
$ git rm --cached <submodule-name>
3. 其他说明
3.1 使用 foreach 批量操作
若一个项目中有多个子模块需要执行相同的操作,每次切换到对应的目录挨个执行效率太低,此时可以使用 git submodule foreach <command>
:
$ git submodule foreach git checkout master
$ git submodule foreach git pull
3.2 关于默认分支
当 clone 包含 submodule 的项目时,主项目获取到的是 submodule 的 commit id
,然后当执行 git submodule update
时是根据这个 commit id
来拉取代码的,所以 clone 之后不在任何分支上,但如果子仓库都在 master 开发的,此时 master 分支的 commit id
和 HEAD
保持一致。因此,如果需要在主项目中开发子模块,建议还是把子模块切换到 master 分支进行开发提交,便于管理。此外,当主项目 clone 后,也可以使用上述的 foreach 命令批量切换到 master 分支进行更新。
4. 总结
本文详述的 git submodule 的使用方式源自本人在当下工作中的实践,希望能对您有所帮助,若有更好的实践方案可以给我留言。