当我使用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将签出到旧的提交
TL;DR
您在这里的问题是--remote
的使用。别那样做了。
Long
你在VonC的回答评论中提到:
当我[运行]
git status
[我得到]时modified: app/Services/Notification (new commits) modified: app/Services/Payment (new commits) modified: database/migrations (new commits)
(new commits)
部分的意思是:您的子模块正在积极使用的提交哈希ID(通过其当前签出)与您的索引(建议的下一次提交)所说的应该使用的提交哈希ID不同。
这里有很多行话("子模块"、"gitlinks"、"索引"、"提交哈希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
我们在这里看到的是,对于app/Services/Payment
,您的(主、顶级、"或超级项目")存储库的索引表明,这个特定的子模块应该使用提交72602bc5d9e7cef136043791242dfdcfd979370c
。但实际上使用的是提交a7263787e5515abe18e7cfe76af0f26d9f62ceb4
。我们只添加了一个术语来定义:超级项目。
一些初步定义
让我们从Git存储库的定义开始。存储库的核心是一对数据库。一个是提交和其他内部Git对象的数据库。另一个数据库保存名称——人类可读的名称,因为Git为自己的对象使用的名称是不可理解的。
commit是Git存储在第一个(通常要大得多)数据库中的四种内部对象之一。这些提交被编号为,非常大的数字范围高达2160-1。这些数字以十六进制表示,例如72602bc5d9e7cef136043791242dfdcfd979370c
。(提交是你通常以我们即将描述的方式进行交互的唯一提交,所以我们可以方便地忽略剩下的三个,但它们也都有编号。)
数字看起来是随机的,尽管它们实际上是加密哈希函数的输出,因此完全是非随机的。它们来自散列函数,这就是为什么我们也将它们称为散列ID。但这里真正的问题是,它们似乎完全被打乱了,没有人类会记得它们。我们需要一台电脑。
幸运的是,我们有一台计算机。我们只是让计算机记住这些散列ID,使用分支名称和标记名称之类的东西。每次提交还将散列ID或以前的一些提交存储在自己的内部。在这里,我们真的不需要担心,但这就是分支在Git中真正的工作方式。
因此:
- 存储库是
- 一对数据库,其中一个数据库包含提交
- 其具有散列ID或大的丑陋数字
我们和Git使用第二个名称数据库来查找特定提交的哈希ID,我们使用提交来查找更多提交的更多哈希ID,依此类推
提交是只读的:工作树和索引
现在,了解关于这些提交——实际上是Git的所有内部对象——的一件关键的事情是,它们都是只读。由于哈希技巧,它们必须是:哈希ID是进入内部对象的每一位的函数,并且我们通过哈希ID找到对象,因此哈希ID必须始终匹配。如果我们从数据库中提取的某个对象的哈希ID与我们用来在中找到它的哈希ID不匹配,Git就会判定数据库已损坏1
所以提交是完全只读的。不仅如此,每个提交中的文件——我们之前没有定义,但每个提交都持有每个文件的完整快照——都是一种特殊的仅限Git的格式,经过压缩和消除重复,只有Git才能读取。(实际上,没有什么东西可以重写它们,因为所有内容都是只读的。)
这意味着,为了使用一些提交,我们必须提取该提交。Git将通过以下方式提取提交:
- 读取提交中的压缩文件和Git-ified文件
- 将它们扩展为普通的读/写文件;以及
- 将这些文件写入工作树
这个工作树——另一个术语——是我们实际工作的地方。在这里,我们可以查看、读取甚至写入文件。它们以文件的形式存在,而不是以只读、仅Git数据库条目的形式存在。所以,现在我们可以完成工作了。
工作树还使我们能够进行新的提交,但在这里,Git插入了一个额外的绊脚石。在Git允许我们进行新的提交之前,Git要求我们将任何更新的文件复制回Git。
这一步骤实际上有一定的意义,因为我们在工作树中看到和处理的文件在Git中根本不是。它们可能已经被从Git中复制出来(从提交或其支持对象之一中复制出来),但一旦它们被复制出去,它们就被复制出去了。
Git用三个不同的名字来调用Git让我们重新复制更新文件的地方:索引,这个名字本身毫无意义;暂存区,指的是我们和Git如何使用索引,以及缓存(几乎不再使用,但仍显示为git rm --cached
中的标志)。
索引作为暂存区的角色非常简单。它在合并冲突中扮演着一个扩大的角色,但由于我们在这里并不担心这些,我们只会看看我们和Git如何将其作为一个集结区。
当我们第一次签出提交时——使用git checkout
或git switch
——Git需要将所有压缩和Git化的文件扩展到我们的工作树中。但是Git偷偷地贴了一个";复制";的索引/暂存区域。我把";复制";因为Git的内部文件副本都是经过消除重复的。这就是为什么即使每次提交都存储每个文件,Git存储库也不会变得非常胖:大多数提交都会重复使用大多数文件,在这种情况下,重复使用的文件根本不占用空间,因为它已经被消除了重复。
这些索引";副本":它们是重复,因为有问题的文件是提交中的。所以索引";副本";不占用空间2但进行新的提交的关键是:索引副本正是要进入 下一次提交换言之,该索引包含您提出的下一次提交。现在,在做了一个";"干净";签出一些现有的提交,则索引与提交匹配。但是现在,如果您愿意,您可以修改工作树中的一些文件。修改工作树文件后,需要将其复制回Git的索引。您可以使用 结果是,索引现在包含您提议的下一次提交——就像运行 对所有要更新的文件重复此操作:在工作树中更新它们,然后,迟早,但总是在运行 因此,我们现在知道两件事: 当你运行 1目前我们能做的相当有限。处理损坏最常见的方法是完全丢弃数据库,并从一个好的副本中克隆一个新的数据库,这很好,因为Git是分布式的,每个存储库都有数千个副本;在那里";。当然,如果没有其他副本,它就会停止工作。 2它们需要一点空间来保存文件名、内部blob哈希ID和一堆缓存数据——这就是名称cache的用武之地——通常每个文件的字节数不到100:现在几乎什么都没有。 3如果使用 也就是说, 现在你知道了: 我们即将进入Git的子模块。 Git子模块的最短定义是它是另一个Git存储库。不过,这个定义可能有点太短了。它遗漏了一个关键项,所以让我们再试一次:子模块是: 我们现在知道,必须至少有git add
执行此操作,它是:git add
之前的一样。只是现在,您提议的下一次提交已经更新。git commit
之前,根据需要运行git add
。add
步骤根据您添加的内容更新您提议的下一次提交。(请注意,一个全新的文件也会进入索引,以同样的方式,它只是不必踢出一些现有的消除重复的副本。)git commit
时,Git只需打包当时索引中的任何东西,并将其作为一组Git-ified、只读、永久存储、压缩和消除重复的文件放入新的提交中3git commit -a
,请注意这大致相当于运行:git add -u
git commit
-a
选项真正做的就是插入一个";"更新";提交前样式git add
。Git仍然使用(通过添加更新的)索引构建新的提交。不过,这里有几个技术复杂性。这些都与原子性和Git钩子的操作有关。将它们放在一起意味着,如果使用预提交挂钩,则必须非常聪明地编写这些预提交挂钩和/或避免使用git commit -a
。不过,这里不是详细信息的地方。子模块导致Git存储库爆炸式增长
这就是我们如何定义超级项目:超级工程是一个Git存储库,它有一个子模块。超级项目是监督员。
一个超级项目可以是多个子模块的超级项目。(对你来说就是这样:你至少有三个子模块。所以你至少有四个Git存储库。)
一个充当主管(扮演超级项目角色)的Git存储库本身可以是另一个Git存储池的子模块。在这种情况下;中间的";存储库既是子模块,也是超级项目。我不知道你是否有这些:在你的问题中没有任何证据。
现在,大多数Git存储库的一点是:它们是其他Git存储的克隆。我们主要使用克隆人。因此,假设您拥有某个存储库R0的克隆R1作为您的超级项目。如果您的克隆R1是三个子模块的超级项目,那么这三个Git存储库本身可能是三个以上存储库的克隆。因此,在您的基本问题中,我们突然谈到了这里至少有八个Git存储库!
如果有八个或更多的存储库,事情可能会很快变得非常混乱。不再有存储库、工作树、索引等等。取而代之的是八个储存库、计算机上的四个克隆、1个工作树、四个1 Git索引等等。
我们需要能够独立地讨论每个存储库、索引和工作树,即使它们可能在某种程度上是相互依赖的这意味着我们每个都需要名称。为了在某种程度上简化,我将为您的超级项目git clone
使用名称R,为代表app/Services/Payment
的其中一个存储库使用名称S0;为其中另一个存储库使用名称S1。
这一切是如何运作的
您从某个地方(从某个存储库R0)克隆了您的超级项目存储库
R,但在此之后,我们可以暂时停止考虑它,所以我们只考虑
R本身。您的存储库R有提交,这些提交包含文件等等
您在R中选择了一些提交以检查:
git checkout somebranch
名称somebranch
解析为原始提交哈希IDH
,这是Git从R中提取的提交,用于填充索引和工作树,以便使用R。
到目前为止,还没有其他存储库。但是,有一个名为.gitmodules
的文件来自R中的提交H
。此外,commitH
列出了一些gitlinks。gitlink是一个特殊的条目,它将进入提交,它包含两件事:
- 路径名,在本例中为
app/Services/Payment
,以及 - 一些提交散列ID
S
(在这种情况下为CCD_
这些gitlink进入R中的索引。我们只讨论一个特定的gitlink。
如果您现在运行git submodule update --init
(请注意此处缺少--remote
),则在存储库R上操作的Git命令会注意到索引中的此gitlink。(没有相应的文件,只有gitlink。)
执行这个git submodule update
的超级项目Git命令现在会注意到您还没有克隆任何子模块,并且由于--init
选项,将为您运行一个git clone
命令。此git clone
命令需要一个URL。URL来自.gitmodules
文件。
Git此时克隆的存储库是存储库S0(可能在GitHub上:无论如何都在某个服务器上)。克隆被隐藏起来,4创建一个新的存储库S1。您的Git软件现在在S1中运行git checkout
操作,以便将提交复制到工作树和索引中。
S1的索引隐藏在S1的存储库中,但S1工作树放置在app/Services/Payment
中:您希望从子模块中查看和使用文件的位置。因此,现在普通目录(或者文件夹,如果您喜欢这个术语的话)app/Services/Payment
中充满了普通文件。这些包括S1的工作树。
您的子模块S1现在可以使用了。我们需要考虑三个存储库:R、S0和S1。我们有两个分期区域/索引es:一个与R相关,另一个与S1有关。我们有两个工作树可供使用,一个与R相关,另一个与S1有关。S1的工作树在R工作树内,但R存储库不会使用它。只有S1存储库才会使用它。
4在现代Git中,克隆的.git
目录被填充到.git/modules/
中的R中。在Git的古代版本中,子模块克隆进入子模块路径中的.git
中——在本例中为app/Services/Payment/.git
。
git submodule update --remote
git submodule update
的--remote
标志告诉它,不是服从超级项目gitlink-记住,这是R索引中的一个条目,名称为app/Services/Payment
,当前包含哈希ID72602bc5d9e7cef136043791242dfdcfd979370c
-你的Git软件应该进入子模块S1并运行:
git fetch origin
这会联系到存储库S0。存储库S0有自己的分支和标记名称,有自己的1是早些时候从S0克隆的,但SO可能随时更新。因此,git fetch
步骤会联系到处理S0的Git软件,并从该Git获取So任何新的提交,并将它们放入您的克隆S1中。然后,作为最后一步,S1内的git fetch origin
创建或更新S1中的所有远程跟踪名称,这些名称与S0的分支名称一致。
这将根据S0中的分支名称更新S1的(本地)origin/master
、origin/develop
、origin/feature/tall
等。现在,在S1中,来自S0的所有提交*,并且您知道它们(SO)调用";最新的";例如,在他们的master
上提交。
您的git submodule update --remote
现在所做的是将您的名称origin/master
转换为哈希ID。S1Git从该操作中获得的哈希ID不是72602bc5d9e7cef136043791242dfdcfd979370c
。实际上是a7263787e5515abe18e7cfe76af0f26d9f62ceb4
。
您的超级项目Git现在指示您的S1Git运行:
git checkout --detach a7263787e5515abe18e7cfe76af0f26d9f62ceb4
(git switch
也是如此;无论如何,这一切都是在最新版本的Git内部完成的,尽管旧版本在这里确实运行git checkout
)。
这将从提交a7263787e5515abe18e7cfe76af0f26d9f62ceb4
填充S1索引和工作树。因此,这就是S1中的当前提交。
同时,您的超级项目存储库R仍然需要提交72602bc5d9e7cef136043791242dfdcfd979370c
。这就是您将在R中进行的新提交的索引/暂存区域中的内容。
对此该怎么办
如果希望R开始调用a7263787e5515abe18e7cfe76af0f26d9f62ceb4
,则只需运行:
git add app/Services/Payment
同时在R中工作。这指示RGit在S1Git内运行git rev-parse HEAD
,该Git查找当前签出的提交的哈希ID。然后,该哈希ID进入R索引/暂存区域,以便您在R-中进行的下一次提交将通过该哈希ID调用该提交。
如果您希望S签出提交72602bc5d9e7cef136043791242dfdcfd979370c
,那么您有许多选项:
(cd app/Services/Payment && git checkout --detach 72602bc5d9e7cef136043791242dfdcfd979370c)
例如,会这样做。或者您可以运行git submodule update
。该命令在R中运行,告诉RGit从R-索引读取提交哈希ID,并在每个子模块中运行git checkout
命令,以强制子模块签出返回到所需的提交。
运行git submodule update --init
时,如果添加--remote
,则指示RGit在每个子模块中提取,并从源存储库中的某个分支中查找最新提交(此处的示例中为S0)。选择的分支在R中的不同地方被定义,尽管现在它往往是master
或main
。没有--init
的git submodule update
也是如此。--init
仅表示在需要时执行初始克隆。--remote
部分意味着执行提取并从远程跟踪名称中获取哈希ID。关键部分始终是哈希ID。来自:
- 您的索引,或
- 一些远程跟踪名称
,并控制提交的您的Git指示子模块Git签出。
git status
和git diff
命令在R中运行,仅报告索引(R的索引)和工作树(在这种情况下,S1工作树检查)是否匹配。如果不是,git diff
告诉你区别是什么,而git status
只是说";它们是不同的";。
git submodule update
不应"生成";子模块文件夹内容旁边的任何文件。
git diff
i,父存储库可能会向您显示您提到的内容,如";从"子模块"开始;
如果你在上面运行
git diff
,你会看到一些有趣的东西:$ git diff --cached DbConnector diff --git a/DbConnector b/DbConnector new file mode 160000 index 0000000..c3f01dc --- /dev/null +++ b/DbConnector @@ -0,0 +1 @@ +Subproject commit c3f01dc8862123d317dd46284b05b6892c7b29bc
虽然
DbConnector
是工作目录中的一个子目录,但Git将其视为子模块,当您不在该目录中时,它不会跟踪其内容
相反,Git将其视为来自该存储库的特定提交。