git submodule

Posted bigben0123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了git submodule相关的知识,希望对你有一定的参考价值。

参考

https://blog.csdn.net/zajin/article/details/89042509

https://blog.csdn.net/breavo_raw/article/details/101857235


前言
工作中碰到这样一个场景,一个项目里面的代码分为基础代码和定制化代码,定制化代码是针对不同客户的,基础代码需要和定制化代码分开管理,部署的时候是作为一个项目一起跑的。

我们在这里使用git submodule功能来尝试解决代码的管理问题。

功能介绍
submodule 目前对 git 仓库拆分的已有实现之一。
它允许将一个Git仓库作为另一个Git仓库的子目录。能够将另一个仓库克隆到自己的项目中,同时还保持独立的提交。

功能听上去非常的牛逼,那我们赶紧来试用一下吧!

使用
准备工作
我们将在Github上进行相关功能的操作,并在操作过程中时刻观察仓库状态的变化,加深理解。

首先确保自己有GitHub的账号哈~

在github新建两个仓库
很简单,像这样

我这里直接使用github新建了一个仓库作为主仓库。
依葫芦画瓢直接在新建两个子仓库,分别起名child1_repo,child2_repo(名字随便起,自己认得就行)

我们把主仓库(以下称parent)clone下来,随便提交点什么,像这样

同样对子仓库child1_repo(以下简称child1)和child2_repo(以下简称child2)进行一样的操作。

将子仓库添加至父仓库
来到parent目录下,执行

# []中为子仓库的git url
git submodule add [child1 url]
git submodule add [child2 url]
1
2
3
会出现如下提示:
我们来看看这条命令做了哪些事情:

首先,child1和child2被克隆到了parent目录下
使用git status命令查看以下文件状态

可以看到,除了两个子仓库外,还多了一个叫.gitmodules的文件,这是一份子模块与路径的映射关系图,git 根据这份文件去识别 submodule。现在查看一下文件内容:
[submodule "child1_repo"]
path = child1_repo
url = git@github.com:xxx/child1_repo.git
[submodule "child2_repo"]
path = child2_repo
url = git@github.com:xxx/child2_repo.git
1
2
3
4
5
6
一个子仓库对应一个git url,清晰明了。

提交
我们将parent下的改动进行提交,并push到远程仓库上

我们注意到,在commit了改动之后,除了常规的100644之外(100代表regular file,644代表文件权限),还出现了160000。

160000 代表 Git 中的一种特殊模式,它本质上意味着你是将一次提交记作一项目录记录
的,而非将它记录成一个子目录或者一个文件。
上面这句话是什意思呢,我们通过git diff 查看刚才提交的信息

# child1_repo
Commit 8e182c74182adde49f2b6d192f2a85c50d87f538
Commit Message add child1
# child2_repo
Commit 64a14d2c3f22aee584071b93c2bda4ef5243e4a2
Commit Message add child2
1
2
3
4
5
6
可以看到,这两个子仓库在parent下commit的就是一次提交的信息,而git就把它们当作是一个目录进行记录的。

我们查看一下远端仓库的情况:

可以看到,两个子仓库均已经push到了parent下,另外在child1和child2后面还跟着一串code,这是子仓库的commitId的后缀,表示该子仓库签出时的版本(这个在下面解释),这个code是不会显示在克隆到本地的仓库中的。

进入child1_repo @ 8e182c7观察一下仓库的状态

如图所示,此时child1处于一种游离的状态,在git页面无无法新建和编辑文件
点击编辑会提示:

you must be on a branch to make or propose changes to this file
1
我们点击上图画圈的部分,将其切换到master分支后,我们就可以执行正常的git操作。
(注意:此时我们已经是在child1_repo下进行操作的)

我们查看一下child1的commit记录

可以看到,child1提交的commitId与parent中的child1_repo @ 8e182c7的后缀是一致的,因为本地parent子模块在push时签出的版本正是8e182c7

总结一下,对于child1和child2来说,只有它们的远程 URL 会被记录在父仓库中,以及它们在主项目中的本地路径和签出的版本。

模拟多人协作
我们在git 页面尝试进入child1,其下有一个文件,我们尝试编辑这个文件,并将修改的内容提交,像这样

再看看child1的commit记录,此时git head的指针已经指向了新的commitId:

这是一个标准的git操作流程。

我们返回到parent中查看一下child1的状态,并没有变化,记录依旧保持在第一次签出时的版本。

拉取远程子模块的代码到本地
子仓库在远程更改了,那么我们本地如何进行同步呢(如果只要修改主仓库的代码,正常的git操作就可以了)

这里以child1为例,进入parent,执行

