了解和使用Git中的子模块

270 阅读4分钟

Understanding and Working with Submodules in Git

大多数现代软件项目都依赖于他人的工作。当别人已经写了一个很好的解决方案时,在自己的代码中重新发明车轮是浪费时间。这就是为什么这么多项目以库或模块的形式使用第三方代码。

Git,世界上最流行的版本控制系统,提供了一个很好的方式来管理这些依赖关系,以一种优雅,强大的方式。它的 "子模块 "概念使我们能够包含和管理第三方库,同时使它们与我们自己的代码干净地分开。

在这篇文章中,你将了解为什么Git中的子模块如此有用,它们究竟是什么,以及它们如何工作。

保持代码的独立性

为了弄清楚为什么Git的子模块确实是一个宝贵的结构,让我们看看没有子模块的情况。当你需要包含第三方代码(比如一个开源库)时,你当然可以采取简单的方法:只需从GitHub上下载代码,然后把它倒入你的项目中。这种方法当然很快,但由于一些原因,这种方法绝对是肮脏的

  • 通过强行将第三方代码复制到你的项目中,你实际上是将多个项目混为一体。你自己的项目和别人的项目(库)之间的界限开始变得模糊不清。
  • 每当你需要更新库的代码时(因为其维护者提供了一个伟大的新功能或修复了一个讨厌的错误),你又不得不下载、复制和粘贴。这很快就成为一个乏味的过程。

软件开发中的一般规则是 "将不同的东西分开",这是有原因的。对于管理你自己项目中的第三方代码来说,这当然也是正确的。幸运的是,Git的子模块概念正是为这些情况而生的。

但当然,子模块并不是解决这类问题的唯一办法。你也可以使用许多现代语言和框架所提供的各种 "包管理器 "系统之一。这也没有什么不对的地方。

然而,你可以说,Git的子模块架构有几个优点:

  • 子模块提供了一个一致的、可靠的接口--无论你使用什么语言或框架。特别是如果你正在使用多种技术,每一种技术都可能有自己的软件包管理器,有自己的一套规则和命令。另一方面,子模块的工作方式总是一样的。
  • 并非每一段代码都可以通过包管理器获得。也许你只是想在两个项目之间分享你自己的代码--在这种情况下,子模块可能提供最简单的工作流程。

Git 子模块到底是什么

Git 中的子模块实际上只是标准的 Git 仓库。没有花哨的创新,只是我们现在都知道的相同的 Git 仓库。这也是子模块力量的一部分:它们是如此强大和直接,因为它们是如此 "无聊"(从技术角度来看)和经过实地测试。

让一个Git仓库成为子模块的唯一原因是,它被放置在另一个父级Git仓库

除此之外,一个Git子模块仍然是一个全功能的仓库:你可以执行所有你在 "正常 "Git工作中已经知道的操作--从修改文件,一直到提交、拉取和推送。在子模块中一切皆有可能。

添加一个子模块

让我们举个经典的例子,比如我们想在我们的项目中添加一个第三方库。在我们获取任何代码之前,创建一个单独的文件夹是有意义的,像这样的东西可以有一个家:

$ mkdir lib
$ cd lib

现在,我们已经准备好将一些第三方代码注入我们的项目--但要以一种有序的方式,使用子模块。比方说,我们需要一个小小的 "时区转换器 "JavaScript库:

$ git submodule add https://github.com/spencermountain/spacetime.git

当我们运行这个命令时,Git开始将仓库克隆到我们的项目中,作为一个子模块:

Cloning into 'carparts-website/lib/spacetime'...
remote: Enumerating objects: 7768, done.
remote: Counting objects: 100% (1066/1066), done.
remote: Compressing objects: 100% (445/445), done.
remote: Total 7768 (delta 615), reused 975 (delta 588), pack-reused 6702
Receiving objects: 100% (7768/7768), 4.02 MiB | 7.78 MiB/s, done.
Resolving deltas: 100% (5159/5159), done.

如果我们看一下我们的工作副本文件夹,我们可以看到库文件实际上已经到达我们的项目中:

Our library files are here, included in a submodule

你可能会问:"那么有什么区别呢?"。毕竟,第三方库的文件在这里,就像我们复制粘贴它们那样。关键的区别在于,它们是包含在自己的 Git 仓库里的如果我们只是下载一些文件,把它们扔到我们的项目中,然后提交它们--就像我们项目中的其他文件一样--它们会成为同一个Git仓库的一部分。然而,这个子模块确保了这些库文件不会 "泄漏 "到我们主项目的仓库里。

让我们看看还发生了什么:在我们主项目的根文件夹中创建了一个新的.gitmodules 文件。下面是它的内容:

[submodule "lib/spacetime"]
  path = lib/spacetime
  url = https://github.com/spencermountain/spacetime.git

这个.gitmodules 文件是 Git 追踪我们项目中子模块的多个地方之一。另一个是.git/config ,现在它的结尾是这样的:

[submodule "lib/spacetime"]
  url = https://github.com/spencermountain/spacetime.git
  active = true

最后,Git还在一个内部的.git/modules 文件夹中保存了每个子模块的.git 仓库的副本。

所有这些都是技术细节,你不需要记住。然而,它可能有助于你理解,Git 子模块的内部维护是相当复杂的。這就是為什麼要帶走一件事:不要**用手去攪亂Git子模塊的配置!**如果你想移动、删除或以其他方式操作一个子模块,请帮你自己一个忙,不要手动尝试。要么使用适当的Git命令,要么使用像 "Tower "这样的Git桌面GUI,它将为你处理这些细节。

Git desktop GUIs like Tower make handling Git submodules easier

让我们看看我们的主项目的状态,现在我们已经添加了子模块:

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
  new file:   .gitmodules
  new file:   lib/spacetime

正如你所看到的,Git把添加一个子模块看作是一个与其他项目一样的变化。因此,我们必须像其他项目一样提交这一变更:

$ git commit -m "Add timezone converter library as a submodule"