Git 子模块 submodule 的使用

应用场景

Git 子模块具有一定的应用场景。

比如在一个项目 P 中,该项目被分为两部分,由 A 和 B 两个团队并行开发。而 B 团队开发的内容属于公共支持类的项目,不止会被该项目使用,其他项目也可能会使用。因此 B 团队开发的内容不可能和团队 A 共用一个仓库。因为这样的话别人拉取该公共内容时会连带拉取不需要的内容。而 A 团队项目的开发又依赖于 B 团队所做的内容,并希望能随时获得团队 B 得最新成果。

此时使用 Git 的子模块功能能较好的解决该问题。

Git 得子模块功能允许把一个仓库作为另一个仓库得子目录。

概述

子模块常用得命令很简单,该文章完全学习于 Git 的官方文档《Git 工具 - 子模块》。本文对官方文档进行了提炼。

常用命令

添加一个子模块

1
git submodule add <url> path

此时会在根目录下创建 .gitmoudules 文件,该文件保存了子模块的相关配置。如下:

1
2
3
[submodule "themes/next"]
path = themes/next
url = [email protected]:hooozen/hexo-theme-next.git

添加完毕后,即拥有了一个子模块。可以在子模块进行代码的修改和提交和拉取等操作。

需要注意的是,当在上层仓库执行 commit 操作时,该仓库会记录子模块的提交记录,但也仅限于此。上层仓库并不会跟踪子模块里的文件。

所有的 git 命令,在两个仓库里独立工作。

克隆一个带子模块的项目

git clone <url>

将上层仓库克隆下来,子模块仅仅被拉取了一个文件夹,里面没有任何内容。接下来要执行两个命令:

git submodule init

初始化本地配置文件

git submodule update

拉取子目录所有数据,并检出上层仓库里所列的提交。

之后每次需要更新子模块仓库时都需运行 git submodules update。子模块并不会随上层目录的更新而一起更新。

需要注意的问题

  1. 上层仓库只保持对子模块的一个记录,而不会跟踪内部文件。

上层仓库(后简称 A 仓库)只会保留子模块(后简称 B 仓库)的一个提交记录(即 HEAD 指针值)。所以当在 A 仓库拉取更新时,B 仓库的内容并不会得到更新,而是只是更新了 B 仓库的 HEAD。当执行 git submodule update 命令时,B 仓库才会从远程仓库拉取所有数据,并根据 A 仓库中对 B 仓库的 HEAD 记录检出需要的文件内容。

  1. 子模块拉取失败的原因

有上文可知,A 仓库仅仅记录一个 HEAD 值。所以当一个开发者在本地的 B 仓库里做了代码的修改,并提交,但未推送到远程仓库。他随后在 A 仓库中进行了提交和推送,此时 A 仓库会记录 B 仓库在该开发者本地的一个 HEAD。当其他开发者尝试通过 git submodule update 更新 B 仓库时,会发生错误,因为此时 B 仓库的 HEAD 所指向的提交只存在于第一个开发者的本地。

此时,你只能查看是谁提交了本次更改,然后联系他,教育他一下。

  1. 在子模块里工作要格外小心

因为在上层仓库里进行 git submodule update 时会将子模块检出至一个游离的 HEAD,这可能导致你在子目录里提交但未在上层仓库提交的工作丢失。这种情况仍可以使用 git reflog 命令去查找出那个游离的 HEAD。但仍要避免这种失误出现。