git 子模块更新 --init --force --remote
git submodule update --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 检出到旧提交
A git submodule update
除了子模块文件夹内容外,不应“生成”任何文件。
A git diff
i,父存储库可能会显示您提到的内容,如“Starting with Submodules”
中所示
If you run git diff
on that, you see something interesting:
$ 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
Although DbConnector
is a subdirectory in your working directory, Git sees it as a submodule and doesn’t track its contents when you’re not in that directory.
Instead, Git sees it as a particular commit from that repository.
TL;DR
您的问题是 --remote
的使用。停止这样做。
长
您提到 on :
When I [run] git status
[I get]
modified: app/Services/Notification (new commits)
modified: app/Services/Payment (new commits)
modified: database/migrations (new commits)
(new commits)
部分表示:您的子模块主动使用(通过其当前签出)的提交哈希 ID 不同于您的 index(建议下一次提交)说 应该使用 。
这里有很多行话(“子模块”、“gitlinks”、“索引”、“提交哈希 ID”),因此需要解压很多。我们稍后会谈到这一点。
请注意,上面 git status
的输出是您在原始问题中引用的 git diff
输出的 more-compact 表示形式:
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
,您的(主要,top-level,“或超级项目”存储库的索引表明这个特定的子模块 应该 使用 commit 72602bc5d9e7cef136043791242dfdcfd979370c
。但是 actually using commit a7263787e5515abe18e7cfe76af0f26d9f62ceb4
instead. 我们刚刚添加了一个术语来定义:superproject.
一些初始定义
让我们从 Git 存储库 的定义开始。存储库的核心是一对数据库。一个是 commits 和其他内部 Git 对象的数据库。另一个数据库保存名称—human-readable 个名称,因为 Git 用于它自己的对象的名称是不可理解的。
commit 是 Git 存储在第一个(通常更大)数据库中的四种内部对象之一。这些提交是 编号的,数量非常大,运行最多可达 2160-1。这些数字以 hexadecimal 表示,例如 72602bc5d9e7cef136043791242dfdcfd979370c
。 (提交是您通常以我们将要描述的方式与之交互的唯一提交,因此我们将方便地忽略其余三个,但它们也都已编号。)
数字 看起来 运行dom,尽管它们实际上是加密哈希函数的输出,因此完全 non-运行国内。它们来自散列函数的事实是我们也称它们为 散列 ID 的原因。但这里真正的重点是它们似乎完全被打乱了,没有 human 会记住它们。我们需要一台电脑。
幸运的是,我们有一台电脑。我们只需让计算机为我们记住这些哈希 ID,使用诸如 b运行ch 名称和标签名称之类的东西。每个提交还在其自身内部存储哈希 ID 或一些先前的提交。我们真的不需要在这里担心这个,但这就是 b运行ches really 在 Git.
中的工作方式
所以:
- 一个存储库是
- 一对数据库,其中一个数据库保存提交
- 具有 哈希 ID 或丑陋的大数字。
我们和 Git 使用名称的第二个数据库来查找特定提交的哈希 ID,我们使用提交来查找更多提交的更多哈希 ID,依此类推。
提交是 read-only:工作树 和 index
现在,要了解关于这些提交——实际上是所有Git的内部对象——的关键是它们都是只读。它们必须是,因为散列技巧:散列 ID 是 进入内部对象 的每一位的函数,我们 找到 对象的哈希 ID,因此哈希 ID 必须始终匹配。如果我们从数据库中提取的某个对象的哈希 ID 与我们用来在数据库中找到它的哈希 ID 不匹配,Git 决定数据库已损坏。1
因此提交完全 read-only。不仅如此,每个提交 中的 文件——我们之前没有定义它,但每个提交都包含每个文件的完整快照——采用特殊的 Git-only 格式,压缩和 de-duplicated,只有 Git 可以阅读。 (从字面上看,没有什么可以 覆盖它们 因为一切都是 read-only。)
这意味着只是为了使用一些提交,我们必须提取那个提交。 Git 将通过以下方式提取提交:
- 正在读取提交中的压缩文件和 Git-ified 文件;
- 将它们扩展成普通的 read/write 文件;和
- 将这些文件写入工作树。
这个工作树——另一个行话——是我们实际工作的地方。在这里,我们可以查看、读取甚至写入文件。它们存在 作为 文件,而不是 read-only、Git-only 数据库条目。所以,现在我们可以完成工作了。
工作树还使我们能够进行 new 提交,但是 hre, Git 插入了一个额外的绊脚石。在 Git 将 允许 我们进行新的提交之前,Git 要求我们 将任何更新的文件复制回 Git.
这一步实际上有一定的意义,因为我们在工作树中看到和处理/使用的文件 不是 在 Git 。它们可能已被 从 Git 中复制(从提交或其支持对象之一中),但一旦它们被复制出来,它们就被复制出来了。
Git 调用 Git 使我们 re-copy 更新文件的三个不同名称:index,作为名称本身没有意义; 暂存区,指的是我们和Git如何使用索引——以及缓存,它几乎不再使用,但仍然显示为 git rm --cached
中的标志。
索引的角色作为临时区域非常简单。它在合并冲突期间发挥了更大的作用,但由于我们在这里并不担心这些,所以我们将看看我们和 Git 如何将其用作临时区域。
当我们第一次检出一个提交时——使用git checkout
或git switch
——Git需要展开所有压缩和Git-ified 文件到我们的工作树中。但是 Git 秘密地将这些文件中的每一个的“副本”粘贴到其索引 / staging-area 中。我在这里把“复制”这个词放在引号里是因为Git的内部文件副本都是de-duplicated。这就是为什么 Git 存储库不会变得非常胖,即使每个提交都存储每个文件:大多数提交 re-use 大多数文件,在这种情况下, re-used 文件根本不需要 space,因为它已经被 de-duplicated 拿走了。
这些索引“副本”也是如此:它们是重复,因为相关文件在提交中。所以索引“副本”没有 space.2 但是制作新的 commit 的关键是:索引副本是下一次提交 .
中的确切内容
换句话说,索引包含您提议的下一次提交。现在,对一些现有的提交进行了“干净”的检查,索引与提交匹配。但是现在您可以根据需要修改工作树中的某些文件。一旦您修改了 工作树 文件,您 需要将其复制回 Git 的索引 。您使用 git add
执行此操作,其中:
- 读取工作树副本;
- 压缩它,否则 Git-ifies 它;
- 检查结果是否重复;和
- 如果 是 副本,则使用原始文件(丢弃临时 Git-ified 副本),否则使用新的 Git-ified 文件,并使用这是为了更新索引。
结果是索引现在包含您建议的下一次提交 — 就像在您 运行 git add
之前 所做的一样。只是现在,您提议的下一次提交 已更新。
你对你打算更新的所有文件重复这个:在工作树中更新它们,然后,迟早,但总是在运行宁git commit
,运行git add
根据需要。 add
步骤根据您添加的任何内容更新您的 提议的下一次提交 。 (请注意,totally-new 文件也进入索引,以同样的方式,只是它不必踢出一些现有的 de-duplicated 副本。)
因此我们现在知道两件事:
- 工作树 包含有用的文件副本。
- 暂存区——或索引——保存建议的下一次提交,你更新工作树后更新。
当您执行 运行 git commit
时,Git 只是打包 当时索引中的任何内容 并将其放入作为 Git-ified、read-only、stored-forever、压缩和 de-duplicated 文件集的新提交。3
1目前我们能做的还比较有限。处理损坏的最常见方法是完全丢弃数据库并从一个好的副本克隆一个新的,这很好用,因为 Git 是分布式的,每个存储库都有数千个副本“在那里”。当然,如果没有其他副本,它会停止工作。
2他们用一些 space 来保存文件名、内部 blob 哈希 ID 和一堆缓存数据——这就是名字 cache 再次出现——通常每个文件不到 100 字节:现在几乎没有。
3如果使用git commit -a
,注意这大致相当于运行ning:
git add -u
git commit
也就是说,所有 -a
选项真正做的是 inse在 提交之前 git add
没有“更新”风格。 Git 仍然从 (updated-by-add) 索引构建新的提交。虽然这里有几个技术复杂性。这些与原子性和 Git 挂钩的操作有关。把它们放在一起意味着如果你使用 pre-commit hooks,你一定很聪明写这些pre-commit钩子,and/or 避免使用 git commit -a
。不过这里不是介绍细节的地方。
子模块导致 Git 存储库的爆炸式增长
现在你知道了:
- 什么是存储库;和
- 索引和工作树如何工作
我们正准备继续 Git 的 子模块 。
Git 子模块的最短定义是它是 另一个 Git 存储库 。不过,这个定义可能有点 too 短。它遗漏了一个关键项,所以让我们再试一次: A submodule is:
- 一个 Git 存储库,其中
- 一些其他Git存储库引用这个Git存储库;和
- 一些其他 Git 存储库 对 这个 Git 存储库进行一些控制。
我们现在知道必须至少 涉及两个Git 存储库,并且一个存储库被置于对另一个存储库的某种监督位置。
这就是我们如何定义术语 超级项目: 超级项目是一个 Git 存储库,它有一个 子模块 。 superproject是监督者/supervisor.
一个超级项目可以是多个子模块的超级项目。 (这就是你的情况:你至少有三个子模块。所以你至少有四个 Git 涉及的存储库。)
A Git 存储库充当主管——扮演超级项目角色——它本身可以是另一个 Git 存储库的子模块。在这种情况下,“中间”存储库是子模块 和 超级项目。我不知道你是否有这些:在你的问题中没有任何证据。
现在,关于大多数 Git 存储库的一件事是:它们是其他 Git 存储库的 克隆 。我们主要与克隆人一起工作。因此,让我们假设您拥有某个存储库 R0 的克隆 R1 作为您的超级项目。如果您的克隆 R1 是三个子模块的超级项目,那么这三个 Git 存储库本身可能是三个 more 存储库的克隆。所以我们突然在你的基本问题中谈论至少八个 Git 存储库!
如果有八个或更多存储库,事情很快就会变得非常混乱。不再有 the 存储库、the 工作树、the 索引等。相反,您的计算机上有 八个 个存储库,四个 个克隆,四个 个工作树,四个 Git索引东西,等等。
我们需要能够独立讨论每个存储库、索引和工作树,即使它们可能有些相互依赖。这意味着我们需要 每个人的名字。为了稍微简化一些事情,我将使用名称 R 作为您的超级项目 git clone
,S0 作为代表的存储库之一app/Services/Payment
,还有 S1 另一个。
这一切是如何运作的
您从某处(从某个存储库R0)克隆了您的超级项目存储库R,但在那之后,我们可以停止考虑它一会儿,所以我们只考虑 R 本身。您的存储库 R 有提交,这些提交包含文件等。
您选择了一些提交 in R 以签出:
git checkout somebranch
名称 somebranch
解析为原始提交哈希 ID H
,这是您的 Git 从 R 中捕获的提交填充索引和工作树,以便您可以 use R.
到目前为止,没有额外的存储库。但是,有一个名为 .gitmodules
的文件来自 R 中的提交 H
。此外,commit H
列出了一些 gitlinks。 gitlink 是一个特殊的条目,它将 进入 提交,它包含两件事:
- 路径名,在本例中为
app/Services/Payment
,
- 一些提交哈希 ID
S
(在本例中为 72602bc5d9e7cef136043791242dfdcfd979370c
)。
这些 gitlinks 进入 R 中的 index。我们只讨论这个特定的 gitlink。
如果你现在 运行 git submodule update --init
(注意这里缺少 --remote
),我们的 Git 命令在存储库 R 上运行,会在索引中注意到这个 gitlink。 (没有相应的 files,只有 gitlink。)
你的超级项目 Git 命令,执行这个 git submodule update
,现在会注意到你还没有克隆任何子模块,并且 - 因为 --init
选项 - 将 运行 给你一个 git clone
命令。此 git clone
命令需要一个 URL。 URL 来自 .gitmodules
文件。
此时Git克隆的存储库是存储库S0(可能在GitHub上:无论如何在某些服务器上)。克隆被隐藏起来,4 创建一个新的存储库 S1。您的 Git 软件现在 运行 在 S1 中执行 git checkout
操作 ,以便将提交复制到工作树和索引。
S1 的 index 隐藏在 S1 的存储库中,但是working tree for S1 放在 app/Services/Payment
: 你想要 files 的地方将从子模块中查看并使用。所以现在普通目录(或文件夹,如果你喜欢这个词)app/Services/Payment
充满了普通文件。这些包括工作树 S1.
您的子模块 S1 现在可以使用了。我们需要考虑三个存储库:R、S0 和 S1。我们有两个暂存区 / index-es:一个与 R 对应,一个与 S1 对应。我们有两种工作树可供使用,一种与 R 一起使用,另一种与 S1 一起使用。 S1 的工作树是 inside R 的工作树,但是 R repository 不会使用它。只有 S1 存储库 使用 它。
4在现代Git中,克隆的.git
目录被塞入[=61中的R =].在 Git 的旧版本中,子模块克隆进入子模块路径中的 .git
——在本例中为 app/Services/Payment/.git
.
git submodule update --remote
git submodule update
的 --remote
标志告诉它,而不是 服从超级项目 gitlink——记住,这是 R 索引,在名称 app/Services/Payment
下,当前持有哈希 ID 72602bc5d9e7cef136043791242dfdcfd979370c
——您的 Git 软件应该进入子模块 S1和 运行:
git fetch origin
这会到达存储库 S0。存储库 S0 有 它自己的 b运行ch 和标签名称,它自己的 提交.存储库 S1 之前是从 S0 克隆的,但是 S0可能随时更新。因此 git fetch
步骤会接触到处理 S0 的 Git 软件,并从 Git 获取 的任何新提交]S0 并将它们放入您的克隆 S1 中。然后,作为最后一步,S1 中的 git fetch origin
创建或更新 [=382] 中的所有 remote-tracking names =]S1 与来自 S0.
的 b运行ch 名称一起使用
这会根据 b运行ch names 如 S0 所示。您现在在 S1 中拥有来自 S0 的所有 commits*,并且您知道哪个提交 他们 (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
).
这会从提交 a7263787e5515abe18e7cfe76af0f26d9f62ceb4
填充您的 S1 索引和工作树。所以这就是 当前提交 在你的 S1.
与此同时,您的超级项目 存储库R 仍然需要提交72602bc5d9e7cef136043791242dfdcfd979370c
。这就是您将在 R.
中进行的新提交的索引 / staging-area 中的内容
如何应对这一切
如果您想要 R开始呼叫a7263787e5515abe18e7cfe76af0f26d9f62ceb4
,您只需要运行:
git add app/Services/Payment
在 R 工作。这会将 R Git 指向 S1 Git 中的 运行 git rev-parse HEAD
,这查找当前 checked-out 提交的哈希 ID。然后,此哈希 ID 进入 R 索引 / staging-area,以便 next 提交您在 in R 会通过该哈希 ID 调用该提交。
如果您希望 S 签出提交 72602bc5d9e7cef136043791242dfdcfd979370c
,您有多种选择:
(cd app/Services/Payment && git checkout --detach 72602bc5d9e7cef136043791242dfdcfd979370c)
例如,会这样做。或者你可以 运行 git submodule update
。此命令 R 中的 运行 告诉 R Git 从 [=382] 读取提交哈希 ID =]R 索引和 运行 git checkout
每个子模块中的命令,以强制子模块检出返回到所需的提交。
当您 运行 git submodule update --init
时,如果您添加 --remote
,您将引导您的 R Git 获取在每个子模块中,从源存储库中的某个 b运行ch 中找到 latest 提交(在我们此处的示例中为 S0)。所选择的 b运行ch 在 R 的不同地方都有定义,尽管现在它往往是 master
或 main
。没有 --init
的 git submodule update
也是如此。 --init
仅表示 在需要时进行初始克隆 。 --remote
部分意味着 从 remote-tracking 名称 中获取并获取哈希 ID。关键部分始终是 哈希 ID。来自:
- 你的索引,或者
- 一些remote-tracking名字
它控制着你的Git哪个提交指示子模块Git检出。
git status
和git diff
命令,运行inR,仅仅报告是否索引(R 的索引)和工作树(在这种情况下,S1 的工作树结帐)match。如果不是,git diff
会告诉你有什么区别,而git status
只会说“它们不同”。
当我使用 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 检出到旧提交
A git submodule update
除了子模块文件夹内容外,不应“生成”任何文件。
A git diff
i,父存储库可能会显示您提到的内容,如“Starting with Submodules”
If you run
git diff
on that, you see something interesting:$ 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
Although
DbConnector
is a subdirectory in your working directory, Git sees it as a submodule and doesn’t track its contents when you’re not in that directory.
Instead, Git sees it as a particular commit from that repository.
TL;DR
您的问题是 --remote
的使用。停止这样做。
长
您提到
When I [run]
git status
[I get]modified: app/Services/Notification (new commits) modified: app/Services/Payment (new commits) modified: database/migrations (new commits)
(new commits)
部分表示:您的子模块主动使用(通过其当前签出)的提交哈希 ID 不同于您的 index(建议下一次提交)说 应该使用 。
这里有很多行话(“子模块”、“gitlinks”、“索引”、“提交哈希 ID”),因此需要解压很多。我们稍后会谈到这一点。
请注意,上面 git status
的输出是您在原始问题中引用的 git diff
输出的 more-compact 表示形式:
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
,您的(主要,top-level,“或超级项目”存储库的索引表明这个特定的子模块 应该 使用 commit 72602bc5d9e7cef136043791242dfdcfd979370c
。但是 actually using commit a7263787e5515abe18e7cfe76af0f26d9f62ceb4
instead. 我们刚刚添加了一个术语来定义:superproject.
一些初始定义
让我们从 Git 存储库 的定义开始。存储库的核心是一对数据库。一个是 commits 和其他内部 Git 对象的数据库。另一个数据库保存名称—human-readable 个名称,因为 Git 用于它自己的对象的名称是不可理解的。
commit 是 Git 存储在第一个(通常更大)数据库中的四种内部对象之一。这些提交是 编号的,数量非常大,运行最多可达 2160-1。这些数字以 hexadecimal 表示,例如 72602bc5d9e7cef136043791242dfdcfd979370c
。 (提交是您通常以我们将要描述的方式与之交互的唯一提交,因此我们将方便地忽略其余三个,但它们也都已编号。)
数字 看起来 运行dom,尽管它们实际上是加密哈希函数的输出,因此完全 non-运行国内。它们来自散列函数的事实是我们也称它们为 散列 ID 的原因。但这里真正的重点是它们似乎完全被打乱了,没有 human 会记住它们。我们需要一台电脑。
幸运的是,我们有一台电脑。我们只需让计算机为我们记住这些哈希 ID,使用诸如 b运行ch 名称和标签名称之类的东西。每个提交还在其自身内部存储哈希 ID 或一些先前的提交。我们真的不需要在这里担心这个,但这就是 b运行ches really 在 Git.
中的工作方式所以:
- 一个存储库是
- 一对数据库,其中一个数据库保存提交
- 具有 哈希 ID 或丑陋的大数字。
我们和 Git 使用名称的第二个数据库来查找特定提交的哈希 ID,我们使用提交来查找更多提交的更多哈希 ID,依此类推。
提交是 read-only:工作树 和 index
现在,要了解关于这些提交——实际上是所有Git的内部对象——的关键是它们都是只读。它们必须是,因为散列技巧:散列 ID 是 进入内部对象 的每一位的函数,我们 找到 对象的哈希 ID,因此哈希 ID 必须始终匹配。如果我们从数据库中提取的某个对象的哈希 ID 与我们用来在数据库中找到它的哈希 ID 不匹配,Git 决定数据库已损坏。1
因此提交完全 read-only。不仅如此,每个提交 中的 文件——我们之前没有定义它,但每个提交都包含每个文件的完整快照——采用特殊的 Git-only 格式,压缩和 de-duplicated,只有 Git 可以阅读。 (从字面上看,没有什么可以 覆盖它们 因为一切都是 read-only。)
这意味着只是为了使用一些提交,我们必须提取那个提交。 Git 将通过以下方式提取提交:
- 正在读取提交中的压缩文件和 Git-ified 文件;
- 将它们扩展成普通的 read/write 文件;和
- 将这些文件写入工作树。
这个工作树——另一个行话——是我们实际工作的地方。在这里,我们可以查看、读取甚至写入文件。它们存在 作为 文件,而不是 read-only、Git-only 数据库条目。所以,现在我们可以完成工作了。
工作树还使我们能够进行 new 提交,但是 hre, Git 插入了一个额外的绊脚石。在 Git 将 允许 我们进行新的提交之前,Git 要求我们 将任何更新的文件复制回 Git.
这一步实际上有一定的意义,因为我们在工作树中看到和处理/使用的文件 不是 在 Git 。它们可能已被 从 Git 中复制(从提交或其支持对象之一中),但一旦它们被复制出来,它们就被复制出来了。
Git 调用 Git 使我们 re-copy 更新文件的三个不同名称:index,作为名称本身没有意义; 暂存区,指的是我们和Git如何使用索引——以及缓存,它几乎不再使用,但仍然显示为 git rm --cached
中的标志。
索引的角色作为临时区域非常简单。它在合并冲突期间发挥了更大的作用,但由于我们在这里并不担心这些,所以我们将看看我们和 Git 如何将其用作临时区域。
当我们第一次检出一个提交时——使用git checkout
或git switch
——Git需要展开所有压缩和Git-ified 文件到我们的工作树中。但是 Git 秘密地将这些文件中的每一个的“副本”粘贴到其索引 / staging-area 中。我在这里把“复制”这个词放在引号里是因为Git的内部文件副本都是de-duplicated。这就是为什么 Git 存储库不会变得非常胖,即使每个提交都存储每个文件:大多数提交 re-use 大多数文件,在这种情况下, re-used 文件根本不需要 space,因为它已经被 de-duplicated 拿走了。
这些索引“副本”也是如此:它们是重复,因为相关文件在提交中。所以索引“副本”没有 space.2 但是制作新的 commit 的关键是:索引副本是下一次提交 .
中的确切内容换句话说,索引包含您提议的下一次提交。现在,对一些现有的提交进行了“干净”的检查,索引与提交匹配。但是现在您可以根据需要修改工作树中的某些文件。一旦您修改了 工作树 文件,您 需要将其复制回 Git 的索引 。您使用 git add
执行此操作,其中:
- 读取工作树副本;
- 压缩它,否则 Git-ifies 它;
- 检查结果是否重复;和
- 如果 是 副本,则使用原始文件(丢弃临时 Git-ified 副本),否则使用新的 Git-ified 文件,并使用这是为了更新索引。
结果是索引现在包含您建议的下一次提交 — 就像在您 运行 git add
之前 所做的一样。只是现在,您提议的下一次提交 已更新。
你对你打算更新的所有文件重复这个:在工作树中更新它们,然后,迟早,但总是在运行宁git commit
,运行git add
根据需要。 add
步骤根据您添加的任何内容更新您的 提议的下一次提交 。 (请注意,totally-new 文件也进入索引,以同样的方式,只是它不必踢出一些现有的 de-duplicated 副本。)
因此我们现在知道两件事:
- 工作树 包含有用的文件副本。
- 暂存区——或索引——保存建议的下一次提交,你更新工作树后更新。
当您执行 运行 git commit
时,Git 只是打包 当时索引中的任何内容 并将其放入作为 Git-ified、read-only、stored-forever、压缩和 de-duplicated 文件集的新提交。3
1目前我们能做的还比较有限。处理损坏的最常见方法是完全丢弃数据库并从一个好的副本克隆一个新的,这很好用,因为 Git 是分布式的,每个存储库都有数千个副本“在那里”。当然,如果没有其他副本,它会停止工作。
2他们用一些 space 来保存文件名、内部 blob 哈希 ID 和一堆缓存数据——这就是名字 cache 再次出现——通常每个文件不到 100 字节:现在几乎没有。
3如果使用git commit -a
,注意这大致相当于运行ning:
git add -u
git commit
也就是说,所有 -a
选项真正做的是 inse在 提交之前 git add
没有“更新”风格。 Git 仍然从 (updated-by-add) 索引构建新的提交。虽然这里有几个技术复杂性。这些与原子性和 Git 挂钩的操作有关。把它们放在一起意味着如果你使用 pre-commit hooks,你一定很聪明写这些pre-commit钩子,and/or 避免使用 git commit -a
。不过这里不是介绍细节的地方。
子模块导致 Git 存储库的爆炸式增长
现在你知道了:
- 什么是存储库;和
- 索引和工作树如何工作
我们正准备继续 Git 的 子模块 。
Git 子模块的最短定义是它是 另一个 Git 存储库 。不过,这个定义可能有点 too 短。它遗漏了一个关键项,所以让我们再试一次: A submodule is:
- 一个 Git 存储库,其中
- 一些其他Git存储库引用这个Git存储库;和
- 一些其他 Git 存储库 对 这个 Git 存储库进行一些控制。
我们现在知道必须至少 涉及两个Git 存储库,并且一个存储库被置于对另一个存储库的某种监督位置。
这就是我们如何定义术语 超级项目: 超级项目是一个 Git 存储库,它有一个 子模块 。 superproject是监督者/supervisor.
一个超级项目可以是多个子模块的超级项目。 (这就是你的情况:你至少有三个子模块。所以你至少有四个 Git 涉及的存储库。)
A Git 存储库充当主管——扮演超级项目角色——它本身可以是另一个 Git 存储库的子模块。在这种情况下,“中间”存储库是子模块 和 超级项目。我不知道你是否有这些:在你的问题中没有任何证据。
现在,关于大多数 Git 存储库的一件事是:它们是其他 Git 存储库的 克隆 。我们主要与克隆人一起工作。因此,让我们假设您拥有某个存储库 R0 的克隆 R1 作为您的超级项目。如果您的克隆 R1 是三个子模块的超级项目,那么这三个 Git 存储库本身可能是三个 more 存储库的克隆。所以我们突然在你的基本问题中谈论至少八个 Git 存储库!
如果有八个或更多存储库,事情很快就会变得非常混乱。不再有 the 存储库、the 工作树、the 索引等。相反,您的计算机上有 八个 个存储库,四个 个克隆,四个 个工作树,四个 Git索引东西,等等。
我们需要能够独立讨论每个存储库、索引和工作树,即使它们可能有些相互依赖。这意味着我们需要 每个人的名字。为了稍微简化一些事情,我将使用名称 R 作为您的超级项目 git clone
,S0 作为代表的存储库之一app/Services/Payment
,还有 S1 另一个。
这一切是如何运作的
您从某处(从某个存储库R0)克隆了您的超级项目存储库R,但在那之后,我们可以停止考虑它一会儿,所以我们只考虑 R 本身。您的存储库 R 有提交,这些提交包含文件等。
您选择了一些提交 in R 以签出:
git checkout somebranch
名称 somebranch
解析为原始提交哈希 ID H
,这是您的 Git 从 R 中捕获的提交填充索引和工作树,以便您可以 use R.
到目前为止,没有额外的存储库。但是,有一个名为 .gitmodules
的文件来自 R 中的提交 H
。此外,commit H
列出了一些 gitlinks。 gitlink 是一个特殊的条目,它将 进入 提交,它包含两件事:
- 路径名,在本例中为
app/Services/Payment
, - 一些提交哈希 ID
S
(在本例中为72602bc5d9e7cef136043791242dfdcfd979370c
)。
这些 gitlinks 进入 R 中的 index。我们只讨论这个特定的 gitlink。
如果你现在 运行 git submodule update --init
(注意这里缺少 --remote
),我们的 Git 命令在存储库 R 上运行,会在索引中注意到这个 gitlink。 (没有相应的 files,只有 gitlink。)
你的超级项目 Git 命令,执行这个 git submodule update
,现在会注意到你还没有克隆任何子模块,并且 - 因为 --init
选项 - 将 运行 给你一个 git clone
命令。此 git clone
命令需要一个 URL。 URL 来自 .gitmodules
文件。
此时Git克隆的存储库是存储库S0(可能在GitHub上:无论如何在某些服务器上)。克隆被隐藏起来,4 创建一个新的存储库 S1。您的 Git 软件现在 运行 在 S1 中执行 git checkout
操作 ,以便将提交复制到工作树和索引。
S1 的 index 隐藏在 S1 的存储库中,但是working tree for S1 放在 app/Services/Payment
: 你想要 files 的地方将从子模块中查看并使用。所以现在普通目录(或文件夹,如果你喜欢这个词)app/Services/Payment
充满了普通文件。这些包括工作树 S1.
您的子模块 S1 现在可以使用了。我们需要考虑三个存储库:R、S0 和 S1。我们有两个暂存区 / index-es:一个与 R 对应,一个与 S1 对应。我们有两种工作树可供使用,一种与 R 一起使用,另一种与 S1 一起使用。 S1 的工作树是 inside R 的工作树,但是 R repository 不会使用它。只有 S1 存储库 使用 它。
4在现代Git中,克隆的.git
目录被塞入[=61中的R =].在 Git 的旧版本中,子模块克隆进入子模块路径中的 .git
——在本例中为 app/Services/Payment/.git
.
git submodule update --remote
git submodule update
的 --remote
标志告诉它,而不是 服从超级项目 gitlink——记住,这是 R 索引,在名称 app/Services/Payment
下,当前持有哈希 ID 72602bc5d9e7cef136043791242dfdcfd979370c
——您的 Git 软件应该进入子模块 S1和 运行:
git fetch origin
这会到达存储库 S0。存储库 S0 有 它自己的 b运行ch 和标签名称,它自己的 提交.存储库 S1 之前是从 S0 克隆的,但是 S0可能随时更新。因此 git fetch
步骤会接触到处理 S0 的 Git 软件,并从 Git 获取 的任何新提交]S0 并将它们放入您的克隆 S1 中。然后,作为最后一步,S1 中的 git fetch origin
创建或更新 [=382] 中的所有 remote-tracking names =]S1 与来自 S0.
这会根据 b运行ch names 如 S0 所示。您现在在 S1 中拥有来自 S0 的所有 commits*,并且您知道哪个提交 他们 (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
).
这会从提交 a7263787e5515abe18e7cfe76af0f26d9f62ceb4
填充您的 S1 索引和工作树。所以这就是 当前提交 在你的 S1.
与此同时,您的超级项目 存储库R 仍然需要提交72602bc5d9e7cef136043791242dfdcfd979370c
。这就是您将在 R.
如何应对这一切
如果您想要 R开始呼叫a7263787e5515abe18e7cfe76af0f26d9f62ceb4
,您只需要运行:
git add app/Services/Payment
在 R 工作。这会将 R Git 指向 S1 Git 中的 运行 git rev-parse HEAD
,这查找当前 checked-out 提交的哈希 ID。然后,此哈希 ID 进入 R 索引 / staging-area,以便 next 提交您在 in R 会通过该哈希 ID 调用该提交。
如果您希望 S 签出提交 72602bc5d9e7cef136043791242dfdcfd979370c
,您有多种选择:
(cd app/Services/Payment && git checkout --detach 72602bc5d9e7cef136043791242dfdcfd979370c)
例如,会这样做。或者你可以 运行 git submodule update
。此命令 R 中的 运行 告诉 R Git 从 [=382] 读取提交哈希 ID =]R 索引和 运行 git checkout
每个子模块中的命令,以强制子模块检出返回到所需的提交。
当您 运行 git submodule update --init
时,如果您添加 --remote
,您将引导您的 R Git 获取在每个子模块中,从源存储库中的某个 b运行ch 中找到 latest 提交(在我们此处的示例中为 S0)。所选择的 b运行ch 在 R 的不同地方都有定义,尽管现在它往往是 master
或 main
。没有 --init
的 git submodule update
也是如此。 --init
仅表示 在需要时进行初始克隆 。 --remote
部分意味着 从 remote-tracking 名称 中获取并获取哈希 ID。关键部分始终是 哈希 ID。来自:
- 你的索引,或者
- 一些remote-tracking名字
它控制着你的Git哪个提交指示子模块Git检出。
git status
和git diff
命令,运行inR,仅仅报告是否索引(R 的索引)和工作树(在这种情况下,S1 的工作树结帐)match。如果不是,git diff
会告诉你有什么区别,而git status
只会说“它们不同”。