最近在做一个上传的客户端,上传的部分由后端同学以 SDK 的方式提供,因此该 SDK 是在一个独立的仓库,那么对于客户端该如何方便的集成该 SDK 呢?每次 SDK 更新把代码拷贝到客户端仓库?把 SDK 发布到 npm?这就可以考虑用 git 的 submodule。
什么是 submodule
submodule 是一个多项目管理工具,它允许将子项目以独立的 git 项目添加到主项目,而主项目以 submodule 的形式拥有子项目。子项目拥有自己的 commit、push、pull,而与主项目互不干扰。主项目只需要记录子项目的地址和所需要的 commit id,通过地址和 commit id 就能够得到对应的子项目。
添加 submodule
通常情况下,我们都有一个主项目(MainProject),在 MainProject 文件夹下执行如下命令,即可添加 submodule。
/*
url: 子项目远程地址或本地地址
path: 子项目路径,可省略
*/
$ git submodule add [url] [path]
例: git submodule add git@github.com:fengyueran/UploaderSDK.git ./src/UploaderSDK
git status 可以看到如下信息
On branch master
Changes to be committed:
new file: .gitmodules
new file: UploaderSDK
可以看到多了两个文件: .gitmodules 和 UploaderSDK。 cat .gitmodules 看到.gitmodules 储存了 submodule 的路径及远程地址。
[submodule "src/uploaderSDK"]
path = src/uploaderSDK
url = git@github.com:fengyueran/UploaderSDK.git
UploaderSDK 的内容为 submodule 的 commit id。
Subproject commit 6b53e1840b27ca1587b96c1eb9dd5f4ff0866089
不难想象通过.gitmodules 和 UploaderSDK 的信息就可以拿到 submodule 的内容了,因此我们需要提交这个两个文件。
git add .
git commit -m "add submodule"
克隆带有 submodule 的项目
主要有两个方式
1. 采用先克隆后更新的方式
和想象中的不一样,直接 clone 主项目,submodule 并不会跟着 clone 下来,而只有包含 submodule 名的空文件夹。
1)$ git clone git@github.com:fengyueran/MainProject.git
需再执行如下命令
2)$ git submodule init
输出如下,可以看到该命令给子项目注册了路径,即在主项目中的位置。此时,uploaderSDK 文件夹仍未空。
Submodule 'src/uploaderSDK' (git@github.com:fengyueran/UploaderSDK.git) registered for path 'src/uploaderSDK'
再执行
//该命令并不是直接更新到最新的submodule commit,而是更新至主项目所存储存的commit(有可能是较旧的commit)。
3)$ git submodule update
输出如下,可以看到 sumodule 得到更新,更新到主项目存储的 submodule commit,是一个游离的 git header。
Cloning into '/Work/test/MainProject/tmp/MainProject/src/uploaderSDK'...
Submodule path 'src/uploaderSDK': checked out '6b53e1840b27ca1587b96c1eb9dd5f4ff0866089'
2. 采用递归参数--recursive
git clone git@github.com:fengyueran/MainProject.git --recursive
输出如下,可以看到主项目包括 submodule 都被 clone 下来了。
Cloning into 'MainProject'...
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (4/4), done.
Receiving objects: 100% (7/7), done.
remote: Total 7 (delta 0), reused 4 (delta 0), pack-reused 0
Submodule 'src/uploaderSDK' (git@github.com:fengyueran/UploaderSDK.git) registered for path 'src/uploaderSDK'
Cloning into '/Work/test/MainProject/tmp/MainProject/tmp/MainProject/src/uploaderSDK'...
Submodule path 'src/uploaderSDK': checked out '6b53e1840b27ca1587b96c1eb9dd5f4ff0866089'
修改更新 submodule
主要有两种情况
1. 直接在主项目中的 submodule 下修改
如上例,直接在 MainProject 下的 src/uploaderSDK 中修改,uploaderSDK 切换到工作分支,修改并提交后,可以 checkout 到最新的 commit,也可以不切,反正都在当前最新的 commit 上(如果想测试其他 commit 也可以切换到相应 commit 上),此时 MainProject 中我们可以看到 src/uploaderSDK 的 commit 有如下变化
-Subproject commit 6b53e1840b27ca1587b96c1eb9dd5f4ff0866089
+Subproject commit a4d6dc0457673a275b1f6cbeda6f8ff23293b9de
a4d6 为修改的提交,需要注意的地方是此时 submodule 已经在最新的 commit 上了,不要再在 MainProject 中 git submodule update 进行更新了,如果进行此操作 submodule 又会回到原来的 commit(带有减号的 commit),只需要在 MainProject 提交,并在必要的时候 push 到远程仓库。这种方法,非 submodule 的开发人员就不用关心 submodule 是否更新了,只需要在 MainProject 下 pull 代码发现 submodule 有更改时执行 git submodule update(更新为带减号 commit)进行更新,前提是其他开发人员提交了正确的 submodule commit。
2. 在 submodule 自己独立的仓库进行修改
在工作目录克隆下 submodule 的仓库,切换到工作分支进行修改提交并 push 到远程仓库。这种方法需要 submodule 开发人员告诉 MainProject 的开发人员 submodule 有更新或主动查看是否有更新,有更新时就在 MainProject 的 src/uploaderSDK 下 pull 远程代码(需要知道 submodule 的工作分支),快速合并后,uploaderSDK 的 commit 有如下变化,此时同 1 不要 git submodule update,而只是在 MainProject 下提交这个更改。
-Subproject commit f4573cc1bb50000779202c7f56a640b1ffc075cb
+Subproject commit 64ae6d149c0f6e3b06b8cea262c6126a7bc0887f
删除 submodule
执行如下命令
- $ git submodule deinit
逆初始化模块,submodule为子模块目录,执行后可发现子模块目录被清空
$ git submodule deinit [submodule_name]
-> Cleared directory 'test2sub'
Submodule 'test2sub' (git@github.com:fengyueran/test2sub.git) unregistered for path 'test2sub'
//执行如下命令还能看到子项目信息
$ git submodule
-> -dab52c62f52353d9967619625c28e28dc4320aef test2sub
- $ git rm
--
cached [submodule_name]
// 删除.gitmodules中记录的模块信息(--cached选项清除.git/modules中的缓存)
git rm --cached test2sub
//执行如下命令已看看不到删除的子项目信息了
$ git submodule
- $ git commit
git commit -m "remove submodule"