0%

git submodule不完全指北

项目中经常会使用到第三方的 git 库,将三方库整合到项目中最简单的办法就是复制粘贴,但是如果这个库如果经常更新,那么怎么快捷地整合进来呢?

这就是本次要介绍的 git submodule操作,直接把第三方的版本库合并到自己的库中.

添加第三方库

我这里是自己开了两个库做测试,主库叫 blog,另一个库叫 next

首先在本地的blog库中添加theme-next

1
2
3
git clone https://github.com/anspoon/blog.git 
cd blog
git submodule add https://github.com/anspoon/theme-next.git ./themes/next

这时用git status查看下状态会多两个文件

1
2
3
4
5
6
7
8
9
git status 
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

new file: .gitmodules
new file: next

这就多了一个 next的库,和一个.gitmodules的文件,现在提交一下

1
git commit -am "add theme-next"

在其他地方使用合并后的版本库

本地提交了版本之后可以提交到远程试一下

1
git push

这时去远程库中看的话库中的内容是这样的
git submodule示例

这里有个奇怪的 next @ db22b25,版本库里明明是没有的啊? 点一下原来是一个快捷方式,直接给链接到了 next库的地址,版本库中不会存放第三方引入库的实体文件,而是通过 .gitmodules的方式存储三方的库地址,当下载到本地运行的时候才会再拉取文件

而且这个时候在其他地方安装blog这个库的时候直接运行 git clone 是生成不了完整的文件的,会缺少 theme-next库的文件,因为这个时候的 blog/theme/next目录是空的。

这是为什么呢?别着急,下面我们会讲到原因。

1
2
3
4
5
git clone https://github.com/anspoon/blog.git 
git submodule init && git submodule update

#下面这个命令的效果和上面三条命令的效果是一样的,多加了个参数 `--recursive`
git clone https://github.com/anspoon/blog.git --recursive

这时克隆下来的才是一个完整的库!

将三方库同步到主线

之前的一些步骤其实还不完整,因为 blog/theme/next 这个目录中的文件并没有和主线在一条线上,这也是为什么在远程库的 theme/next 目录下是空的,因为在 master 分支里面它确实是空的,文件是在另一个分支上,我们先去看一下

1
2
3
cd path/to/blog/theme/next 
git branch
* (HEAD detached at db22b25) master

别的文件的分支都是 master 到这个文件的时候就是 db22b25分支了,其实这个值也是 next库当前的 commitId, 而且如果不把第三方的库纳入自己的主线的话会非常的危险,因为你对项目中的第三方库发生的任何改动都不会对主线产生任何影响。

因此我们还需要接下来的操作:

1
2
cd path/to/blog/theme/next 
git checkout master

更新第三方库

这里还有个问题就是如果 blog/theme/next 发生了更新就首先在这个文件中提交一个commit,然后在blog这个目录下再 commit一次,第一次 commit 是为了更新 next的版本控制,第二次更新是更新blog的版本控制,同时更新 next库在blog的指针。

如果更新的比较多,可以参考下面的批量更新方法。

批量更新第三方库

假设你的项目当中引入了100个第三方的库,你需要同步的时候难道还要一个一个执行?

1
2
3
cd module-dir/ 
git checkout master
git pull

是不是想起了被小学老师罚抄一百遍的感觉? 没事,这些东西 git 早就帮你想好了。具体操作可以看一下git help submodule,里面有相关的介绍的,不想看文档的可以参考下面的方法。

1
2
3
git submodule foreach <command> 
#例如:
git submodule foreach git checkout master

这条命令就会按照 .gitmodules文件里的path参数,寻找所有的第三方模块,并在每一个模块中都执行 foreach 后的命令。

比如你想批量更新模块到最新的时候:

1
git submodule foreach git submodule update                               

画个重点(敲黑板!)

如果你想让引入的第三方库next符合你自己的定制,你在里面发生了一些修改,但是这些修改并不能提交到远程第三方库去,因为你的提交会让第三方库的作者产生困扰。

我写的好好的一个轮子你给造成这样,还怎么去给其他的人用?

而且这个问题很严重,如果你本地的nextblog都更新了,但是第三方的next不能提交到远程,而blog提交上去了,那么与你同使用这个版本库的小伙伴就会因为当前项目next的指针地址找不到而产生错误。

所以如果有定制的需要就得去fork这个项目到你自己的 github 上,然后自己想怎么折腾都随意了。

但是怎么做到既有定制,还能保持和轮子作者的版本同步呢?

怎么保持与作者同步?

首先是自己有一个 fork 的三方项目,然后在自己本地添加一个三方的源:

1
2
3
4
5
6
7
8
9
10
11
cd path/to/blog/theme/next 
git remote -v
origin https://github.com/anspoon/next.git (fetch)
origin https://github.com/anspoon/next.git (push)

#添加第三方包的源地址
git remote add dist_source https://github.com/xxxx/theme-next.git
git remote -v
dist_source https://github.com/xxxx/theme-next.git (fetch) #这个是三方的源地址 dist_source https://github.com/xxxx/theme-next.git (push)
origin https://github.com/anspoon/theme-next.git (fetch) #这个是你 fork 的项目地址
origin https://github.com/anspoon/theme-next.git (push)

这样的话就可以拉取源文件到本地进行合并代码了:

1
git pull dist_source master

修复一下版本冲突的文件,确认没有问题之后提交到自己 fork 的库中:

1
git push origin master                      

这样其他人就能正常使用了

怎么删除submodule?

在当前 git 版本1.7.8之前,删除指定的 submodule 的命令是

1
git rm --cached <submodule-name>   

在新版的 git 下,则是运行以下命令

1
git submodule deinit <submodule-name>

查看本地有哪些三方模块可以查看 .gitmodules

1
2
3
4
cat .gitmodules 
[submodule "theme-next"]
path = theme-next
url = https://github.com/anspoon/theme-next.git
请勺子喝杯咖啡吧!