搜索
首页科技周边IT业界理解和与git中的子模型合作

Understanding and Working with Submodules in Git

现代软件项目大多依赖于其他项目的工作成果。如果别人已经编写了优秀的解决方案,而你却在代码中重新发明轮子,那将是极大的时间浪费。这就是为什么许多项目使用第三方代码,例如库或模块。

Git,全球最流行的版本控制系统,提供了一种优雅而强大的方法来管理这些依赖项。其“子模块”概念允许我们包含和管理第三方库,同时保持它们与我们自己的代码清晰分离。

本文将阐述Git子模块为何如此有用,它们究竟是什么以及它们的工作原理。

关键要点

  • Git子模块是一种强大而直接的方法,用于在项目中管理第三方库,并将它们与主代码库清晰地隔离开来。它们是放置在另一个父Git存储库中的标准Git存储库。
  • 向项目添加子模块涉及创建单独的文件夹,然后使用“git submodule add”命令,后跟所需库的URL。这会将存储库克隆到项目中作为子模块,使其与主项目的存储库分开。
  • 克隆包含Git子模块的项目时,使用“git clone”命令中的“--recurse-submodules”选项会自动初始化和克隆子模块。如果不这样做,克隆后子模块文件夹将为空,需要使用“git submodule update --init --recursive”来填充。
  • 在Git子模块中,检出的是特定版本,而不是分支,从而可以完全控制在主项目中使用哪些确切的代码。更新子模块涉及使用“git submodule update”,后跟子模块名称。

保持代码分离

为了清楚地说明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会将存储库克隆到我们的项目中,作为一个子模块:

<code>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.</code>

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

Understanding and Working with Submodules in Git

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

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

$ mkdir lib
$ cd lib

这个.gitmodules文件是Git跟踪项目中子模块的多个位置之一。另一个是.git/config,现在结尾如下:

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

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

所有这些都是您不必记住的技术细节。但是,了解Git子模块的内部维护相当复杂可能会有所帮助。这就是为什么记住一件事很重要:不要手动修改Git子模块配置!如果您想移动、删除或以其他方式操作子模块,请帮自己一个忙,不要手动尝试这样做。可以使用适当的Git命令或像“Tower”这样的Git桌面GUI,它会为您处理这些细节。

Understanding and Working with Submodules in Git

让我们看看我们添加子模块后主项目的状态:

<code>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.</code>

如您所见,Git将添加子模块视为与其他更改一样的更改。因此,我们必须像其他任何更改一样提交此更改:

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

克隆包含Git子模块的项目

在我们上面的例子中,我们向现有的Git存储库添加了一个新的子模块。但是,“反过来”呢,当您克隆已经包含子模块的存储库时会发生什么?

如果我们在命令行上执行了普通的git clone ,我们将下载主项目——但是我们会发现任何子模块文件夹都是空的!这再次生动地证明了子模块文件是独立的,并且包含在其父存储库中。

在这种情况下,要在克隆其父存储库后填充子模块,您可以简单地执行git submodule update --init --recursive。更好的方法是在第一次调用git clone时直接添加--recurse-submodules选项。

检出版本

在“普通”Git存储库中,我们通常检出分支。通过使用git checkout 或更新的git switch ,我们告诉Git我们当前活动的分支应该是什么。当在这个分支上进行新的提交时,HEAD指针会自动移动到最新的提交。理解这一点很重要——因为Git子模块的工作方式不同!

在子模块中,我们始终检出一个特定的版本——而不是分支!即使您在子模块中执行类似于git checkout main的命令,在后台,也会记录该分支上当前最新的提交——而不是分支本身。

当然,这种行为并非错误。考虑一下:当您包含第三方库时,您希望完全控制在主项目中使用哪些确切的代码。当库的维护者发布新版本时,这很好……但是您不一定希望自动在您的项目中使用这个新版本。因为您不知道这些新更改是否会破坏您的项目!

如果您想找出您的子模块正在使用哪个版本,您可以在主项目中请求此信息:

$ mkdir lib
$ cd lib

这将返回我们lib/spacetime子模块当前检出的版本。它还让我们知道这个版本是一个名为“6.16.3”的标签。在使用Git子模块时,大量使用标签是很常见的。

假设您希望您的子模块使用较旧的版本,该版本标记为“6.14.0”。首先,我们必须更改目录,以便我们的Git命令将在子模块的上下文中执行,而不是我们的主项目。然后,我们可以简单地使用标签名运行git checkout:

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

如果我们现在回到我们的主项目并再次执行git submodule status,我们将看到我们的检出结果:

