前言
一个中大型项目每每会依赖几个模块,git提供了子库的概念。能够将这些子模块存放在不一样的仓库中,经过submodule或subtree实现仓库的嵌套。git
1、
submodule
submodule:子模块的意思,表示将一个版本库做为子库引入到另外一个版本库中:github
image-20200408224205125
1.引入子库
须要使用以下命令:ide
git submodule add 子库地址 保存目录
好比:url
git submodule add git@github.com:AhuntSun/git_child.git mymodule
执行上述命令会将地址对应的远程仓库做为子库,保存到当前版本库的mymodule目录下:3d
随后查看当前版本库的状态:指针
image-20200329203048016
能够发现新增了两个文件。查看其中的.gitmodules文件:日志
image-20200329203507411
能够看到当前文件的路径和子模块的url,随后将这两个新增文件「添加」、「提交」并「推送」。在当前仓库git_parent对应的远程仓库中多出了两个文件:code
image-20200329203746236
其中mymodule文件夹上的3bd7f76 对应的是「子仓库」git_child中的「最新提交」:server
image-20200329203905051
点击mymodule文件夹,会自动跳转到「子仓库」中:blog
image-20200329203957392
经过上述分析,能够得出结论:两个仓库已经关联起来了,而且仓库git_child为仓库git_parent的子仓库;
2.同步子库变化
「当被依赖的子版本库发生变化时」:在子版本库git_child中新增文件world.txt并提交到远程仓库:
image-20200329204252524
这个时候依赖它的父版本库git_parent要如何感知这一变化呢?
方法一
这个时候git_parent只须要进入存放子库git_child的目录mymodule,执行git pull就能将子版本库git_child的更新拉取到本地:
image-20200330102106961
方法二
当父版本库git_parent依赖的「多个子版本库」都发生变化时,能够采用以下方法遍历更新全部子库:首先回到版本库主目录,执行如下指令:
git submodule foreach git pull
该命令会「遍历」当前版本库所依赖的「全部」子版本库,并将它们的更新「拉取」到父版本库git_parent:
image-20200330102642607
拉取完成后,查看状态,发现mymodule目录下文件发生了变化,因此须要执行一次添加、提交、推送操做:
image-20200330102914556
3.复制父版本库
若是将使用了submodule添加依赖了子库的父版本库git_parent,克隆一份到本地的话。在克隆出来的新版本库git_parent2中,原父版本库存放依赖子库的目录虽在,可是内容不在:
image-20200330103417911
进入根据git_parent复制出来的仓库git_parent2,会发现mymodule目录为空:
image-20200330103502848
「解决方法」:可采用多条命令的分步操做,也能够经过参数将多步操做进行合并。
分步操做
这是在执行了clone操做后的额外操做,还须要作两件事:
手动初始化submodule:
git submodule init
手动拉取依赖的子版本库;:
git submodule update --recursive
image-20200330103803762
执行完两步操做后,子版本库中就有内容了。由此完成了git_parent的克隆;
合并操做
分步操做相对繁琐,还能够经过添加参数的方式,将多步操做进行合并。经过如下指令基于git_parent克隆一份git_parent3:
git clone git@github.com:AhuntSun/git_parent.git git_parent3 --recursive
image-20200330104210732
--recursive表示递归地克隆git_parent依赖的全部子版本库。
4.删除子版本库
git没有提供直接删除submodule子库的命令,可是咱们能够经过其余指令的组合来达到这一目的,分为三步:
将submodule从版本库中删除:
git rm --cache mymodule
image-20200330105131697
❝
git rm的做用为删除版本库中的文件,并将这一操做归入暂存区;
❞
将submodule从工做区中删除;
image-20200330105226923
最后将.gitmodules目录删除;
image-20200330105542069
完成三步操做后,再进行添加,提交,推送便可完成删除子库的操做:
image-20200330105614793
2、
subtree
1.简介
subtree与submodule的做用是同样的,可是subtree出现得比submodule晚,它的出现是为了弥补submodule存在的问题:
「第一」:submodule不能在父版本库中修改子版本库的代码,只能在子版本库中修改,是单向的;
「第二」:submodule没有直接删除子版本库的功能;
而subtree则能够实现双向数据修改。官方推荐使用subtree替代submodule。
2.建立子库
首先建立两个版本库:git_subtree_parent和git_subtree_child而后在git_subtree_parent中执行git subtree会列出该指令的一些常见的参数:
image-20200330112616987
3.创建关联
首先须要给git_subtree_parent添加一个子库git_subtree_child:
「第一步」:添加子库的远程地址:
git remote add subtree-origin git@github.com:AhuntSun/git_subtree_child.git
添加完成后,父版本库中就有两个远程地址了:
image-20200330113223780
这里的subtree-origin就表明了远程仓库git_subtree_child的地址。
「第二步」:创建依赖关系:
git subtree add --prefix=subtree subtree-origin master --squash //其中的--prefix=subtree能够写成:--p subtree 或 --prefix subtree
该命令表示将远程地址为subtree-origin的,子版本库上master分支的,文件克隆到subtree目录下;
❝
注意:是在某一分支(如master)上将subtree-origin表明的远程仓库的某一分支(如master)做为子库拉取到subtree文件夹中。可切换到其余分支重复上述操做,也就是说子库的实质就是子分支。
❞
--squash是可选参数,它的含义是「合并,压缩」的意思。
若是不增长这个参数,则会把远程的子库中指定的分支(这里是master)中的提交一个一个地拉取到本地再去建立一个合并提交;
若是增长了这个参数,会将远程子库指定分支上的屡次提交合并压缩成一次提交再拉取到本地,这样拉取到本地的,远程子库中的,指定分支上的,历史提交记录就没有了。
image-20200330114203889
拉取完成后,父版本库中会增添一个subtree目录,里面是子库的文件,至关于把依赖的子库代码拉取到了本地:
image-20200330114316257
此时查看一下父版本库的提交历史:
会发现其中没有子库李四的提交信息,这是由于--squash参数将他的提交压缩为一次提交,并由父版本库张三进行合并和提交。因此父版本库多出了两次提交。
随后,咱们在父版本库中进行一次推送:
image-20200330114730534
结果远程仓库中多出了一个存放子版本库文件的subtree目录,而且彻底脱离了版本库git_subtree_child,仅仅是属于父版本库git_subtree_parent的一个目录。而不像使用submodule那样,是一个点击就会自动跳转到依赖子库的「指针」:
subtree的远程父版本库:
image-20200330115004586
submodule的远程父版本库:
image-20200329203746236
即submodule与subtree子库的区别为:
image-20200408224805624
4.同步子库变化
在子库中建立一个新文件world并推送到远程子库:
在父库中经过以下指令更新依赖的子库内容:
git subtree pull --prefix=subtree subtree-origin master --squash
image-20200330115726052
此时查看一下提交历史:
image-20200330115755340
发现没有子库李四的提交信息,这都是--squash的做用。子库的修改交由父库来提交。
5.参数--squash
该参数的做用为:防止子库指定分支上的提交历史污染父版本库。好比在子库的master分支上进行了三次提交分别为:a、b、c,并推送到远程子库。
首先,复习一下合并分支时遵循的「三方合并」原则:
image-20200408003842196
当提交4和6须要合并的时候,git会先寻找两者的公共父提交节点,如图中的2,而后在提交2的基础上进行二、四、6的三方合并,合并后获得提交7。
父仓库执行pull操做时:若是添加参数--squash,就会把远程子库master分支上的这三次提交合并为一次新的提交abc;随后再与父仓库中子库的master分支进行合并,又产生一次提交X。整个pull的过程一共产生了五次提交,以下图所示:
image-20200420103912282
「存在的问题」:
因为--squash指令的合并操做,会致使远程master分支上的合并提交abc与本地master分支上的最新提交2,找不到公共父节点,从而合并失败。同时push操做也会出现额外的问题。
「最佳实践:要么所有操做都使用--squash指令,要么所有操做都不使用该参数,这样就不会出错。」
「错误示范」:
为了验证,从新建立两个仓库A和B,并经过subtree将B设置为A的子库。此次全程都没有使用参数--squash,重复上述操做:
首先,修改子库文件;
而后,经过下列指令,在不使用参数--squash的状况下,将远程子库A变化的文件拉取到本地:
git subtree pull --prefix=subtree subtree-origin master
image-20200330141920474
此时查看提交历史:
image-20200330142000915
能够看到子库儿子的提交信息污染了父版本库的提交信息,验证了上述的结论。
因此要么都使用该指令,要么都不使用才能避免错误;若是不须要子库的提交日志,推荐使用--squash指令。
❝
「补充」:echo 'new line' >> test.txt:表示在test.txt文件末尾追加文本new line;若是是一个>表示替换掉test.txt内的所有内容。
❞
6.修改子库
subtree的强大之处在于,它能够在父版本库中修改依赖的子版本库。如下为演示:
进入父版本库存放子库的subtree目录,修改子库文件child.txt,并推送到远程父仓库:
image-20200330121429186
此时远程父版本库中存放子库文件的subtree目录发生了变化,可是独立的远程子库git_subtree_child并无发生变化。
「修改独立的远程子库」:
可执行如下命令,同步地修改远程子版本库:
git subtree push --prefix=subtree subtree-origin master
以下图所示,父库中的子库文件child.txt新增的child2内容,同步到了独立的远程子库中:
image-20200330125911158
「修改独立的本地子库」:
回到本地子库git_subtree_child,将对应的远程子库进行的修改拉取到本地进行合并同步:
image-20200330144044823
由此不管是远程的仍是本地的子库都被修改了。
❝
实际上使用subtree后,在外部看起来父仓库和子仓库是一个总体的仓库。执行clone操做时,不会像submodule那样须要遍历子库来单独克隆。而是能够将整个父仓库和它所依赖的子库当作一个总体进行克隆。
❞
存在的问题
父版本库拉取远程子库进行更新同步会出现的问题:
「子仓库第一次修改」:
经历了上述操做,本地子库与远程子库的文件达到了同步,其中文件child.txt的内容都是child~4。在此基础上本地子库为该文件添加child5~6:
image-20200330145702019
而后推送到远程子库。
「父仓库第一次拉取」:
随后父版本库经过下述指令,拉取远程子库,与本地父仓库git_subtree_parent中的子库进行同步:
git subtree pull --p subtree subtree-origin master --squash
结果出现了合并失败的状况:
image-20200330145839093
咱们查看冲突产生的文件:
image-20200330145922152
发现父版本库中的子库与远程子库内容上并没有冲突,可是却发生了冲突,这是为何呢?
探究冲突产生的缘由以前咱们先解决冲突,先删除多余的内容:
image-20200330150141430
随后执行git add命令和git commit命令标识解决了冲突:
image-20200330150312944
image-20200330150406317
解决完冲突后将该文件推送到独立的远程子库,发现文件并无发生更新,也就是说git认为咱们并无解决冲突:
image-20200330150747452
「子仓库第二次修改与父仓库第二次拉取」:
再次修改本地子库的文件并推送到对应的远程仓库,父版本库再次将远程子库更新的文件拉取到本地进行同步:
image-20200330151140092
此次却成功了!为何一样的操做,有的时候成功有的时候失败呢?
解决方案
缘由出如今--squash指令中。实际上,--squash指令把子库中的提交信息合并了,致使父仓库在执行git pull操做时找不到公共的父节点,从而致使即便文件没有冲突的内容,也会出现合并冲突的状况。其实不使用--squash也会有这种问题,问题的根本缘由仍然是「三方合并时找不到公共父节点」。咱们打开gitk:
image-20200330154944300
从图中不难看出,当使用subtree时,子库与父库之间是没有公共节点的,因此时常会由于找不到公共节点而出现合并冲突的状况,此时只须要解决冲突,手动合并便可。
❝
不使用subtree时,普通的版本库中的各分支总会有一个公共节点:
image-20200330160206258
❞
「再次强调」:使用--squash指令时必定要当心,要么都使用它,要么都不使用。
7.抽离子库
git subtree split
当开发过程当中出现某些子库彻底能够复用到其余项目中时,咱们但愿将它独立出来。
「方法一」:能够手动将文件拷贝出来。缺点是,这样会丢失关于该子库的提交记录;「方法二」:使用git subtree split指令,该指令会把关于独立出来的子库的每次提交都记录起来。可是,这样存在弊端:好比该独立子库为company.util,当一次提交同时修改了company.util和company.server两个子库时。经过上述命令独立出来的子库util只会记录对自身修改的提交,而不会记录对company.server的修改,这样在别人看来此次提交就只修改了util,这是不完整的。