日期:2022年6月12日标签:DeveloperHandbook

Git Submodules #

Git submodule 功能可以让我们在一个仓库中添加另一个仓库作为当前仓库的子仓库。Git submodules 只是某个仓库某一时刻的一个状态的引用,即某个 commit 的引用。

介绍 #

开发一个复杂的项目时,通常需要依赖一些外部的代码包,这样既方便了代码管理,也免去我们重复造轮子的精力和时间。

依赖外部代码通常有两种方式:

  • 直接将外部代码拷贝到当前项目源码中,这种方式是最不建议的方式,因为它丢弃了外部代码之前的 git 版本信息,而且拷贝容易出错,可能造成功能的缺失,导致 bug 的产生。
  • 另一种方式是依靠各种编程语言的 package manager,例如 npm、NuGut,这种方式是目前最流行使用也是最多的。使用这种方式的缺点就是,你需要对 package 进行各种版本管理,如果在依赖的 package 里发现 bug,或者新增功能,你都需要在每次更新后重新发布 package。

所以,如果你在一个复杂的项目里需要依赖一个外部的 package,并且你需要频繁更新这个 package,那么你可以使用 Git submodules 的功能。

git submodule 是某个 git 仓库的某个 commit 的引用,它并不会追踪仓库的具体分支。在仓库中添加一个 submodule,会自动创建一个 .gitsubmodules 文件,该文件包含了所有子模块的信息——子模块项目地址、子模块在当前仓库中的代码位置。

使用 #

1.添加子模块 #

使用 git submodule add <submodule_url> 命令添加子模块。

$ git submodule add https://bitbucket.org/jaredw/awesomelibrary
Cloning into '/Users/atlassian/git-submodule-demo/awesomelibrary'...
remote: Counting objects: 8, done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 8 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (8/8), done.

添加子模块后,可以使用 git status 查看仓库状态,在仓库中多了一个 .gitmodules 文件和 awesomelibrary 文件夹。

$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

 new file:   .gitmodules
 new file:   awesomelibrary

查看 .gitmodules 文件内容。

[submodule "awesomelibrary"]
 path = awesomelibrary
 url = https://bitbucket.org/jaredw/awesomelibrary
  • path 指定了子模块在当前仓库中所在的位置
  • url 指定了子模块的仓库位置

2.子模块初始化和更新 #

当你克隆了一个包含 submodules 的 git 仓库,使用 git clone 命令并不会拉取子模块的代码,克隆下来的仓库中的子模块文件夹都是空的,所有的子模块必须先初始化然后更新

可以使用 git submodule init [<submodule_name> ...] 命令初始化子仓库,它的作用是将 .gitmodules 文件中的子模块信息拷贝到当前仓库的 ./.git/config 配置文件中,乍一看这一步骤似乎是多余的,因为 .gitmodules 已经包含了子模块的信息,为什么还要多此一举呢?其实,这并不是一个多余的步骤,假设当前仓库包含了很多子模块,但是此时你开发的工作并不需要全部的子模块代码,所以你可以指定初始化你需要的子模块,然后再拉取代码。

$ git submodule init
Submodule 'awesomelibrary' (https://bitbucket.org/jaredw/awesomelibrary) registered for path 'awesomelibrary'

当子模块初始化后,可以使用 git submodule udpate 命令拉取子模块的代码。

$ git submodule update
Cloning into 'awesomelibrary'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'awesomelibrary': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'

更新后,再次检查 ./awesomelibrary 文件夹,发现它不再是一个空的文件夹了,可以使用 git submodule init && git submodule update 将两个命令合并成一个。

这里有一个技巧,当存在嵌套模块时,子模块包含子模块的情况,可以使用 git submodule update --init --recursive,循环初始化和更新所有子模块(包含嵌套子模块)。

3.子模块工作流程 #

子模块有自己独立的版本管理。还记得前面说过的吗?仓库中的子模块引用的仅仅是子模块中的一个 commit,而不是一个 branch。所以当子模块发生更新后,我们必须在父仓库中更新一下子模块的引用,将其指向子模块中新的 commit。这意味着你在子模块中的更新,需要两次 add 和 commit 操作,一次是子模块自己的 add、commit,还有一次是为了在父模块中更新子模块引用,这是一个比较麻烦的一点,也是我一直厌恶的一点,但没有办法,机制就是这样,既然无法改变,那只有接受。

参考资料:

(完)

目录