最近项目中用到了Git子模块,记录一下Git操作子模块的一些知识,供以后自己参考。
如何增加子模块。
首先使用mkdir,cd等命令,切换到想要将子模块存储的路径(当然也可以在子模块的url后面加上你的子目录名字),然后执行git submodule add SubmoduleUrl
,(请将颜色字体自觉换成子目录的url)。该命令即在该目录下将子模块clone下来,而且会新建一个.gitmodules的文件,此文件存储了一些子目录的信息。即git submodule add命令进行了如下的操作:
它在当前目录下克隆各个子模块, 默认签出master分支.
它把子模块的克隆路径加入到gitmodules文件中, 然后把这个文件加入到索引, 准备进行提交.
它把子模块的当前提交ID加入到索引中, 准备进行提交.
如何拉取子模块。
使用clone命令拉取下来的分支,是没有子模块的内容的。你需要进行如下两个命令,才能将子模块拉取下来。
第一步:运行git submodule init, 把子模块的URL加入到.git/config。
第二部:使用git submodule update去克隆子模块的仓库和签出父项目中指定的那个版本。
当然,如果你嫌上面的步骤麻烦,可以在clone主模块的时候,加上 --recursive。他就会递归的把你的子模块都给clone下来,如:git clone https://github.com/luchenqun/GitTest.git --recursive。
如何删除一个子模块。
删除一个子模块没有一个类似于git submodule rm SubmodulePath这么简单的命令来完成。需要做以下操作:
第一步:删除文件“.gitmodules”下面你不需要的子模块的内容。如下图要是像不要子项目UnitTest,那么请删除红线框里面的内容即可。
第二步:使用命令git add .gitmodules 保存.gitmodules文件的修改。
第三步,删除文件“.git/config”文件下面不需要的子模块的内容。(注意:.git文件夹默认是隐藏的,如果你看不到这个文件夹,请自觉勾选隐藏文件夹可见)
第四步:执行命令git rm --cached PathToSubmodule(没有横线)删除缓存内容。如:我要删除上面的UnitTest子模块。执行:git rm --cached UnitTest。注意:不是git rm --cached UnitTest/。
第五步:执行命令rm -rf .git/modules/PathToSubmodule删除子模块里面的模块内容。(不想打命令手动删除也行)
第六步:提交上面的修改。使用 git commit -m "Del submodule UnitTest"(内容自己写)。
第七步:执行命令rm -rf PathToSubmodule删除子模块内容。不想打命令手动删除即可。
经过上面的步骤,就将一个子模块从项目中删除啦!
如何推送(push)一个子模块。
有的时候,你修改了一个子模块的内容之后想推送到远程端,我们会很自然的想到,cd到子目录下面,然后add,commit之后再push。不过等你push完成之后,你到远程端查看,发现并没有将你的内容推送到远程端。这是为什么呢?原因是默认git submodule update并不会将submodule切到任何branch,所以,默认下submodule的HEAD是处于游离状态的(“detached HEAD” state)。所以在修改前,记得一定要用git checkout master将当前的submodule分支切换到master(不一定是master,切换到你想要修改的分支即可),然后才能做修改和提交。
当然,如果你不慎在“detached HEAD”进行了commit。那也不要紧,经过以下步骤,依然可以将你的更改提交到远程端。
第一步:使用命令git checkout master切换到主分支。这时候git会有个警告说:Warning: you are leaving 1 commit behind, not connected to any of your branches:35e565f update..,意思就是说有一个提交没有跟你的任何一个分支关联。不理他。记住这个“35e565f”这个提交的commit id即可。
第二步:使用命令git cherry-pick CommitId来将刚刚的提交作用在master分支上。我上面展示的CommitId是“35e565f”。如果忘记了CommitId,那么使用git log找出你的CommitId吧。
第三步:执行git push即可。
如何更新一个子模块。
当别人更新了子模块的时候,你可能需要将子模块更新到你的本地。如果你想执行git pull就将子模块跟你的主项目进行合并。那么,你就想错了。这里面有几个陷阱,稍微记录一下。
首先大家要知道,submodule项目和它的父项目本质上是2个独立的git仓库。所以你对父项目的任何操作,都不会影响子模块。比如:子模块做了修改,父项目也做了修改,你对父项目的add,commit等操作,都不会对子项目产生任何影响。父项目只是存储了它依赖的submodule项目的版本号信息而已。如果别人更新了submodule,然后更新了父项目中依赖的版本号。你需要在git pull父项目之后,调用 git submodule update来更新submodule信息。基于此,有一下几个陷阱需要大家注意:
第一个陷阱:谁更新子模块,一定要记得,先push子模块,再push主项目。如果你先push主项目,再push子模块。就会导致别人合并了你的主项目再执行git submodule update,就不会是最新的子模块了。因为在push子模块的时候,主项目会更新依赖的submodule项目的版本号信息。你只有把这些信息push上去了,别人在更新子模块的时候,将最新的子模块更新下来。否则,别人更新的只是你前一个子模块。
第二个陷阱:如果你更新子项目之后,没有将子项目push上去,你就将主项目push上去了。这会导致一个更严重的后果就是,别人pull你的主项目之后,无法将你的子模块克隆下来。原因就是因为你修改了主项目依赖的submodule项目的版本号信息,但是子项目却没有提交到远程端,别人当然无法克隆你的项目。
第三个陷阱:如果你的同事更新了submodule,然后更新了父项目中依赖的版本号。你需要在git pull之后,调用 git submodule update来更新submodule信息。这儿的坑在于,如果你git pull之后,忘记了调用 git submodule update,那么你极有可能再次把旧的submodule依赖信息提交上去。所以大家执行git pull之后,立即执行git status, 如果发现submodule有修改,再执行git submodule update。更复杂一些,如果你的submodule又依赖了submodule,那么很可能你需要在git pull 和 git submodule update之后,再分别到每个submodule中再执行一次git submodule update,这里可以使用 git submodule foreach命令来实现: git submodule foreach git submodule update。
当然,还有一个笨办法,上面说了,submodule项目和它的父项目本质上是2个独立的git仓库。如果父项目确实没有存有子模块的最新的版本信息,那么我们可以在子模块下面,切换到它的分支,然后将最新的项目弄下来也是可以的。但是,由于父项目存储的子模块的信息不是最新的,你可以随便修改一下子模块的东西提交,再对父项目进行push。这样,一切都恢复正常了。
合并主项目,子模块冲突问题
假设将分支A合并到分支B的过程中,有个子模块C提示有冲突。我们可以按照如下方式解决:
- 将B分支重置到未冲突的状态。
- 将B分支里面的子模块重置到跟A的子模块一样的状态之后commit分支B。
- 然后将A分支合并到B分支,此时子模块C应该不会再有冲突。
- 将B模块里面的子模块切换到想到的分支。
- 推送B模块。