首页 > 解决方案 > git 子模块更新 --init --force --remote

问题描述

例如,当我使用它拉子模块时,git submodule update --init --force --remote它会创建包含 git diff 的新文件

diff --git a/app/Services/Payment b/app/Services/Payment
index 72602bc..a726378 160000
--- a/app/Services/Payment
+++ b/app/Services/Payment
@@ -1 +1 @@
-Subproject commit 72602bc5d9e7cef136043791242dfdcfd979370c
+Subproject commit a7263787e5515abe18e7cfe76af0f26d9f62ceb4

我不知道这些文件是什么以及如何摆脱它们以及当我删除它们时将 sobmodule 签出到旧提交

标签: gitgithubgitlab

解决方案


TL;博士

您的问题是使用--remote. 别那样做。

您在对VonC 的回答的评论中提到:

当我 [跑] git status[我得到]

    modified:   app/Services/Notification (new commits)
    modified:   app/Services/Payment (new commits)
    modified:   database/migrations (new commits)

(new commits)部分意味着:您的子模块正在积极使用的提交哈希 ID (通过其当前结帐)与您的索引(建议的下一次提交)说应该使用的提交哈希 ID 不同。

这里有很多行话(“submodules”、“gitlinks”、“index”、“commit hash ID”),因此需要解压很多。我们稍后会谈到这个。

请注意,上面的输出是您在原始问题中引用git status的输出的更紧凑的表示:git diff

diff --git a/app/Services/Payment b/app/Services/Payment
index 72602bc..a726378 160000
--- a/app/Services/Payment
+++ b/app/Services/Payment
@@ -1 +1 @@
-Subproject commit 72602bc5d9e7cef136043791242dfdcfd979370c
+Subproject commit a7263787e5515abe18e7cfe76af0f26d9f62ceb4

