git 子模块更新 --init --force --remote

Oma*_*ziz 3 git github gitlab

当我使用它拉取子模块时,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

Run Code Online (Sandbox Code Playgroud)

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

tor*_*rek 6

长话短说

\n

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

\n

长的

\n

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

\n
\n

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

\n
    modified:   app/Services/Notification (new commits)\n    modified:   app/Services/Payment (new commits)\n    modified:   database/migrations (new commits)\n
Run Code Online (Sandbox Code Playgroud)\n
\n

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

\n

这里有很多行话(“子模块”、“gitlinks”、“索引”、“提交哈希 ID”),因此有很多需要解压的内容。我们稍后会讨论这个。

\n

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

\n
\n
diff --git a/app/Services/Payment b/app/Services/Payment\nindex 72602bc..a726378 160000\n--- a/app/Services/Payment\n+++ b/app/Services/Payment\n@@ -1 +1 @@\n-Subproject commit 72602bc5d9e7cef136043791242dfdcfd979370c\n+Subproject commit a7263787e5515abe18e7cfe76af0f26d9f62ceb4\n
Run Code Online (Sandbox Code Playgroud)\n
\n

我们在这里看到的是,对于app/Services/Payment,你的(主、顶级、“或超级项目”存储库的索引表明这个特定的子模块应该使用 commit 72602bc5d9e7cef136043791242dfdcfd979370c。但它实际上是使用commit a7263787e5515abe18e7cfe76af0f26d9f62ceb4。我们刚刚添加了还要定义一个行话术语:superproject

\n

一些初步定义

\n

让我们从Git 存储库的定义开始。存储库的核心是一对数据库。一个是提交和其他内部 Git 对象的数据库。另一个数据库保存名称\xe2\x80\x94人类可读的名称,因为Git为其自己的对象使用的名称是难以理解的。

\n

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

\n

这些数字看起来是随机的,尽管它们实际上是加密哈希函数的输出,因此完全是非随机的。事实上,它们来自哈希函数,这就是为什么我们也将它们称为哈希 ID。但真正的问题是,它们似乎完全被打乱了,没有会记住它们。为此我们需要一台计算机。

\n

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

\n

所以:

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

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

\n

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

\n

现在,了解这些提交\xe2\x80\x94 以及实际上所有 Git\ 的内部对象\xe2\x80\x94 的一个重要事项是它们都是只读的。它们必须是,因为散列技巧:散列 ID 是进入内部对象的每个位的函数,并且我们通过散列 ID找到对象,因此散列 ID 必须始终匹配。如果我们从数据库中提取的某个对象的哈希 ID 与我们用于在数据库中查找它的哈希 ID 不匹配,Git 会判定数据库已损坏。1

\n

所以提交是完全只读的。不仅如此,每个提交\xe2\x80\x94 中的文件我们之前没有定义,但每个提交都保存每个文件 \xe2\x80\x94 的完整快照,并且采用特殊的仅 Git 格式,经过压缩和压缩已去重,只有 Git 可以读取。(实际上没有任何东西可以覆盖它们,因为所有内容都是只读的。)

\n

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

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

这个工作树\xe2\x80\x94(另一个行话)\xe2\x80\x94 是我们实际工作的地方。在这里,我们可以查看、读取、甚至写入文件。它们文件的形式存在,而不是以只读的、仅限 Git 的数据库条目的形式存在。所以,现在我们可以完成工作了。

\n

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

\n

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

\n

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

\n

索引作为暂存区的作用非常简单。它在合并冲突期间发挥了更大的作用,但由于我们在这里并不担心这些,因此我们将只看看我们和 Git 如何将它用作暂存区域。

\n

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

\n

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

\n

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

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

结果是索引现在包含您建议的下一个提交\xe2\x80\x94,就像在运行之前git add一样。只是现在,你提议的下一次提交已经更新了

\n

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

\n

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

\n
    \n
  • 工作保存文件的有用副本。
  • \n
  • 暂存区域\xe2\x80\x94 或索引\xe2\x80\x94保存建议的下一次提交,您在更新工作树后更新该提交。
  • \n
\n

当您运行 时git commit,Git 会简单地打包当时索引中的所有内容,并将其作为一组 Git 化、只读、永久存储、压缩和去重复的文件放入新的提交中。3

\n
\n

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

\n

2它们占用一点空间来保存文件名、内部 blob 哈希 ID 和一堆缓存数据\xe2\x80\x94,这就是名称缓存再次出现的地方\xe2\x80\x94每个文件通常略低于 100 字节:如今几乎没有什么。

\n

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

\n
git add -u\ngit commit\n
Run Code Online (Sandbox Code Playgroud)\n

也就是说,所有选项真正做的就是在提交之前-a插入“更新”样式。Git 仍然从(通过添加更新)索引构建新的提交。但这里存在一些技术复杂性。这些与原子性和 Git 挂钩的操作有关。将它们放在一起意味着如果您确实使用预提交挂钩,则必须非常聪明地编写这些预提交挂钩,和/或避免使用. 不过,这里不是详细介绍的地方。git add git commit -a

\n
\n

子模块导致 Git 存储库爆炸

\n

现在你知道了:

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

我们即将准备好继续讨论 Git 的子模块

\n

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

\n
    \n
  • Git 存储库,其中
  • \n
  • 其他一些 Git 存储库引用此 Git 存储库;和
  • \n
  • 其他一些 Git 存储库对此 Git 存储库实施一些控制
  • \n
\n

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

\n

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

\n

一个超级项目可以是多个子模块的超级项目。(您的情况就是这样:您至少有三个子模块。因此您至少涉及四个 Git 存储库。)

\n

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

\n

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

\n

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

\n

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

\n

这一切是如何运作的

\n

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

\n

R中选择了一些提交来检查:

\n
git checkout somebranch\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
    \n
  • 路径名,在本例中app/Services/Payment为 ,以及
  • \n
  • 一些提交哈希 ID S(在本例中72602bc5d9e7cef136043791242dfdcfd979370c)。
  • \n
\n

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

\n

如果您现在运行git submodule update --init(请注意此处缺少--remote),您在存储库R上运行的 Git 命令将在索引中注意到此 gitlink。(没有相应的文件,只有 gitlink。)

\n

执行此命令的超级项目 Git 命令git submodule update现在会注意到您尚未克隆任何子模块,并且 xe2x80x94 因为选项--initxe2x80x94 将为git clone您运行命令。该git clone命令需要一个 URL。URL 来自文件.gitmodules

\n

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

\n

S1的索引隐藏在S1的存储中,但S1工作树放置在:您希望从子模块中看到和使用文件的位置。所以现在普通目录(或文件夹,如果您喜欢这个术语)充满了普通文件。这些构成了S1工作树app/Services/Paymentapp/Services/Payment

\n

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

\n
\n

4在现代 Git 中,克隆的.git目录被填充到R中的.git/modules/. 在旧版本的 Git 中,子模块克隆进入.git子模块路径的右侧\xe2\x80\x94(在本例中)app/Services/Payment/.git

\n
\n

git submodule update --remote

\n

标志--remote告诉git submodule update它,不要遵守超级项目 gitlink \xe2\x80\x94 请记住,这是R索引中的一个条目,名称为app/Services/Payment,当前保存哈希 ID 72602bc5d9e7cef136043791242dfdcfd979370c\xe2\x80\x94,您的 Git 软件应该进入子模块S1并跑步:

\n
git fetch origin\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

这会根据S0中看到的分支名称更新S1origin/master中的 (本地) 、origin/developorigin/feature/tall等。现在,您在S1中拥有来自S0 的所有提交,并且您知道它们S0 )将哪个提交称为其实例上的“最新”提交。master

\n

git submodule update --remote现在要做的就是把你的名字origin/master变成一个哈希ID您的S1 Git 从该操作中获取的哈希 ID不是72602bc5d9e7cef136043791242dfdcfd979370c。其实是a7263787e5515abe18e7cfe76af0f26d9f62ceb4.

\n

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

\n
git checkout --detach a7263787e5515abe18e7cfe76af0f26d9f62ceb4\n
Run Code Online (Sandbox Code Playgroud)\n

(或与 相同git switch;无论如何,这一切都是在最新版本的 Git 内部完成的,尽管较旧的版本确实git checkout在这里运行)。

\n

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

\n

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

\n

对于这一切该怎么办

\n

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

\n
git add app/Services/Payment\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

如果您希望S72602bc5d9e7cef136043791242dfdcfd979370c出提交,您有多种选择:

\n
(cd app/Services/Payment && git checkout --detach 72602bc5d9e7cef136043791242dfdcfd979370c)\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

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

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

\n

Rgit status中运行的 and命令git diff仅报告索引(R的索引)和工作树(在本例中为S1的工作树结帐)是否匹配。如果不是,则告诉您有什么区别,并只是说“它们是不同的”。 git diffgit status

\n