深入理解 Git submodules

简介: 深入理解 Git submodules

DRY 原则是程序世界的基本原则之一,我们每个人在工作中都不可避免的会复用别人的代码,有可能是某个开源项目,也有可能是公司里其他团队提供的模块。Git 是最流行的现代化代码版本控制工具,为了支持模块的复用,Git 引入了 submodule 的概念,通过这篇文章,你会理解什么是 git submodule 以及在项目中如何应用。原文:Understanding and Working with Submodules in Git[1]


大部分现代软件项目都需要依赖于他人的工作,当别人已经实现了一个很好的解决方案,就不需要再浪费时间再去实现一遍。因此很多项目都会以库或模块的形式使用第三方代码。


Git 是世界上最流行的版本控制系统,它提供了一种优雅、健壮的方式管理这些依赖关系。Git 的“submodule”概念允许我们引用和管理第三方库,同时保持与我们自己的代码清晰的隔离。


在本文中,您将了解到为什么 Git 中的 submodule 能起作用、它的底层逻辑是什么以及它是如何工作的。


保持代码独立


为了弄清楚为什么 Git 的 submodule 很有价值,让我们先看一个没有 submodule 的例子。当我们需要引用第三方代码(比如开源库)的时候,最简单的办法是从 GitHub 下载代码,然后将其保存到自己的项目中。虽然这么做可以很快解决问题,但这么做非常丑陋,原因如下:


  • 通过强制将第三方代码复制到项目中,我们可以有效的将多个项目混合到一个项目中,而我们自己的项目和别人的项目(库)之间的界限开始变得模糊。
  • 每当我们需要更新库代码时(也许是因为它的维护者提供了一个很棒的新特性或修复了一个讨厌的 bug),我们都必须再次下载、复制和粘贴,这很快就变成了一个乏味的过程。


软件开发中“将不同的东西分开”的一般规则是有原因的。当然,在自己的项目中管理第三方代码也需要这样,而 Git submodule 的概念正是针对这些情况而设计的。


当然,submodule 并不是解决这类问题的唯一方法,我们还可以使用许多现代语言和框架提供的各种“包管理器”系统,选择哪种依赖管理方法并没有绝对的对错之分。


不过,Git 的 submodule 体系架构有以下几个优点:

  • Submodule 提供一致、可靠的接口,和语言或框架无关。当我们使用多种技术时,每种技术可能都有自己的包管理器和自己的一组规则和命令,而 Submodule 总是可以以相同的方式工作。
  • 不是每段代码都可以通过包管理器复用,有时候我们只想在两个项目之间共享自己的代码,在这种情况下,submodule 可以提供最简单的工作流。


Git Submodule 到底是什么


Git 中的 submodule 实际上只是标准的 Git repository。并没有什么花哨的创新,就只是我们现在都很熟悉的 Git repository 而已。这也是 submodule 的一项优势:它们十分健壮、方便直接,因为从技术角度来说,没有任何新东西在里面,并且经过了大量现场测试的考验。


使 Git repository 成为 submodule 的唯一原因是它被放在了另一个父 Git repository 中。


除此之外,Git submodule 仍然是一个功能完整的 repository:我们可以执行所有常规的 Git 操作——从修改文件,一直到 commit、pull 和 push,在 submodule 中都可以实现。


添加一个 Submodule


让我们举一个典型的例子,假设我们想要向项目中添加一个第三方库,在我们获取任何代码之前,需要先创建一个单独的文件夹,作为第三方库存储的路径:


$ mkdir lib
$ cd lib


现在,我们准备将一些第三方代码以 submodule 的方式注入到项目中,下面假设我们需要一个小小的“时区转换器”JavaScript 库:


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



当我们运行这个命令时,Git 开始将 repository 作为 submodule 克隆到我们的项目中:

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.


看一下工作区文件夹,我们可以看到库文件实际上已经加到项目里了。


