排除子项目提交 Git

5

如何排除 Subproject commit ....?我只是从远程仓库拉取了变更,没有修改子模块。我认为在创建子模块时在主仓库中提交了一些不必要的内容。有任何想法吗?

1)当我创建子模块时,在主仓库中执行 git diff 也显示了这些信息。

submodule-path:
    Subproject commit 5a8162ff9a602deb96956854346988e1ee45672e

我提交了这个

2) 然后有人向子模块进行了提交,所以它有以下日志

2ff89a2bfcaa0 last commit
5a8162ff9a602d  first commit

3) 我使用以下命令更新了子模块:

git submodule update --remote --merge

4) 现在 git status 显示如下:

modified:   submodule-path (new commits)

但我没有改变子模块中的任何内容,只是拉取了最新的远程更改!而我需要这些最新的更改。
git diff 显示:
diff --git a/submodule-path b/submodule-path
index 5a8162f..2ff89a2 160000
--- a/submodule-path
+++ b/submodule-path
@@ -1 +1 @@
-Subproject commit 5a8162ff9a602deb96956854346988e1ee45672e
+Subproject commit 2ff89a2bfcaa014885a70b0da86e997ecd8d0688

“Pull” 的意思是 “运行获取,然后运行合并”。合并可能会改变某些内容(在这种情况下确实如此)。你几乎肯定也想要改变一些东西。你需要知道的是你想要做出哪些更改,以及在哪里和如何进行更改。我们(StackOverflow)可以告诉您如何以各种方式操作子模块,但我们无法告诉您想要进行哪些更改。 - torek
在下面@oktapodia建议使用git submodule update -f --init,但它会重置到第一个提交,而我需要最后一个。 - Ivan Kush
默认的 update 子命令只是将子模块强制更新到超级项目中记录的提交。但在 任何 Git 仓库中,并不一定存在单个“最新”的提交。我会写点东西,但请记住,子模块本质上是复杂的。 - torek
3个回答

3
更新:
这不是一个错误。子模块就是以这种方式工作的。
主仓库不跟踪子模块的文件。它只跟踪子模块的URL和提交ID(特定时间点的子模块状态)。
引自《Pro Git》书中《起步使用子模块》一章的链接:
“尽管 sbmodule DbConnector 是你工作目录下的一个子目录,Git 却将其视为子模块,并在你不在该目录时不跟踪其内容。相反,Git 将其视为来自该存储库的特定提交。”
由于你使用了 git submodule update 命令更新了模块,因此你必须暂存更改(实际上是更新的提交 ID)。如果你不想更新子模块的跟踪,请在开始时不要使用 git submodule update 命令,或者直接放弃更改。
很可能子模块是HEAD分离状态。进入子模块,重置子模块以修复HEAD分离状态。
# Do this in the submodule
git reset --hard origin/master

然后更新子模块到最新的提交。
# run in the project's root, not the submodule's
git submodule update --remote --merge

是的,它已经被分离了,但是不行,仍然看到 modified: submodule-path (new commits) =( 做了什么:1)进入子模块目录,2)git checkout master,3)git reset --hard origin/master,4)在项目根目录中执行 git submodule update --remote --merge - Ivan Kush
@IvanKush 子模块就是这样工作的。主仓库不跟踪子模块的文件,仅跟踪子模块的URL和提交ID(特定时间的状态)。由于您使用了 git submodule update 更新了模块,因此您必须暂存更改(实际上是更新后的提交ID)。 - Simba
@IvanKush 更新了我的回答,解释了子模块的工作原理。 - Simba

3

首先,让我们尝试澄清一些事情。当你自己使用Git时,它是很复杂的。当你添加第二个Git仓库 - 你可以将自己的提交推送到其中一个Git仓库,并从中获取其他人的新提交 - 那就更加复杂了。子模块不过是第三个Git仓库,而这个第三个Git仓库有第四个Git仓库,你或许可以将自己的提交推送到其中一个Git仓库,并从中获取其他人的新提交。因此,我们立刻面临至少四个Git仓库的情况,所有这些仓库都有点独立于彼此。

我们可以尝试画图,但即使是图片也会有点混乱。我们如何区分这四个仓库?Git为其中两个仓库命名:你直接与这两个仓库交互。其中一个是超级项目,这是你运行git diff并看到的地方:

diff --git a/submodule-path b/submodule-path
index 5a8162f..2ff89a2 160000
--- a/submodule-path
+++ b/submodule-path
@@ -1 +1 @@
-Subproject commit 5a8162ff9a602deb96956854346988e1ee45672e
+Subproject commit 2ff89a2bfcaa014885a70b0da86e997ecd8d0688
另一个重要的部分是子模块本身:如果你cd submodule-path并运行各种Git命令,你会发现它是一个普通的Git仓库。唯一不寻常的是它几乎总是在“分离头指针”模式下。
你的超级项目Git可能有一个origin。这是一个仓库,技术上来说,是你可以在这个Git中使用的一个简短的名称,用于引用另一个仓库 - 你可以在超级项目中进行提交并将这些新提交推送到origin。这个超级项目提交确切地包含了什么?我们马上就会看到。
你的子模块Git同样也有一个origin。那是另一个不同的Git仓库:这个不太好画的图中的第四个Git仓库。我不确定你是否想将提交发送到那个第四个仓库。但是,你肯定想从那个仓库获取提交。有多种方法可以做到这一点,包括使用git submodule update --remote,也许还带有其他选项。我更喜欢只是cd submodule-path并直接开始在子模块Git中工作,因为这将问题简化为你已经知道如何操作的事情:根据出现在其origin中的新提交来操作一个本地Git仓库。
假设你只想在子模块中拾取一些新的提交
如果是这种情况,你可以:
cd submodule-path           # begin working in your submodule
git fetch                   # update origin/*
git checkout origin/master  # get a detached HEAD on the desired commit
                            # (this assumes `origin/master` is the
                            # desired commit; it's impossible for me
                            # to know which commit you desire)

