chenqunfeng / Blog

个人技术记录博客
6 stars 1 forks source link

Git submodule与subtree #12

Open chenqunfeng opened 6 years ago

chenqunfeng commented 6 years ago

前言

当项目越来越庞大之后,不可避免的要拆分成多个子模块,我们希望各个子模块有独立的版本管理,并且由专门的人去维护,这时候我们就要用到git的submodule或subtree功能。

submodule

创建带子模块的版本库

cd project  // 父模块根目录
git submodule add A B // A:子模块的git地址;B:本地路径
// 可以看到目录下多了.gitmodules和对应的B目录
// 同时.git/config也多了对应的submodule配置(记住这几点变化,在删除子模块的时候会用到)
git status
git add .
git commit -m "add submodule"
git push origin master

至此,一个带有子模块的版本库就已经创建好并更新到远程仓库了

克隆一个带有子模块的版本库

git clone A project // A为带有子模块的版本库的git地址
cd project // 可以看到.gitmodules文件和对应的子模块空文件夹
git submodule init // 初始化子模块,只需要初始化一次即可
git submodule update // 更新完之后,可以看到子模块文件夹中的文件已经都更新好了

克隆之后的主版本库中的子模块只是一个引用,所以默认没有内容;而远程仓库中对应子模块则是对应子模块的commit id引用

修改子模块,并更新到父版本库

// 进入子模块目录之后,需要注意子模块的分支
// 刚克隆时,分支是默认的,而不是具体的分支
// 修改时需要对应checkout到对应的分支
cd project/common 
echo "This is a submodule." > common.txt
git add .
git commit -m "add common.txt"
// 到目前为止我们更新了子模块并更新了子模块的代码到远程仓库
git push origin master
cd ..
git add .
git commit -m "modify common"
// 此时,将子模块的更改同步到父版本库中
// 可以看到仓库中对应的子模块的commit id已经更新为最新的commit id了
git push origin master

更新子模块

cd project
git pull origin master
git submodule update

删除子模块

还记得前面创建带子模块的版本库,git submodule add 之后增加的内容不,删除子模块时需要将这增加的内容全部清除干净。

git rm --cached common
rm -rf common
rm .gitmodules
vim .git/config

subtree

创建带子模块的版本库

// -f表示添加的同时fetch仓库资源;A:remote名称; B:远程git地址
git remote add -f A B
// A:本地目录; B:远程git地址; C:分支
git subtree add --prefix=A B C --squash
git add .
git commit -m "add subtree"
// 至此,带有子模块的版本库便创建好并更新到远程仓库了
// 而这里的common与submodule不一样,是一个完整的文件目录而不是一个引用
git push origin master

克隆一个带有子模块的版本库

// 至此,一个带有子模块的版本库便克隆完成了 // 我们可以发现与submodule相比,少了初始化和更新的操作 git clone A project

修改子模块,并更新到父版本库

cd project/common
echo "This is subtree" > a.txt
git add .
git commit -m "add a.txt"
// 向远程子模块推送代码更新,A:本地目录; B:前面添加的subtree的remote别名; C:分支
// 这里有点内容需要注意一下,就是A的路径设置需要和前面add的路径形式保持一致
// 否则可能会抛错:assertion failed
git subtree push --prefix=A B C 
git push origin master // 更新父版本库的修改到父版本库

ps:其中,向子模块推送代码更新时,这个过程可能会很慢,因为它会自己去检查主仓库中的所有commit,然后找出实际上针对子模块更新的commit,然后再推送对应的commit

更新子模块

cd project/common
// A:本地目录; B:前面添加的subtree的remote别名; C:分支
// 当主仓库有修改的代码,且未commit,这个时候去更新subtree的代码时
// 会提示Working tree has modifications.  Cannot add.
git subtree pull --prefix=A B C
git status // 如果pull阶段会有子模块更新,你也看不到对应的修改项
git push origin master // 你只需要在更新完毕之后将更新推送到仓库即可

ps:如果都是在同一个父仓库中操作,或许你不需要care后面两个指令,但是如果你是在一个父仓库更新并推送了子模块的更新,然后在另一个父仓库中拉去子模块的最新代码并更新到远程仓库中,你的操作也跟正常的父仓库代码更新一样,没有任何不一致的地方

删除子模块

rm -rf common

submodule与subtree的异同

从以上的各个操作来看,我们可以明显感受到submodule和subtree之间的差异性。 其中一个明显的感受就是subtree不需要像submodule一样每一次的代码更新都需要去更新subtree子模块的代码,subtree的子模块在更新和推送之外,就感觉跟一个普通的文件夹一样,而submodule则每次都需要执行git submodule update,否则则可能导致submodule的指向出错。 另外,除了以上的区别之外,在指令方面subtree除了pull和push的指令稍复杂之外,其他方面的操作也都优于submodule。