<code>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.</code>

仔细查看输出:该SHA-1哈希前面的 符号告诉我们子模块的版本与当前存储在父存储库中的版本不同。由于我们刚刚更改了检出的版本,这看起来是正确的。

现在,在我们的主项目中调用git status也会告知我们这一事实:

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

您可以看到Git将移动子模块的指针视为与其他更改一样的更改:如果我们想存储它,我们必须将其提交到存储库:

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

更新Git子模块

在上述步骤中,是我们自己移动了子模块指针:我们是那些选择检出不同版本、提交它并将其推送到我们团队的远程存储库的人。但是,如果我们的同事更改了子模块版本会怎样——也许是因为发布了子模块的有趣的新版本,并且我们的同事决定在我们的项目中使用它(当然,在彻底测试之后……)。

让我们在主项目中执行一个简单的git pull——因为我们可能经常这样做——以从共享的远程存储库获取新的更改:

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

倒数第二行表示子模块中的某些内容已更改。但是让我们仔细看看:

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

我相信您还记得那个小的 号:这意味着子模块指针已移动!要将我们本地检出的版本更新到我们的队友选择的“官方”版本,我们可以运行update命令:

$ git submodule status
   ea703a7d557efd90ccae894db96368d750be93b6 lib/spacetime (6.16.3)

好了!我们的子模块现在已检出到记录在我们主项目存储库中的版本!

使用Git子模块

我们已经介绍了使用Git子模块的基本构建块。其他工作流程非常标准!

例如,检查子模块中的新更改就像在任何其他Git存储库中一样:您在子模块存储库中运行git fetch命令,如果确实要使用更新,之后可能会运行类似于git pull origin main的命令。

更改子模块也可能适合您,特别是如果您自己管理库代码(因为它是内部库,而不是来自第三方)。您可以像使用任何其他Git存储库一样使用子模块:您可以进行更改、提交它们、推送它们等等。

充分利用Git的强大功能

Git在幕后拥有强大的功能。但是,许多高级工具(如Git子模块)并不为人所知。许多开发人员错过了很多强大的功能,这实在令人遗憾!

如果您想深入了解一些其他高级Git技术,我强烈推荐“高级Git工具包”:这是一个(免费的!)短视频合集,它将向您介绍Reflog、交互式变基、Cherry-Picking甚至分支策略等主题。

祝您成为更好的开发者!

关于Git子模块的常见问题

什么是Git子模块? Git子模块是一种将另一个Git存储库作为子目录包含到您自己的Git存储库中的方法。它允许您将单独的存储库作为子项目维护在主项目中。

为什么要使用Git子模块? Git子模块对于将外部存储库合并到您的项目中非常有用,尤其是在您希望将它们的开发历史与主项目分开时。这对于管理依赖项或包含外部库非常有益。

主项目中关于子模块存储了哪些信息? 主项目将子模块的URL和提交哈希存储在父存储库中的特殊条目中。这允许任何克隆主项目的人也克隆引用的子模块。

如何克隆包含子模块的Git存储库? 克隆包含子模块的存储库时,您可以使用git clone命令的--recursive标志自动初始化和克隆子模块。或者,您可以在克隆后使用git submodule update --init。

我可以嵌套子模块吗? 是的,Git支持嵌套子模块,这意味着子模块可以包含它自己的子模块。但是,管理嵌套子模块可能会变得复杂,并且必须确保每个子模块都已正确初始化和更新。

以上是理解和与git中的子模型合作的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
使用AWS ECS和LAMBDA的无服务器图像处理管道使用AWS ECS和LAMBDA的无服务器图像处理管道Apr 18, 2025 am 08:28 AM

该教程通过使用AWS服务来指导您通过构建无服务器图像处理管道。 我们将创建一个部署在ECS Fargate群集上的next.js前端,与API网关,Lambda函数,S3桶和DynamoDB进行交互。 Th

CNCF ARM64飞行员:影响和见解CNCF ARM64飞行员:影响和见解Apr 15, 2025 am 08:27 AM

该试点程序是CNCF(云本机计算基础),安培计算,Equinix金属和驱动的合作,简化了CNCF GitHub项目的ARM64 CI/CD。 该计划解决了安全问题和绩效

使用GO构建网络漏洞扫描仪使用GO构建网络漏洞扫描仪Apr 01, 2025 am 08:27 AM

此基于GO的网络漏洞扫描仪有效地确定了潜在的安全弱点。 它利用了GO的并发功能的速度功能,包括服务检测和漏洞匹配。让我们探索它的能力和道德

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具