image.png


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


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


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


这个.gitmodules文件是 Git 用来跟踪项目中的 submodule 的几个配置之一,另一个是.git/config,它的结尾被添加了下面的配置:


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


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


你不需要记住所有这些技术细节。可以看到,Git submodule 的内部维护是相当复杂的,因此请记住:千万不要手工修改 Git 子模块的配置!如果你想移动、删除或以其他方式操作子模块,请不要手动尝试。要么使用适当的 Git 命令,要么使用像“Tower”[2]这样的 Git 桌面 GUI,它们会处理这些细节。


image.png


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


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


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

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


克隆一个含有 submodule 的项目


在上面的示例中,我们向现有的 Git repository 添加了一个新的 submodule。但是,反过来,当我们需要克隆一个已经包含 submodule 的仓库时,又会怎么样呢?


如果我们执行普通的git clone ,将会下载主项目,但任何 submodule 文件夹都是空的!这再次生动的证明了 submodle 文件是独立的,不包含在它们的父仓库中。


在这种情况下,要在克隆了父仓库之后填充 submodule,可以简单地执行git submodule update --init --recursive。不过更好的方法是在调用git clone时直接添加--recurse-submodules选项。


使用特定版本


在普通的 Git 仓库中,我们通过使用git checkout 或者在 Git 2.23 引入的git switch ,告诉 git 当前活动的分支是什么。当在这个分支上进行新的提交时,HEAD 指针会自动移动到最近的提交。理解这一点很重要——因为 Git submodule 的工作方式不太一样!


在 submodule 中,我们总是签出一个特定的版本——而不是一个分支!即使在 submodule 中执行git checkout main这样的命令,在后台,也只会把该分支上当前最新的提交记录下来,而不会改变分支本身的内容。


这并不是系统错误,而是有意设计的。考虑一下:当我们引用第三方库时,希望完全控制在主项目中使用的确切代码。当库的维护者发布一个新版本(这当然很好),我们不一定希望这个新版本立马被应用到项目中,因为我们还不知道这些新的更改是否会破坏我们的项目!


如果想知道项目中的子模块使用的是什么版本,可以在主项目中查看以下信息:


$ git submodule status  
ea703a7d557efd90ccae894db96368d750be93b6 lib/spacetime (6.16.3)


上面显示了lib/spacetime子模块当前签出的版本,它还告诉我们这个版本是基于一个名为“6.16.3”的 tag。在 Git 中处理 submodule 时,经常会使用 tag。


要是我们希望 submodule 使用 tag 为“6.14.0”的旧版本。首先,我们必须更改目录,以便在子模块的上下文中执行 Git 命令。然后,我们可以基于 tag 执行 git checkout:

$ cd lib/spacetime/
$ git checkout 6.14.0
Previous HEAD position was ea703a7 Merge pull request #301 from spencermountain/dev
HEAD is now at 7f78d50 Merge pull request #268 from spencermountain/dev


如果我们回到主项目,再次执行git submodule status,我们会看到:


$ cd ../..
$ git submodule status
+7f78d50156ae1205aa50675ddede81a61a45fade lib/spacetime (6.14.0)


仔细看一下输出:SHA-1 哈希值前面的小+符号告诉我们,submodule 的版本与当前存储在父仓库中的版本不同。由于我们刚刚更改了已签出的版本,因此这是正常的。


在主项目中调用git status也会告诉我们这个事实:


$ git status
On branch master
Changes not staged for commit: 
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)  
modified:   lib/spacetime (new commits)


可以看到,Git 认为移动 submodule 的指针和其他变化一样:如果我们保存这个改动,就必须提交到仓库里:


$ git commit -m "Changed checked out revision in submodule"
$ git push


更新 Git Submodule


