通过案例学习git-submodule

1. Git submodule的简介

如果你也曾经检出过一个比较大型的项目,肯定有过这样的痛苦经历,仅仅clone下载项目就花费了非常长的时间。git会把一个仓库的全部版本数据放到.git目录,如果一个大型项目长时间的提交后,就会造成仓库过大下载变慢。git submodule是一个大型项目模块分拆编排的解决方案,合理的引入submodule或许可以显著改善大型项目结构和编排。下面我们一起动手学习一下这个小技能。

1.1 从git的一个缺点说起

首先,我们从git的一个小缺点说起,很多人应该都用过SVN(git流行前曾经是很多公司的主力版本管理工具),SVN的任意一个目录都可以单独检出,比如,如果我们仅维护一个大项目的一个模块,那么我们可以仅检出对应的模块的目录。而git一个仓库只能作为一个完整的检出“原子”,所以才有了开篇说的问题,对于一个大型的长期的项目,检出就变的非常卡慢。

1.2 git submodule的适用场景

git submodule可以把一个项目仓库,分解为多个相互独立的子模块子仓库,可以显著降低主仓库的大小。了解了git submodule的基本形态,或许我们都能想到一些适合git submodule的应用场景。下面是笔者想到的两个适用场景,并且后面动手环节,也会从里面选择一个场景进行试验。

  • 大型项目的模块分割,编译版本编排。
  • 在多个项目中使用和完善的公共工具模块项目。

总之,如pro git书中所说:

当想要多个项目当做独立的仓库,同时又想在其他项目中使用它们时,就可以使用submodule。 –《Pro Git》

1.3 git submodule的命令汇总

在动手做实验之前,我们先简单了解一下git submodule相关的命令,这样动手的时候大家能有一个基本的了解。

子模块命令git submodule。

1
2
3
4
5
git submodule add : 添加一个子模块
git submodule init :初始化子模块
git submodule update :应用子模块的远程更新
git submodule sync :子模块url修改后同步变化
git submodule foreach ‘git stash’ :子模块遍历执行命令

一些命令下会有子模块相关的一些选项。

1
2
3
4
5
git clone  --recurse-submodules :递归下载所有子模块
git checkout --recurse-submodules :递归检出子模块
git pull --recurse-submodules :拉起并更新,包括子模块
git push --recurse-submodules=on-daemon :提交更新
git diff –submodule :查看变化

2. 案例1:工具类项目子模块

本章我们将动手来使用git submodule相关的命令完成一个使用案例。这里我们选择1.2节中第二个适用场景,在多个项目中使用和完善的公共工具模块项目。

这里我们假设有一个util模块存放的是一些各个项目中总结下来的工具类;然后,我们创建了一个新的product项目,需要把这个模块作为我们的一个子模块引入;最后,从一个新加入小组的开发者角度,完整的演示检出和参与完善项目。

下面的动手操作,会先给出所有的操作步骤,然后给出对应的完整的命令列表。

2.1 创建项目服务器仓库

我们假设实验的根目录为submodule。

  • 在submodule中创建一个server目录,用来表示我们的git服务器;
  • 在server目录中,创建util.git和product.git目录来表示我们工具项目和主项目。
  • 分别进入两个*.git的目录,初始化我们的git仓库。

完整命令如下:

1
2
3
4
5
6
7
8
9
mkdir submodule && cd submodule
mkdir -p server/util.git
mkdir server/product.git

cd server/util.git
git init --bare

cd ../product.git
git init --bare

2.2 在项目中使用submodule

  • 在根目录下,创建一个wks-1目录,用来表示我们的工作空间。
  • 使用maven的maven-archetype-quickstart创建util/product项目。
  • 在新创建项目中初始化为git仓库,然后添加初始化提交;
  • 在项目中添加git远程仓库,关联2.1节中创建的仓库“服务器”;
  • 在product仓库中,添加util子模块。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    mkdir wks-1 && cd wks-1
    # ...(略)maven命令或者ide创建util/product仓库
    cd util
    git init
    git add *
    git commit -m 'init'
    git remote add origin ../../server/util.git
    git push origin master -u

    cd product
    git init
    git add *
    git commit -m 'init'
    git remote add origin ../../server/product.git
    git push origin master -u

    git submodule add ../../server/util.git
    git add *
    git commit -m 'add util submodule'
    git push

2.3 最简使用模式“仅跟踪submodule的更新”

使用submodule的最简模式就是仅仅跟踪子模块的更新,使用最新版本的子模块代码。那么这种模式下,仅仅需要几个命令就可以完成。如下几个命令:

1
2
3
git submodule sync #同步子模块url和分支配置变更
git submodule init #检出子模块代码完成初始化
git submodule update --recursive #更新应用子模块的变更

我们在主项目product上做变更、提交等操作,可以看到和一个正常的git仓库完全一样。我们唯一要做的是,当接受到submodule升级通知时,执行一下上面的几个命令即可。

1
2
3
4
5
6
7
8
9
10
11
12
cd wks-1/util
echo 'stringutil' > src/main/java/StringUtil.java
git add *
git commit -m 'add StringUtil'
git push
# ---
cd wks-1/product
git pull #拉取更新,但是子模块并不会merge
git submodule update --recursive #应用子模块更新
git pull --recurse-submodules #该命令等于上面两个命令

git submodule update --remote util #可以从父项目中直接更新同步远程子模块

2.4 同时编辑主项目和子模块

