git 分支的插图

Git 子模块

返回列表

Git 子模块允许您将 git 存储库保留为另一个 git 存储库的子目录。Git 子模块只是在特定时间快照下对另一个存储库的引用。Git 子模块允许 Git 存储库合并和跟踪外部代码的版本历史记录。

什么是 git 子模块?

通常,代码存储库将依赖于外部代码。这个外部代码可以通过几种不同的方式合并。外部代码可以直接复制并粘贴到主存储库中。这种方法的缺点是会丢失对外部存储库的任何上游变更。合并外部代码的另一种方法是使用语言的包管理系统,如 Ruby Gems 或 NPM。这种方法的缺点是需要在部署原始代码的所有地方进行安装和版本管理。这两种建议的合并方法都无法跟踪外部存储库的编辑和变更。

git 子模块是主机 git 存储库中的一条记录,它指向另一个外部存储库中的特定提交。子模块非常静态,只跟踪特定的提交。子模块不跟踪 git 引用或分支,也不会在主机存储库更新时自动更新。向存储库添加子模块时,将会创建新的 .gitmodules 文件。.gitmodules 文件包含有关子模块项目的 URL 和本地目录之间映射的元数据。如果主机存储库有多个子模块,则 .gitmodules 文件将为每个子模块提供一个条目。

什么时候应该使用 git 子模块?

如果您需要对外部依赖关系进行严格的版本管理,那么使用 git 子模块是有意义的。以下是 git 子模块的一些最佳用例。

  • 当外部组件或子项目变更过快或即将发生的变更将破坏 API 时,为了您自己的安全,您可以将代码锁定到特定提交。
  • 当您有一个不经常更新的组件,而您想将其作为供应商依赖关系进行跟踪时。
  • 当您将项目的一部分委托给第三方,并且想要在特定时间或发布时整合他们的工作时。同样,这在更新不太频繁的情况下有效。

git 子模块的常用命令

添加 git 子模块

git submodule add 用于向现有存储库添加新的子模块。以下是创建空代码存储库并探索 git 子模块的示例。

$ mkdir git-submodule-demo
$ cd git-submodule-demo/
$ git init
Initialized empty Git repository in /Users/atlassian/git-submodule-demo/.git/

这一系列命令将创建一个新目录 git-submodule-demo,进入该目录,然后将其初始化为新的存储库。接下来,我们将向这个全新的代码存储库添加一个子模块。

$ 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 submodule add 命令采用指向 git 存储库的 URL 参数。在这里,我们将 awesomelibrary 添加为子模块。Git 会立即克隆子模块。我们现在可以使用 git status 查看存储库的当前状态...

$ 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

现在存储库中有两个新文件 .gitmodulesawesomelibrary 目录。看看 .gitmodules 的内容显示了新的子模块映射

[submodule "awesomelibrary"]
path = awesomelibrary
url = https://bitbucket.org/jaredw/awesomelibrary
$ git add .gitmodules awesomelibrary/
$ git commit -m "added submodule"
[main (root-commit) d5002d0] added submodule
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 awesomelibrary

克隆 git 子模块

git clone /url/to/repo/with/submodules
git submodule init
git submodule update

Git 子模块 Init

git submodule init 的默认行为是复制 .gitmodules 文件中的到本地 ./.git/config 文件。这可能看起来多余,并导致有人质疑 git submodule init 的用处。git submodule init 具有扩展行为,它接受显式模块名称列表。这样可以实现仅激活存储库工作所需的特定子模块的工作流程。如果代码存储库中有许多子模块,但是您正在做的工作并不需要全部获取,那这可能会很有帮助。

子模块工作流程

子模块在父项存储库中正确初始化和更新后,就可以像独立存储库一样使用,这意味着子模块有自己的分支和历史记录。对子模块进行变更时,重要的是发布子模块的变更,然后更新对子模块的父项存储库引用。我们继续使用 awesomelibrary 的示例,并进行一些变更:

$ cd awesomelibrary/
$ git checkout -b new_awesome
Switched to a new branch 'new_awesome'
$ echo "new awesome file" > new_awesome.txt
$ git status
On branch new_awesome
Untracked files:
  (use "git add <file>..." to include in what will be committed)

new_awesome.txt

nothing added to commit but untracked files present (use "git add" to track)
$ git add new_awesome.txt
$ git commit -m "added new awesome textfile"
[new_awesome 0567ce8] added new awesome textfile
 1 file changed, 1 insertion(+)
 create mode 100644 new_awesome.txt
$ git branch
  main
* new_awesome

在这里,我们将目录变更为 awesomelibrary 子模块。我们创建了一个包含一些内容的新文本文件 new_awesome.txt,并将这个新文件添加并提交到子模块。现在我们把目录改回父项存储库并查看父项代码存储库的当前状态。

$ cd ..
$ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

modified:   awesomelibrary (new commits)

no changes added to commit (use "git add" and/or "git commit -a")

执行 git status 向我们表明,父项存储库知道对 awesomelibrary 子模块的新提交。它没有详细介绍具体的更新,因为这是子模块存储库的责任。父项存储库只关心将子模块固定到提交中。现在我们可以通过在子模块上执行 git addgit commit 来再次更新父项存储库。这将使本地内容的所有内容都处于良好状态。如果您在团队环境中工作,那么您必须通过 git push 子模块更新和父项存储库更新。

使用子模块时,常见的混乱和错误模式是忘记为远程用户推送更新。如果我们重新审视刚才所做的 awesomelibrary 工作,我们只将更新推送到父项存储库。另一位开发人员会去拉取最新的父项存储库,它将指向一个他们无法拉取的 awesomelibrary 提交,因为我们忘了推送子模块。这将破坏远程开发人员的本地代码存储库。为避免这种失败情况,请确保始终提交并推送子模块和父项存储库。

总结

Git 子模块是利用 git 作为外部依赖关系管理工具的强大方法。使用 git 子模块之前,先权衡一下它们的优缺点,因为它们是一项高级功能,可能需要一段时间才能让团队成员采用。

准备好了解 Git 了吗?

试用本交互式教程。

立即开始