在上面的步骤中,是我们移动了 submodule 指针:我们选择签出一个不同的修订,提交它,并将它推送到团队的远程仓库中。但如果我们的一个同事更改了 submodule 的修订——可能是因为子模块发布了一个有趣的新版本,而我们决定在项目中使用它(当然是在彻底测试之后……)。


我们在主项目中做一个简单的git pull——我们可能会经常这么做——从共享远程仓库中获得新的更改:


$ git pull
From https://github.com/gntr/git-crash-course 
d86f6e0..055333e  main       -> origin/main
Updating d86f6e0..055333e
Fast-forward  
lib/spacetime | 2 +-  
1 file changed, 1 insertion(+), 1 deletion(-)



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


$ git submodule status
+7f78d50156ae1205aa50675ddede81a61a45fade lib/spacetime (6.14.0)


我相信你还记得那个小+号:它表示 submodule 指针被移动了!要将本地签出的版本更新为我们的团队成员选择的“官方”版本,可以运行update命令:


$ git submodule update lib/spacetime 
Submodule path 'lib/spacetime': checked out '5e3d70a88180879ae0222b6929551c41c3e5309e'


好了!我们签出了主项目仓库中记录的 submodule 版本!


在工作中使用 Git Submodule


我们已经介绍了使用 Git submodule 的基本概念,其他工作流程是相当标准的。


例如,检查子模块中新的更改的工作方式与其他任何 Git repository 类似:在 submodule repository 中运行git fetch命令,如果你确实想要使用这些更新,可能会接着执行类似git pull origin main的命令。


如果是自己管理的内部代码库,那也可能需要在子模块中进行更改。可以像处理任何其他 Git 仓库一样处理 submodule:可以进行更改、提交更改、推送更改等等。


使用 Git 的全部功能


在表面简单的命令下面,Git 提供了很强大的功能。但是它的许多高级工具——比如 Git submodule——并不为人所知。这么多开发人员错过了很多强大的东西,这真是太遗憾了!


如果你想深入了解其他一些先进的 Git 技术,强烈推荐一个免费短视频合集:“Advanced Git Kit”[3],你可以学到 Reflog、Interactive Rebase、Cherry-Picking,甚至分支策略等主题。


祝你成为一个更好的开发者!



References:

[1] https://www.sitepoint.com/git-submodules-introduction/

[2] https://www.git-tower.com/?utm_source=sitepoint&utm_medium=guestpost&utm_campaign=git-submodules

[3] https://www.git-tower.com/learn/git/advanced-git-kit?utm_source=sitepoint&utm_medium=guestpost&utm_campaign=git-submodules