我们在这里看到的是 for app/Services/Payment,您的 (main, top-level, "or superproject" 存储库的索引表明这个特定的子模块应该使用 commit 72602bc5d9e7cef136043791242dfdcfd979370c。但实际上它使用的是commit a7263787e5515abe18e7cfe76af0f26d9f62ceb4。我们刚刚添加了一个术语来定义:超级项目

一些初始定义

让我们从Git 存储库的定义开始。存储库的核心是一对数据库。一个是提交和其他内部 Git 对象的数据库。另一个数据库包含名称——人类可读的名称,因为 Git 用于其自身对象的名称是难以理解的。

提交是 Git 存储第一个(通常大得多的)数据库中的四种内部对象之一。这些提交是编号的,数字非常大,最高可达 2 160 -1。这些数字以十六进制表示,例如,72602bc5d9e7cef136043791242dfdcfd979370c。(提交是您通常以我们将要描述的方式与之交互的唯一提交,因此我们将方便地忽略其余三个,但它们也都有编号。)

这些数字看起来是随机的,尽管它们实际上是密码散列函数的输出,因此完全是非随机的。它们来自散列函数这一事实也是我们称它们为散列 ID的原因。但这里真正的重点是它们似乎完全被打乱了,没有会记住它们。为此,我们需要一台计算机。

幸运的是,我们一台电脑。我们只是让计算机为我们记住这些哈希 ID,使用诸如分支名称和标签名称之类的东西。每个提交还在其自身内部存储哈希 ID 或一些先前的提交。在这里我们真的不需要担心这一点,但这就是分支在 Git 中的真正工作方式。

所以:

  • 存储
  • 一对数据库,其中一个数据库保存提交
  • 具有哈希 ID或大而丑陋的数字。

我们和 Git 使用名称的第二个数据库来查找特定提交的哈希 ID,我们使用提交来查找更多提交的哈希 ID,依此类推。

提交是只读的:工作树索引

现在,要了解这些提交(以及所有 Git 的内部对象)的关键它们都是只读的。由于散列技巧,它们必须是:散列 ID 是进入内部对象的每一位的函数,我们通过散列 ID找到对象,因此散列 ID 必须始终匹配。如果我们从数据库中提取的某个对象的哈希 ID 与我们用来在数据库中找到它的哈希 ID 不匹配,Git 会判定数据库已损坏。1

所以提交是完全只读的。不仅如此,每个提交中的文件——我们之前没有定义,但每个提交都包含每个文件的完整快照——是一种特殊的 Git-only 格式,压缩和重复数据删除,只有 Git 可以读取. (从字面上看,没有什么可以覆盖它们,因为一切都是只读的。)

这意味着仅仅为了使用某个提交,我们必须提取该提交。Git 将通过以下方式提取提交:

  • 读取提交中的压缩文件和 Git 化文件;
  • 将它们扩展为普通的读/写文件;和
  • 将这些文件写到工作树中。

这棵工作树——另一种行话——是我们实际工作的地方。在这里,我们可以查看、读取甚至写入文件。它们作为文件存在,而不是作为只读的、仅限 Git 的数据库条目存在。所以,现在我们可以完成工作了。

工作树还使我们能够进行新的提交,但在这里,Git 插入了一个额外的绊脚石。在 Git允许我们进行新的提交之前,Git 要求我们将所有更新的文件复制回 Git

这一步实际上有一定的意义,因为我们在工作树中看到和处理/使用的文件根本不在Git中。它们可能是从 Git 中复制出来的(从提交或其支持对象之一中复制出来),但是一旦它们被复制出来,它们就会被复制出来。

Git 调用 Git 让我们用三个不同的名称重新复制更新文件的地方:index,它作为一个名称本身没有意义;暂存区,它指的是我们和 Git 如何使用索引缓存,它几乎不再使用,但仍然显示git rm --cached为例如标志。

索引作为临时区域的作用非常简单。它在合并冲突期间扮演了一个扩展的角色,但由于我们在这里并不担心这些,所以我们将看看我们和 Git 如何将它用作暂存区域。

当我们第一次用or签出提交时,Git 需要将所有压缩和 Git 化的文件展开到我们的工作树中。但是 Git 偷偷地将这些文件中的每一个的“副本”粘贴到它的索引/暂存区域中。我在这里将“复制”一词放在引号中,因为 Git 的内部文件副本都是重复数据删除的。这就是为什么即使每次提交都存储了每个文件,Git 存储库也不会变得非常庞大的原因:大多数提交会重复使用大多数文件,在这种情况下,重复使用的文件根本不占用空间,因为它已被重复数据删除离开。git checkoutgit switch

这些索引“副本”也是如此:它们是重复的,因为有问题的文件在commit 中。所以索引“副本”不占用空间。2 但是进行新提交的关键是:索引副本正是要进入下一次提交的内容。

换句话说,索引包含您提议的下一次提交。现在,在对某些现有提交进行了“干净”检查后,索引与提交匹配。但是现在您可以根据需要修改工作树中的一些文件。修改工作树文件后,需要将其复制回 Git 的 index。您使用 执行此操作git add,其中:

  • 读取工作树副本;
  • 压缩它,否则 Git 化它;
  • 检查结果是否重复;和
  • 如果它重复的,则使用原始文件(丢弃临时的 Git 化副本),否则使用新的 Git 化文件,并使用它来更新索引。

结果是索引现在包含了您提议的下一次提交——就像它在您运行之前git add所做的一样。只是现在,您提议的下一次提交已经更新

您对要更新的所有文件重复此操作:在工作树中更新它们,然后,迟早,但总是在运行之前git commitgit add根据需要运行。该add步骤会根据您添加的任何内容更新您提议的下一次提交。(请注意,一个全新的文件也会进入索引,以同样的方式,只是它不必踢出一些现有的重复数据删除副本。)

因此,我们现在知道两件事:

  • 工作树包含文件的有用副本。
  • 暂存区(或索引)保存建议的下一次提交,您在更新工作树后更新它。

当您执行 run 时,Git 只是将当时索引中的所有内容git commit打包,并将其作为一组 Git 化、只读、永久存储、压缩和重复数据删除文件放入新的提交中。3


1目前我们能做的非常有限。处理损坏的最常见方法是完全丢弃数据库并从一个好的副本中克隆一个新的,因为 Git 是分布式的,并且每个存储库都有数千个副本“在那里”,所以效果很好。当然,如果没有其他副本,它将停止工作。

2它们需要一些空间来保存文件名、内部 blob 哈希 ID 和一堆缓存数据——这就是名称缓存再次出现的地方——通常每个文件不到 100 字节:几乎没有这些天。

3如果使用git commit -a,请注意这大致相当于运行:

git add -u
git commit

也就是说,所有-a选项实际上只是在提交git add 之前插入“更新”样式。Git 仍然从 (updated-by-add) 索引构建新的提交。不过,这里有几个技术复杂性。这些与原子性和 Git 钩子的操作有关。将它们放在一起意味着如果您确实使用了预提交挂钩,那么您必须非常聪明地编写这些预提交挂钩,并且/或者避免使用git commit -a. 不过,这不是细节的地方。


子模块导致 Git 存储库的爆炸式增长

现在你知道了:

  • 什么是存储库;和
  • 索引和工作树如何工作

我们已经准备好继续讨论 Git 的子模块了。

Git 子模块的最短定义是它是另一个 Git 存储库。不过,这个定义可能有点太短了。它遗漏了一个关键项目,所以让我们再试一次: 一个子模块是:

  • 一个 Git 存储库,其中
  • 其他一些 Git 存储库引用此 Git 存储库;和
  • 其他一些 Git 存储库对这个 Git 存储库进行一些控制。

我们现在知道必须至少涉及两个 Git 存储库,并且一个存储库被置于对另一个存储库的某种监督位置。

这就是我们定义术语超级项目的方式:超级项目是具有子模块的 Git 存储库。超级项目是监督者/监督者。

一个超级项目可以是多个子模块的超级项目。(这就是你的情况:你至少有三个子模块。所以你至少有四个 Git 存储库。)

充当主管(扮演超级项目角色)的 Git 存储库本身可以是另一个 Git 存储库的子模块。在这种情况下,“中间”存储库既是子模块是超级项目。我不知道你是否有这些:在你的问题中没有任何证据。

现在,关于大多数 Git 存储库的一件事是:它们是其他 Git 存储库的克隆。我们主要使用克隆。因此,假设您拥有某个存储库R0的克隆R1作为您的超级项目。如果您的克隆R1是三个子模块的超级项目,那么这三个 Git 存储库本身可能是另外三个存储库的克隆。因此,在您的基本问题中,我们突然在这里谈论了至少八个 Git 存储库!

如果有八个或更多存储库,事情很快就会变得相当混乱。不再存储库、工作树、索引等。相反,有八个存储库、计算机上的四个克隆、四个工作树、四个Git 索引事物,等等。

我们需要能够独立地讨论每个存储库、索引和工作树,即使它们可能有些相互依赖。 这意味着我们需要为每个人命名。为了简化一些事情,我将为您的超级项目使用名称R,将S0git clone用于代表的存储库之一,将app/Services/PaymentS1用于其中的另一个。

这一切是如何运作的

您从某个地方(从某个存储库R0 )克隆了您的超级项目存储库R,但在那之后,我们可以暂时停止考虑它,所以我们只考虑R本身。您的存储库R有提交,这些提交包含文件等。

您在 R中选择了一些提交来签出:

git checkout somebranch

该名称somebranch解析为原始提交哈希 ID H,这是您的 Git 从R中提取出来的提交以填充索引和工作树,以便您可以使用 R

到目前为止,还没有其他存储库。但是,有一个名为的文件来自R.gitmodules的提交。此外,commit列出了一些gitlinks。gitlink 是一个特殊的条目,它将进入提交,它包含两件事:HH

  • 路径名,在这种情况下app/Services/Payment,和
  • 一些提交哈希 ID S(在这种情况下72602bc5d9e7cef136043791242dfdcfd979370c)。

这些 gitlink 进入R中的索引。我们将只讨论这个特定的 gitlink。

如果你现在运行git submodule update --init(注意这里没有),你在存储库R--remote上运行的 Git 命令会在索引中注意到这个 gitlink。(没有对应的文件,只有 gitlink。)

你的超级项目 Git 命令,执行 this git submodule update,现在会注意到你还没有克隆任何子模块,并且——因为这个--init选项——将为git clone你运行一个命令。此git clone命令需要一个 URL。URL 来自.gitmodules文件。

此时Git 克隆的存储库是存储库S0(可能在 GitHub 上:无论如何在某些服务器上)。克隆被隐藏起来,4创建一个新的存储库S1。您的 Git 软件现在S1git checkout中运行一个操作,以便将提交复制到工作树和索引中。

S1索引隐藏在 S1 的存储库中,S1 的工作放置在: 您希望从子模块中看到和使用的文件的地方。因此,现在普通目录(或文件夹,如果您更喜欢该术语)充满了普通文件。这些包括S1工作树app/Services/Paymentapp/Services/Payment

您的子模块S1现在可以使用了。我们需要考虑三个存储库:RS0S1。我们有两个暂存区/索引-es:一个与R一起使用,一个与S1一起使用。我们有两棵工作树可供使用,一棵与R一起使用,另一棵与S1一起使用。S1的工作树位于R的工作树,但R存储库不会使用它。只有S1存储库会使用它。


4在现代 Git 中,克隆的.git目录被填充到R中的.git/modules/. 在古老版本的 Git 中,子模块克隆进入.git子模块路径的右侧——在本例中为app/Services/Payment/.git.


git submodule update --remote

--remote标志git submodule update告诉它不要服从超级项目 gitlink ——记住,这是R索引中的一个条目,名称为app/Services/Payment,当前持有哈希72602bc5d9e7cef136043791242dfdcfd979370cID——你的 Git 软件应该进入子模块S1并运行:

git fetch origin

这会到达存储库S0。存储库S0自己的分支和标签名称,以及自己的提交。存储库S1之前是从S0克隆的 ,但S0可能随时更新。因此,该步骤涉及处理S0的 Git 软件,并从该 Git 获取S0的任何新提交并将它们放入您的克隆S1中。然后,作为最后一步,在S1中创建或更新S1中与分支一起使用的所有远程跟踪名称git fetchgit fetch origin来自S0的名称。

这会根据S0中看到的分支名称更新S1中的 (local) origin/masterorigin/developorigin/feature/tall等等。现在,您在S1中拥有来自S0的所有提交* ,并且您知道他们S0)将哪个提交称为“最新”提交。master

