工作中会遇到这样的问题,当你在一个项目上时,你需要在其中使用另外一个项目,这个项目也许是一个第三方开发的库或者是你独立开发 合并在多个项目中使用的。这样就会产生一个问题:你想将两个项目单独处理但是又需要其中一个项目使用另一个。而Git通过子模块处理这个问题,子模块允许你将一个Git仓库当做另一个Git仓库的子目录。允许你克隆另外一个仓库到你的项目中并且保持你的提交相对对立。
开始使用子模块
首先我们将一个已经存在的Git仓库添加为正在工作的的仓库的子模块。运行git submodule add <url> 命令添加子模块。如果你想将它放在其他地方,可以通过在命令结尾添加路径来实现。
模块添加成功了,接下来我们看一下工作区的变化。运行 git status 命令
我们会发现多两个新的文件,首先是.gitmodules文件。这个文件是一个配置文件,保存了项目URL和已经拉取的本地目录之间的映射,查看一下里面的内容
可以看到文件里包含了上述的两条内容,要重点注意的是,这个文件跟其他文件一样是处在版本控制之下的,它会和该项目的其他文件一样被拉取推送,这也是为什么克隆该项目的人知道去哪里获得子模块的原因。
这里要注意一点,因为.gitmodules文件中的URL是人们第一次克隆项目时的地址,因此我们需要尽可能的确保这个URL是可以被其他人访问的。
接下来我们看看另一个新的文件,运行 git diff 命令,
我们可以看到,虽然childModule1是工作目录中的子目录,但是Git会将它视作一个子模块。当你不在那个目录中时,Git并不会跟踪他的内容,而是把它看做该仓库的一个特殊提交。
当你将更改提交到版本库时,我们会看到以下信息
注意childModule1记录的160000模式。这是Git中的一种特殊模式,表示将一次提交记作一项目录记录的,而非将它记录成一个子目录或者一个文件。
除了自己添加子模块,我们还可以克隆带子模块的目录
克隆一个带子模块的项目
我们可以通过git clone <url> 来克隆一个带子模块的项目,在克隆这个项目时,默认会包含该子模块的目录,但是目录里面没有任何文件。克隆成功之后我们查看一下这个文件
childModule1文件目录存在了,但是目录是空的。这时我们需要运行两个命令:第一个 git submodule init 用来初始化本地配置文件
第二个命令 git submodule update 用来拉取childModule1目录的所有数据并检出你上层项目里所列的合适的提交
到这里我们第一个克隆 包含子模块项目的方法就到这了。既然说了有第一个,当然就有第二个了,第二个方法更简单一点,直接一步到位,就是给 git clone 命令加一个--recursive 参数,他就会自动初始化并更新仓库的每一个子模块
子模块克隆好之后(不管是用那种方法克隆)我们看一下它的状态,
可以看到的是非常醒目的一串红色的字母,那这一串字母代表获得一个游离的HEAD,这意味着HEAD指向的只是一次提交。那会这样的原因是因为我们克隆项目的时候,Git将会获得这些改动并更新子目录的文件,但是会将子模块留在一个叫做“游离的HEAD”的状态,这也意味着子模块没有本地工作分支跟踪改动。所以我们的做的任何改动都不会被跟踪。
现在子模块克隆好了,子目录也处于我们先前提交的状态了。
如果另外的一个开发者更改了子目录的代码,并作了提交,然后我们拉取那次引用合并,然后查看一下主目录的状态。
git merge origin/master 命令的作用是将上游分支合并到本地
因为我们合并的仅仅只是只想子模块的指针,但是它并不更新子模块的代码,所以主目录的工作状态处于一个临时的状态
然后我们再查看一下工作区和版本库的区别
会发现,我们所拥有的指向子模块的指针和子模块目录的真实状态并不匹配。所以为了修复这一点我们要再次执行 git submodule update 命令
执行之后再次查看工作区和版本库的区别我们会发现没有区别了。
有一个很不方便的地方就是我们每次从主项目中拉去子模块的变更都要这样做。