目录
相关文章
|
3天前
|
人工智能 自然语言处理 Shell
深度评测 | 仅用3分钟,百炼调用满血版 Deepseek-r1 API,百万Token免费用,简直不要太爽。
仅用3分钟,百炼调用满血版Deepseek-r1 API,享受百万免费Token。阿里云提供零门槛、快速部署的解决方案,支持云控制台和Cloud Shell两种方式,操作简便。Deepseek-r1满血版在推理能力上表现出色,尤其擅长数学、代码和自然语言处理任务,使用过程中无卡顿,体验丝滑。结合Chatbox工具,用户可轻松掌控模型,提升工作效率。阿里云大模型服务平台百炼不仅速度快,还确保数据安全,值得信赖。
157467 24
深度评测 | 仅用3分钟,百炼调用满血版 Deepseek-r1 API,百万Token免费用,简直不要太爽。
|
5天前
|
人工智能 API 网络安全
用DeepSeek,就在阿里云!四种方式助您快速使用 DeepSeek-R1 满血版!更有内部实战指导!
DeepSeek自发布以来,凭借卓越的技术性能和开源策略迅速吸引了全球关注。DeepSeek-R1作为系列中的佼佼者,在多个基准测试中超越现有顶尖模型,展现了强大的推理能力。然而,由于其爆火及受到黑客攻击,官网使用受限,影响用户体验。为解决这一问题,阿里云提供了多种解决方案。
17027 37
|
13天前
|
机器学习/深度学习 人工智能 自然语言处理
PAI Model Gallery 支持云上一键部署 DeepSeek-V3、DeepSeek-R1 系列模型
DeepSeek 系列模型以其卓越性能在全球范围内备受瞩目,多次评测中表现优异,性能接近甚至超越国际顶尖闭源模型(如OpenAI的GPT-4、Claude-3.5-Sonnet等)。企业用户和开发者可使用 PAI 平台一键部署 DeepSeek 系列模型,实现 DeepSeek 系列模型与现有业务的高效融合。
|
5天前
|
并行计算 PyTorch 算法框架/工具
本地部署DeepSeek模型
要在本地部署DeepSeek模型,需准备Linux(推荐Ubuntu 20.04+)或兼容的Windows/macOS环境,配备NVIDIA GPU(建议RTX 3060+)。安装Python 3.8+、PyTorch/TensorFlow等依赖,并通过官方渠道下载模型文件。配置模型后,编写推理脚本进行测试,可选使用FastAPI服务化部署或Docker容器化。注意资源监控和许可协议。
1311 8
|
13天前
|
人工智能 搜索推荐 Docker
手把手教你使用 Ollama 和 LobeChat 快速本地部署 DeepSeek R1 模型,创建个性化 AI 助手
DeepSeek R1 + LobeChat + Ollama:快速本地部署模型,创建个性化 AI 助手
3416 117
手把手教你使用 Ollama 和 LobeChat 快速本地部署 DeepSeek R1 模型,创建个性化 AI 助手
|
8天前
|
人工智能 自然语言处理 API
DeepSeek全尺寸模型上线阿里云百炼!
阿里云百炼平台近日上线了DeepSeek-V3、DeepSeek-R1及其蒸馏版本等六款全尺寸AI模型,参数量达671B,提供高达100万免费tokens。这些模型在数学、代码、自然语言推理等任务上表现出色,支持灵活调用和经济高效的解决方案,助力开发者和企业加速创新与数字化转型。示例代码展示了如何通过API使用DeepSeek-R1模型进行推理,用户可轻松获取思考过程和最终答案。
|
5天前
|
人工智能 自然语言处理 程序员
如何在通义灵码里用上DeepSeek-V3 和 DeepSeek-R1 满血版671B模型?
除了 AI 程序员的重磅上线外,近期通义灵码能力再升级全新上线模型选择功能,目前已经支持 Qwen2.5、DeepSeek-V3 和 R1系列模型,用户可以在 VSCode 和 JetBrains 里搜索并下载最新通义灵码插件,在输入框里选择模型,即可轻松切换模型。
934 14
|
12天前
|
API 开发工具 Python
阿里云PAI部署DeepSeek及调用
本文介绍如何在阿里云PAI EAS上部署DeepSeek模型,涵盖7B模型的部署、SDK和API调用。7B模型只需一张A10显卡,部署时间约10分钟。文章详细展示了模型信息查看、在线调试及通过OpenAI SDK和Python Requests进行调用的步骤,并附有测试结果和参考文档链接。
1938 9
阿里云PAI部署DeepSeek及调用
|
9天前
|
人工智能 数据可视化 Linux
【保姆级教程】3步搞定DeepSeek本地部署
DeepSeek在2025年春节期间突然爆火出圈。在目前DeepSeek的网站中,极不稳定,总是服务器繁忙,这时候本地部署就可以有效规避问题。本文以最浅显易懂的方式带读者一起完成DeepSeek-r1大模型的本地部署。
|
12天前
|
缓存 自然语言处理 安全
快速调用 Deepseek API!【超详细教程】
Deepseek 强大的功能,在本教程中,将指导您如何获取 DeepSeek API 密钥,并演示如何使用该密钥调用 DeepSeek API 以进行调试。

热门文章

最新文章

相关实验场景

更多