git submodule update --remote现在所做的就是把你的名字origin/master变成一个哈希 ID您的S1 Git 从此操作中获得的哈希 ID不是72602bc5d9e7cef136043791242dfdcfd979370c. 它实际上是a7263787e5515abe18e7cfe76af0f26d9f62ceb4

您的超级项目Git 现在指示您的S1 Git 运行:

git checkout --detach a7263787e5515abe18e7cfe76af0f26d9f62ceb4

(或与git switch; 相同,在任何情况下,这一切都是在最新版本的 Git 内部完成的,尽管旧版本实际上在git checkout这里运行)。

这会从 commit 填充您的S1索引和工作树a7263787e5515abe18e7cfe76af0f26d9f62ceb4。所以这就是你S1中的当前提交

同时,您的超级项目存储库R仍然需要 commit 72602bc5d9e7cef136043791242dfdcfd979370c。这就是您将在R中进行的新提交的索引/暂存区域中的内容。

这一切该怎么办

如果您希望 R开始调用a7263787e5515abe18e7cfe76af0f26d9f62ceb4,您只需运行:

git add app/Services/Payment

R工作时。这会指示R Git 在S1git rev-parse HEAD Git内部运行,从而找到当前签出提交的哈希 ID。然后,此哈希 ID 进入R索引/暂存区域,以便您R中进行的下一次提交将通过该哈希 ID 调用该提交。

如果您希望S72602bc5d9e7cef136043791242dfdcfd979370c改为检查提交,您有多种选择:

(cd app/Services/Payment && git checkout --detach 72602bc5d9e7cef136043791242dfdcfd979370c)

例如,会这样做。或者你可以运行git submodule update. 这个在R中运行的命令告诉R Git 从R索引中读取提交哈希 ID 并git checkout在每个子模块中运行命令,以强制子模块检出到所需的提交。

当您运行时git submodule update --init,如果您添加--remote,您将指示您的R Git 获取每个子模块并从源存储库中的某个分支(此处示例中的S0 )中查找最新提交。选择的分支在R中的不同地方定义,尽管它往往是或现在。没有也是如此。唯一的方法是在需要时进行初始克隆。这部分意味着从远程跟踪名称中获取并获取哈希 ID。关键部分始终是哈希 ID。这来自:mastermaingit submodule update--init--init--remote

  • 您的索引,或
  • 一些远程跟踪名称

并且控制哪个提交你的 Git 指示子模块 Git 签出。

git statusandgit diff命令 R中运行,仅报告索引(R的索引)和工作树(在这种情况下S1的工作树检出)是否匹配。如果不是,git diff请告诉您有什么区别,并git status只是说“它们不同”。


推荐阅读