git submodule update --remote child1_repo
1
这条命令将会拉取child1_repo中最新的提交,结果如下:

这里,我们如果单纯的执行git submodule update,我们拉取的将是远程parent下最后一次签出的子仓库的版本。

另外一种更新子仓库child1_repo的方法就是直接进入child1_repo目录下,像任何普通的Git那样进行操作即可。

本地子模块修改提交到远程
我们在本地的子模块中进行了一些修改,需要进行提交,如何操作呢?

这里依旧以child1_repo为例进行阐述,在上述拉取child1最新的一次提交之后,我们本地相对于远端的parent已经有了改动,我们现在在本地再次进行一些修改,并进行提交。

我们在本地cd到child1_repo目录中瞅瞅~


首先,我们的修改是被捕捉到了,这说明我们是可以进行正常的git操作的。
观察一下此时child1_repo的状态,它没有指向任何一个分支,而是停留在一个称作“游离的 HEAD”的状态,这意味着没有本地工作分支(例如 “master” )跟踪改动,你也就没办法提交代码。

解决方案很简单,使用git checkout branch命令切换到某个分支就可以了
我们把之前修改commit一下,然后切换到master分支上。


我们尝试将本地子仓库的修改推送到远程(上述操作存在一个问题,导致我本地的修改丢失了,记得先pull,我这里重新修改提交了)

我们切回到parent,使用 add commit push 三连将代码提交到远端(记得先pull),完事我们查看下远端的仓库

看来已经正确的提交了:)

可能会出现的幺蛾子
Git对于子模块的管理相对来说比较复杂。当然出现异常的情况也是不可避免的,我们来看看在使用git submodule的过程中可能会出哪些问题。

主模块提交并推送了改动,而子模块并没有推送
如果我们在主仓库中提交并推送但并不推送子模块上的改动,其他人尝试更新子模块的人会遇到麻烦,因为他们无法得到依赖的子模块改动。那些改动只存在于我们本地的拷贝中。

我们尝试一下上述过程,对child1_repo做一些修改并提交,但不推送。

这里我又添加了一句话,并提交了。我们回到parent瞅瞅


好,parent追踪到了submodule的改动,现在我们把它提交并推送到远程

成功了…看来git并不会主动帮你检测子模块的改动是否推送。

我们把本地的仓库删除,重新克隆一份下来。我们观察一下目录,所有子仓库只有一个空的文件夹,这里需要我们去git submodule init初始化本地配置文件以及 git submodule update 拉取代码。

直接报错,无法更新了。

为了确保这不会发生,你可以让 Git 在推送到主项目前检查所有子模块是否已推送。
使用如下命令

git push --recurse-submodules=check # 如果子模块没有提交,会直接报错
# or
git push --recurse-submodules=on-demand # 如果子模块没有提交,会尝试提交,提交不成功同时会阻止主仓库的推送
1
2
3
其他
其实问题还不少,暂时不一一复现了,主要出现的问题参考了这篇博客
另外还有别的可能出现的问题(e.g.将子目录转换成子模块、git submodule update failed等),可以参考文章结尾给出的文档

在有子模块的项目中切换分支可能会造成麻烦
如果你创建一个新分支,在其中添加一个子模块,之后切换到没有该子模块的分支上时,你仍然会有一个还未跟踪的子模块目录,这时候如果不小心提交了这个子模块(git commit -am “message”),就会有问题了。

提交和获取的问题
对子模块做了修改,需要先推送子模块再主模块,同时拉取的时候也需要先主模块,再子模块。

记得先切换分支
对子模块做本地修改需要先检出分支,否则有可能在 “游离的 HEAD” 上做修改。

记得pull完了还得update一下
如果你的同事更新了 submodule,然后更新了父项目中依赖的版本号。你需要在 git pull 之后,调用 git submodule update 来更新 submodule 信息。这儿的坑在于,如果你 git pull 之后,忘记了调用 git submodule update,那么你极有可能再次把旧的submodule 依赖信息提交上去(使用 git submit -am "message" 或者 git add .提交的人会遇到这种事)。

参考
git子模块
git submodule(csdn)
子模块 - Git Tower
git submodule updated failed
————————————————
版权声明:本文为CSDN博主「皇家茶壶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/breavo_raw/article/details/101857235

以上是关于git submodule的主要内容,如果未能解决你的问题,请参考以下文章

Git 常用操作 - git clone/git checkout -b/git diff/git push/git pull

Git 学习路线

从0到1带你掌握git(一分钟掌握git)--git如何下载?git如何使用?git是什么?git怎么获取文件?

Git认识与使用 Git

Git认识与使用 Git

Git认识与使用 Git