Better Practice in Git Submodule

(文中提到的仓库特指git仓库)

1. 背景介绍

开发中可能会遇到这样的情况:

  • 项目依赖一个library
  • 这个library在多个项目中都要用到(符合封装复用的原则)
  • 我需要根据需求修改library的内容
  • 不同项目依赖的library可能是不同版本

如果把library的文件直接放在当前项目的仓库中,会导致一个问题:不同项目同步library的更新会变成一场灾难(耗费大量时间且易出错)。

于是,submodule 诞生了:

submodule提供了一种能力:在一个仓库中,允许以“子目录”的形式存放另一个仓库,同时两个仓库独立提交。

(“子目录”打引号是因为并不是真正的子目录)

回到上面的需求:

  • library作为单独的仓库以submodule的形式引入到不同的项目中
  • 一个项目更新了library,其他项目可以方便地更新
  • 不同的项目可以方便地维护不同的library版本

...

2. 各个场景的用法

2.1 给仓库添加submodule

git submodule add url moduleName
添加submodule

2.2 clone一个有submodule的仓库

2.2.1常规操作

如果clone的仓库有submodule,是不能直接使用的,需要运行以下两条命令来初始化。

git submodule init
git submodule update
初始化有submodule的仓库

2.2.2 submodule中嵌套了submodule

上面的操作不会初始化嵌套的submodule,如果遇到嵌套的情况,你当然可以cd到对应目录,再次执行git submodule init和git submodule update,还有一个便捷操作:

git submodule update --init --recursive

2.3 更新远程submodule

submodule会经常更新, update 是submodule非常重要的操作,它的作用是:

Update the registered submodules to match what the superproject expects by cloning missing submodules, fetching missing commits in submodules and updating the working tree of the submodules.

可见,根据命令的参数,update的功能非常丰富:

  • clone本地缺失的submodule
  • fetch(submodule中)本地缺失的commits
  • 更新工作空间(就是当前目录中看到的代码)

更新远程submodule的基础操作是

git submodule update --remote

以下是各种场景的用法

2.3.1 远程添加了新的submodule

git submodule update --init --remote

2.3.2 远程submodule有更新,本地没更新

git submodule update --remote

2.3.3 远程submodule有更新,本地也有更新

现在是这篇文章最重要的部分,因为这个场景是submodule常见场景中最让人困惑的,如果你直接按照上面的操作(git submodule update --remote), 会发现本地的提交不见了。

想要解决这个问题,就要知道git submodule update --remote到底做了什么:

  • 对于每一个submodule, git 会fetch其远程master分支(*可配置)的最新commit。
  • 拉取到新的commit后,checkout到最新的commit(此时HEAD指针未指向任何分支,即submodule处于游离状态-detached)

综上,默认状态下,所有的submodule都处于游离状态, 新增本地提交后也是游离状态,这时执行git submodule update --remote,submodule被checkout到远程master分支(*可配置)最新的commit, 于是本地的提交从工作空间中消失了(如下图)。

常见问题之: 游离状态

了解到原因,就知道如何解决更新submodule时的游离问题以及本地修改丢失的问题

(1) 给submodule 切分支
cd到submodule的目录下,手动checkout到具体的分支下
(2) update时添加 --merge或--rebase
git submodule update --remote --merge
正确操作
Tip1: 关于配置submodule的分支

操作git submodule update --remote fetch的分支是可以配置的(默认是master),配置方式是:

git config .gitmodules submodule..branch 

也可以改直接改.gitmodules这个配置文件

# in file .gitmodules
[submodule "module/D"]
    path = module/D
    url = [email protected]
    branch = test//改成对应的分支名 
Tip2: 别慌,你的东西丢不了

刚接触submodule的时候,很多朋友看到自己的本地提交丢失就慌了,因为各个记录中好像都找不到刚才的内容。这里介绍一个利器: reflog(顾名思义: ref的log,因为git中的HEAD以及分支都是ref指针,我们通过插件HEAD的log就能知道刚刚“丢失”的commit)

# cd to submodule directory
git reflog HEAD
reflog 示例

3. 一些好用的设置项

3.1 push仓库时,确保修改的submodule已经push

团队协作时,有一种很常见的错误,自己提交仓库时,忘了提交submodule的commit,导致同事无法更新到submodule最新的内容(因为它只在你的本地仓库)。为了避免这种错误,可以在push时添加git push --recurse-submodules=check帮助检查:

git push --recurse-submodules=check

或者修改配置,一劳永逸。

git config push.recurseSubmodules check

3.2 更好看的设置项

默认的设置项,看不到submodule修改的内容,可以修改配置:

git config status.submodulesummary 1
更友好的status

3.3 这些命令也太长了吧

上文中最重要的命令应该是git submodule update --remote了,本来就挺长的,在加一个--merge--rebase就更长了,这也太长了吧,手都敲酸了。。。别急,git alias(git别名机制)来了:

git config alias.supdate 'submodule update --remote --merge'

这样设置后就可以用git supdate代替git submodule update --remote --merge操作了。

4. 一些需要注意的东西

4.1 对于模型的理解

  • 虽然工作空间中看到的submodule是一个个子目录,但submodule不是以子目录的形式存放在主仓库中的(这也是前文“子目录”加引号的原因),这一点比较违反直觉。
  • 在底层的存储上,所有的submodule也是存在主仓库的.git目录中。

你可能感兴趣的:(Better Practice in Git Submodule)