Git submodule 功能可以让我们在一个仓库中添加另一个仓库作为当前仓库的子仓库。Git submodules 只是某个仓库某一时刻的一个状态的引用,即某个 commit 的引用。
开发一个复杂的项目时,通常需要依赖一些外部的代码包,这样既方便了代码管理,也免去我们重复造轮子的精力和时间。
依赖外部代码通常有两种方式:
所以,如果你在一个复杂的项目里需要依赖一个外部的 package,并且你需要频繁更新这个 package,那么你可以使用 Git submodules 的功能。
git submodule 是某个 git 仓库的某个 commit 的引用,它并不会追踪仓库的具体分支。在仓库中添加一个 submodule,会自动创建一个 .gitsubmodules
文件,该文件包含了所有子模块的信息——子模块项目地址、子模块在当前仓库中的代码位置。
使用 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
当你克隆了一个包含 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
,循环初始化和更新所有子模块(包含嵌套子模块)。
子模块有自己独立的版本管理。还记得前面说过的吗?仓库中的子模块引用的仅仅是子模块中的一个 commit,而不是一个 branch。所以当子模块发生更新后,我们必须在父仓库中更新一下子模块的引用,将其指向子模块中新的 commit。这意味着你在子模块中的更新,需要两次 add 和 commit 操作,一次是子模块自己的 add、commit,还有一次是为了在父模块中更新子模块引用,这是一个比较麻烦的一点,也是我一直厌恶的一点,但没有办法,机制就是这样,既然无法改变,那只有接受。
参考资料:
(完)