前言
近期在考虑项目拆分的过程中,对于公共组件的维护总共找到了两种方式,分别是:发布 npm 包与 git submodule
子模块管理。对于 npm 发包的方法这里我就不做赘述了(先前有写到过),而另一种 git submodule
的方法这里就详细介绍一下,对于不想使用发包的方式(比较繁琐)来维护子项目的 coder 来说,这种方式可以算是最为方便的了。
使用场景
有种情况我们经常会遇到:某个工作中的项目需要包含并使用另一个项目。也许是第三方库,或者你独立开发的,用于多个父项目的库。现在问题来了:你想要把它们当做两个独立的项目,同时又想在一个项目中使用另一个。
我们举一个例子。假设你正在开发一个网站然后创建了 Atom 订阅。你决定使用一个库,而不是写自己的 Atom 生成代码。你可能不得不通过 CPAN 安装或 Ruby gem 来包含共享库中的代码,或者将源代码直接拷贝到自己的项目中。如果将这个库包含进来,那么无论用何种方式都很难定制它,部署则更加困难,因为你必须确保每一个客户端都包含该库。如果将代码复制到自己的项目中,那么你做的任何自定义修改都会使合并上游的改动变得困难。
Git 通过子模块来解决这个问题。子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。
该部分直接引用官方的例子,就不造轮子了。
命令使用
使用 git submodule
时,需要的一些命令有:
1 | # 当前项目文件名如不指定,则默认为仓库名 |
初次添加仓库操作
作为添加者来说,子模块的添加是很简单的一件事,主要步骤如下:
1 | # 添加仓库 |
拷贝远程仓库
拷贝远程带有子模块的项目需要稍微注意一点,那就是子模块需要额外的步骤才能将内容拷贝下来,具体步骤如下:
1 | # 克隆仓库 |
三步简化成一步的操作为: git clone <远程仓库地址> –recursive
更新子模块
更新的方法也有多种,最为基础的处理方式是:
1 | # 进入项目中的子模块内 |
当然,此做法比较繁杂,较为简便的处理方法是(无需进入子模块):
1 | # 添加 remote 会更新至远程项目的最新版本,否则是本地最新版本 |
Git 会自动进入子模块并更新它。但是该命令会默认假定你想要更新并检出子模块的 master 分支。简单的说,此时子模块会自动生成一个新临时分支,与远程的源保持一致,如果子模块内部有改动,想要迁回,那么将子模块的 branch 切回原来的分支即可。
例如:子模块本来是 master 分支,后来远程子模块的源更新了,此时 git submodule update --remote
会将子模块分支切换成一个 hash id 分支。此时进入子模块目录,git checkout master
切回 master 分支,你会发现该 master 分支内容与执行 update
指令之前的是一致的,没有变动,但会提醒你远程的源已经更新了,需要手动 pull 一下。
这样看来,这两种写法都没差啦~反正最后都得自己手动处理一下。
对于包含多个子模块的项目来说,进入子模块一个个更新着实是很麻烦,批量更新可以使用 foreach,例如:
1 | # 同步最新的 head 指针,否则 |
或者,如果知道更新了,可以省略掉前两步骤(前面步骤主要是为了 git status
| git submodules
看有没有子模块变动),直接拉取就行,这样也不用再去切分支了。
1 | git submodule foreach git pull |
更改子模块
前面对子模块的处理仅仅只是同步更新而已,这和使用 npm 包没啥区别,但毕竟是将子模块给弄进项目了,当个包放在那里用,那也太委屈了。其实在主项目内,我们还可以对子模块进行更新,还可以并发布其改动,这样就能节省很多时间和劳力了(前提是别改造得别人无法使用,变成私有代码库了)。
这里仅介绍比较省时的操作,直接启动 merge ,我们可以用下面这个命令:
1 | git submodule update --remote --merge |
进入子模块后,我们就能和平常一样的合并冲突了。
在主分支进行推送时,我们得保证当前分支的所有子模块都已经与远程一致(或者都落后,但不可 diverged),防止别的伙伴接受不到新的变动,因此推送的命令改为:
1 | # 这样 git 会进入到子模块中然后在推送主项目前推送了它。如果那个子模块因为某些原因推送失败,主项目也会推送失败 |
注:如果出现了无法推送的情况,特别是远程分支为本地项目的情况,此时我们需要进入该远程分支,然后设置 ‘.git/config’ 文件,加上内容如下:
1 | [receive] |
这样就能 push 了。
删除子模块
删除子模块需要进行 2 步操作,首先得删除对应的 cache 缓存追踪,然后再删除对应的模块文件
1 | git rm --cached <fileName> |