现在子模块已经作为其分离的HEAD使用所需的提交。 从这里开始没有任何东西需要git push:在此存储库中驻留的所有提交都是从此子模块的origin Git获得的提交。
(使用git submodule update --remote可能会为您执行cd submodue-path和git fetch和git checkout origin/master,而无需更改自己的工作目录。整个操作在其自己的子shell中运行,以便这些cd操作不会影响您所在的位置。这似乎就是您的git submodule update --remote --merge所做的:没有必要创建新的合并提交,因此它只是切换到由某个分支名称标识的提交上的origin。)
但是,如果您需要在子模块中进行新提交...
在这种情况下,您可能希望强制子模块处于分支上,以便您可以在更正常的工作流程中工作。然后,您可能想要git checkout master,然后执行各种命令。最终,您可能会在子模块存储库中创建一个新提交,您需要将其git push到子模块的origin存储库,以便其他人也能够获取此提交。
您可以让子模块保持在其分支上。子模块的分支与超级项目Git无关:超级项目Git仅关心子模块中检出的提交。(这就是为什么在上面的早期情况下,我们可以轻松切换分离的HEAD。)
现在子模块Git已经处于正确的提交上,您必须创建一个新的超级项目提交。
此时,您可以从子模块回到超级项目。您将在git diff输出中看到与您上面引用的内容完全相同的内容,并且git status将显示:
modified:   submodule-path (new commits)

这并不一定意味着子模块仓库中有任何未在子模块仓库“origin”中的新提交。它只是意味着子模块仓库处于(作为其HEAD,无论是否分离)不是当前超级项目状态所应在的提交上。

问题在于当前超级项目提交在某种程度上是有缺陷的。它曾经是正确的,但现在不再是了,就像如果您在本地编辑了文件“README.md”,当前超级项目提交也将有缺陷一样。这意味着您需要在超级项目中进行一个新的、已更正的提交。超级项目 Git 将根据超级项目仓库索引中的内容创建新提交,因此现在您需要更新索引。

如果您更改了文件“README.md”,那么更新索引的方法如下:

git add README.md

但你改变的不是一个README.md文件,而是子模块哈希ID。因此,你需要在索引中记录新的哈希ID。方法如下:

git add submodule-path

这将通过运行cd submodule-path; git rev-parse HEAD从子模块中获取哈希ID - 显示在git diff中的原始哈希ID,并将该哈希ID插入索引中。现在git diff - 比较索引和工作树 - 将不再显示这些Subproject commit行,但是git diff --cached - 比较当前(超级项目)提交和索引 - 会显示它们。现在git status将说这些“新提交”已准备好提交,而不是尚未暂存以提交。
您可以在此时git add任何其他超级项目文件(如果有需要在索引中更新的文件)。然后:
git commit

在超级项目中,将创建一个新的提交记录,该记录将记录您在子模块路径上运行git add时放入超级项目索引中的哈希ID。

您(本地)已完成更新,但这里有一些需要考虑的事情

请注意,超级项目中的每个提交都记录了子模块的哈希ID。每次使用git checkout切换到不同的超级项目提交时,它不仅会将正确的超级项目文件提取到(超级项目)索引和您的(超级项目)工作树中,还会将记录的子模块哈希ID提取到(超级项目)索引中。默认情况下,它不会cd进入子模块并按其哈希ID检出该特定提交。您可以通过设置或添加--recursivegit checkout来更改此设置;或者您可以只运行git submodule update,这会告诉您的Git依次cd进入每个子模块,并git checkout当前在(超级项目)索引中记录的哈希ID。

在某个时间点,您需要git push在超级项目中创建的新提交到origin(超级项目的来源),以便新提交及其新记录的哈希ID出现在超级项目的origin Git中。您可以随时执行此操作,但是假设您在子模块中创建了新提交,并且尚未在子模块本身中使用git push将这些新提交发送到子模块的origin。在这种情况下,在超级项目中创建的新提交记录了一个提交的哈希ID,该提交仅存在于您本地的子模块存储库中。如果有人运行git fetch到超级项目Git的origin,他们将从您发送的新提交中获取此新哈希ID,但无法在他们的子模块克隆中找到该提交。因此,如果确实进行了新的子模块提交,则通常最好先git push它们,然后再git push新的超级项目提交。

(如果您没有进行任何新的子模块提交,则没有问题。)


1
运行命令git submodule update -f --init应该解决您的问题,这将重置您的子模块到远程HEAD

不行。它会重置到上一个子模块提交 5a8162ff9a60。但是最后一次提交是 2ff89a2bfcaa0。我需要最后一次提交。 - Ivan Kush
你必须从 git 目录而非子模块目录运行该命令。 - oktapodia
是的,我从基础目录运行了它。此命令还会创建独立的子模块分支。 - Ivan Kush
我和原帖作者遇到了同样的问题,这是唯一对我有效的答案!! - cdahms

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接