更常见的场景是,我们要同时在主项目和子模块上工作。如果已经按照上面的操作步骤操作,我们的product项目中已经包含了util项目的完整代码。其实,这个时候就已经完全可以同时在两个仓库上工作了。但是,默认情况下,子模块并没有分支跟踪远程分支,要在子模块正常工作,通常需要设置一个本地分支跟踪远程分支。

这里也有两种使用方式,如果你每次都切换到子模块的目录,那么工作方式和一个正常的仓库几乎一样;如果你是在主目录中直接操作子模块仓库,那么就需要一些命令的submodule选项。这里仅仅演示第二种情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cd product/util
git checkout master

echo 'date util' > src/main/java/DateUtil.java
git add *
git commit -m 'add DateUtil'

cd ..
git add *
git commit -m 'add DateUtil' #主项目也需要添加子模块变更

# 在提交主项目时,必须确保子模块已经提交
# 提交前先更新子模块
git submodule update --remote [--merge/--rebase] #merge/rebase二选一
# 子模块提交,要么进入每一个子模块执行,要不就要使用主项目的push操作,并添加选项
git push --recurse-submodules=on-demand

2.5 检出包含子模块的项目

上面都是从初始创建的步骤描述的。假设此时有新成员加入开发小组,那么需要那些操作,然后才可以达到同时在多个仓库上开发项目呐。一般来说如下操作:

  • 检出包含子模块的项目,检出正确的分支,初始化子模块;
  • 进入子模块并检出创建对应分支跟踪远程分支,其他的就和2.4步骤基本一致了。
    1
    2
    3
    4
    5
    git clone <url>
    git checkout dev --recurse-submodules
    git submodule sync
    git submodule update --init --recursive
    # ... (略)进入子模块,并检出分支跟踪远程分支

    3. 案例2:前中台衔接项目(backend for frontend)

    在互联网大中台的环境中,为了更好的适配前端请求,通常会有一个聚合层。该层核心的处理逻辑是,聚合多个子域的能力,并提供给前端符合业务逻辑的接口,该层被称为BFF(backend for frontend)。本章的案例中,我们就假设这样一个BFF场景,并借助submodule优化项目分工和发布编排。

假设,我们正在开发一个零售项目,根据一定的领域规划,我们的整个项目拆分了商品、订单、评价三个敏捷开发小组。各个小组聚焦所在领域的业务开发和高可用、高并发设计,并期望开发一个BFF项目为前端提供适配。普通git仓库下,我们只能把BFF仓库代码权限开发给三个小组的开发人员,大家共同在一个仓库下协同工作。God bless,如果每个人都能清晰的提交,并步调一致,大家不会遇到问题。可是,如果各个小组开发步调不一致,上传代码业务不清晰,整个项目的分支管理就会乱成一团麻。当项目发展到这样的情况时,就是时候引入git submodule来优化分工和编排版本发布了。

3.1 如何优化分工?

前面描述的案例下,submodule如何优化分工呐?很明显,可以按照工作小组把聚合层的业务代码拆分为多个submodule;这样,各个小组就可以几乎不会相互影响的独立工作,甚至独立启动调试。各个小组也可以按照自己的节奏完成开发任务,然后由BFF主项目把各个子模块组合到一起编译打包上线。整个聚合层的分工就可以完全贴合中台的分工,各个仓库提交也互不影响。

使用submodule优化分工后的目录结构如下所示:

bff
├── product     # 商品聚合子模块
├── order       # 订单聚合子模块
├── comment     # 评论聚合子模块
├── bff-web     # bff启动工程
├── .gitignore  # ignore配置
├── .gitmodules # 子模块配置
└── pom.xml     # 父工程pom

3.2 如何优化发布编排?

什么是发布编排呐?我们知道,submodule在主项目中,仅仅是记录了子模块的url、分支、所在的提交信息。而且主项目不同分支可以设置不同的子模块分支,甚至主项目中使用的具体的子模块的提交点。有了这样的能力,主项目就可以灵活的编排一次发版中,各个子模块所使用的分支,所在的提交点,这样便达到编排发布版本的能力。子模块的演进对主项目来说可以完全不用关心,只需要知道我要发布的版本使用的分支和提交点就可以了,灵活性非常好。

上面描述的案例,对比上一个案例,并不会有新的submodule命令,无非是一个子模块变成多个子模块,所以这边不在赘述。主要讲解案例场景,优化分工和发布编排的应用原理解释。

4. 总结

上面演示了submodule的基本使用场景,大家应该有了一个基本的了解,咱们再重新总结一下submodule相关的命令。如1.3节中所列,这些命令基本分成两组,第一组就是添加同步submodule的一些基本操作,这些命令是引入submodule的基础;第二组就是我们日常使用的git基本命令,添加了一些选项,以使之在包含子模块的仓库中正常工作;分组后其实就非常简单好记了。

我们都知道git根本是一套对象存储系统,那么最后,咱们再来看一下submodule在git仓库中是如何存储的呐?当我们在项目中添加了子模块后,项目的根目录就多出了一个.gitsubmodule的文件,里面记录了子模块的仓库地址,以及引入的分支。此时,如果我们在主项目目录中执行git diff --submodule命令,就会发现里面也会包含子模块目录的变更,变更内容为子模块所在的提交值的hash。理解了submodule的存储,后面使用过程中就更好理解命令的执行顺序和逻辑了。(完结)

,
© 2023 PLAYAROUND All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero