如何正确配置git子模块

时间:2019-05-31 15:53:32

标签: git

我正在尝试将存储库添加为另一个git模块中的子模块。 添加子模块后,我尝试克隆父项目:

git clone https://...
cd <parent_path>/<submodule_path>
git submodule init
git submodule update

现在,如果我git status在子模块中,则HEAD已分离:

cd <submodule_path>
git status
HEAD detached at a4709b3
nothing to commit, working tree clean

阅读此answer之后,我尝试签出到子模块的master

git checkout master
git status

On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

但是现在,如果我在父目录上git status,则表明子模块中有新的提交(肯定不是这种情况)

cd <parent_dir_path>
git status

On branch test_submodule
Your branch is up to date with 'origin/test_submodule'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   submodule_name (new commits)

no changes added to commit (use "git add" and/or "git commit -a")

有人可以阐明这一点吗?

编辑: 这是cd <submodule_path> && git show master的输出:

commit 42309e0e2f48aba11902633173053e2423d4ba62 (HEAD -> master, origin/master, origin/HEAD)
Merge: a4709b3 1c1ae85
Author: Abc
Date:   Fri May 31 16:27:52 2019 +0100

    Merge pull request #6 in test/testing_submodule from test_integration_with_repos to master

    * commit '1c1ae8515716d9c2d5135e86dc9c024c81e4320b':
      test

2 个答案:

答案 0 :(得分:2)

您的超级项目记住子模块必须位于提交a4709b3处。但是子模块已更新,现在其master指向42309e0。您现在应该做什么取决于您要在超级项目中使用什么代码(提交什么)。最简单的解决方案是检出存储的提交:

cd <submodule_path>
git checkout a4709b3

子模块将处于分离的HEAD状态。不用担心。

另一种可能性是更新子模块:

cd <submodule_path>
git checkout master # reconcile detached HEAD
git pull origin master

,然后更新超级项目:

cd <parent_dir_path>
git add <submodule_path>
git commit -m "Update submodule"

答案 1 :(得分:0)

TL; DR

git status比较HEAD提交(当前提交)和索引。区别在于准备提交。然后,它比较索引和工作树。不管有什么不同,都不上演提交。在这种情况下,Git将索引gitlink与子模块中的实际提交哈希ID进行比较。当它说:

modified:   submodule_name (new commits)

这仅表示索引中的gitlink与子模块中的HEAD提交哈希不匹配。这并不意味着子模块中有 个新提交,也不意味着那里有 个新提交;这只是意味着现有索引gitlink哈希ID与现有子模块签出不匹配。

围绕子模块总是有很多困惑。您链接到的问题Why is my GIT Submodule HEAD detached from master?的答案并没有真正成为问题的核心,我认为可以通过以下对话进行总结:

  • 人员::嗨,Git,我想将此另一个Git存储库用作我的Git存储库的子目录。

  • GIT:好,完成。

  • 人员:太酷了。 [后来:]哦,嘿,为什么当我创建一个新克隆时,我的子模块处于分离的HEAD状态?

  • GIT:就是这样。

  • 人员::但我希望它位于分支master上。

  • GIT:确定。

  • 人员:它仍然是独立的!

  • GIT:是的。

  • 人员::我想在分支上使用它!

  • GIT:是的,不。抱歉。

简而言之,这就是它的工作方式。现在,子模块是一个Git存储库,因此您可以将其添加到分支中。但是 superproject Git会立即从分支中撤回它,因为这就是子模块的工作方式。要理解为什么是这种情况,还需要花点时间。

为什么子模块被分离

首先,让我们再次注意,子模块 Git存储库。 使成为子模块的唯一原因是其他一些Git存储库不时地对其进行控制。我们将另一个Git存储库称为 superproject 。超级项目正在执行控制命令,子模块正在遵循它们。除此之外,它们是两个独立 Git存储库:如果子模块具有masterdevelop以及其他分支,则它们独立于超级项目的masterdevelop以及其他任何内容,子模块和超级项目根本不必具有相同的分支名称集。

还请注意,子模块可以 成为另一个子模块的超级项目。自从现在说“子”子模块或“子”超级项目模棱两可以来,这种情况特别令人困惑。现在有两个超级项目,两个子模块和三个 Git存储库,中间的Git存储库都是(顶部Git的)子模块,超级项目(位于底部)。

设计做出了一个巨大的假设,这是一个完全安全的假设,但可能会令人讨厌。该假设是:子模块是从您无法控制的存储库中克隆的。该其他存储库可能非常频繁地更新,或几乎从未更新过,但是分支会作为子模块的origin/*远程跟踪名称复制到您的克隆中,以不必控制的方式进行更改。第一次克隆该子模块存储库时,git clone 创建一个名为master的新分支,如果他们这么说,则创建其他名称,而您可以通过检查该名字来获得的提交位于该分支的 控制权,而不是您的。

由于这个原因,超级项目本身不需要(而且几乎从未使用过)子模块的任何分支名称。相反,超级项目记录有关 不能更改的子模块存储库的信息。它无法更改的事实意味着,无论谁控制您的克隆源来对子模块进行操作,他们都不会破坏您对子模块的依赖性。 >

(当然不是100%正确。例如,他们可以完全删除源存储库。或者可以丢弃您依赖的提交或标记,但总的来说,人们不会删除存储库。丢弃提交是稀有,并且丢弃已发布的标签及其相应的提交尤其罕见。)

您的超级项目记录的无法更改的是超级项目将告诉子模块git checkout hash的提交的原始哈希ID 。记录的信息将进入您的超级项目中的每次提交! (好吧,每次使用子模块的提交。)

运行git checkout hash

时出现分离的HEAD

如果您已经使用Git足够长的时间,那么您对此很熟悉。通过其哈希ID来检查所有历史记录提交,然后Git进入分离的HEAD模式。

超级项目通过其不可更改的哈希ID来检查历史提交。因此,子模块最终以分离的HEAD模式运行。这都是方法和原因。超级项目记录哈希ID,并命令子模块:检查其中一个,现在您处于分离的HEAD模式。

这对于只读的历史提交非常有用。超级项目中的 每个提交都会记录该超级项目提交的正确子模块哈希ID。签出该提交时,您告诉Git同步您的子模块,然后正确的子模块提交,以便您可以构建和使用您的项目。

这妨碍了新工作

首先,我们都需要就一个定义达成共识:现有的提交与新工作无关。提交是只读的,始终冻结。您不能更改任何现有提交的任何内容。 (这就是子模块的提交的哈希ID对超级项目有用的原因:它被冻结在时间上,并且可能永远永久存在。)每个提交都包含所有文件的快照,以及一些元数据,例如创建者,时间和原因-日志消息。

这样冻结了提交,但我们认为,新工作是一件可取的事情。因此,任何一个Git存储库(除了--bare之外)都提供了一个您可以执行此操作的地方。那个地方是工作树(或工作树或任何数量的相似拼写)。 Git从提交中将压缩的,只读的,仅Git格式的文件复制到工作树中,以它们的日常形式保存,因此您可以查看和使用文件。

引用子模块的提交通过Git称为 gitlink 的方式进行。 gitlink实际上是mode 160000的提交文件(普通文件是mode 100644mode 100755),其中文件的“内容”只是超级项目应命令的哈希ID。子模块Git到git checkout

因此,提交中的子模块条目gitlink告诉git checkout您正在充当超级项目。当您到达工作树中的这个位置时,而不是仅在此处提取一个文件,子模块应该作为单独的HEAD进入此提交。如果您使用git checkout --recurse-submodules,Git会做到这一点。如果您使用git checkout --no-recurse-submodules,则Git会坚持这样做-它会留下子模块,而子模块毕竟是一个单独的Git存储库。

现在,Git使 new 提交的不是工作树中的内容,而是 index 中的内容。索引包含每个文件的副本...并且当提交具有gitlink时,索引包含该gitlink的副本。 这是索引中的gitlink条目,它决定您进行下一次提交的内容。因此,下一个 superproject 提交将使用超级项目索引中的内容。

有时您希望子模块在另一个提交上。由于子模块是一个Git存储库,因此您可以直接进入该子模块并git checkout喜欢。如果使用分支名称,则现在将附加子模块的HEAD。就超级项目Git而言,这无关紧要:对超级项目重要的是实际的哈希ID。如果子模块的git rev-parse HEAD仍产生与超级项目索引的gitlink中相同的哈希ID,则所有内容仍然匹配。如果它产生其他哈希值,则由您自己解决。 由于您想要另一次提交,因此现在应该更新超级项目索引的gitlink。

  • 人员:但是,嘿Git:我告诉您要记住超级项目中该特定子模块的分支名称。 Git,您如何命令控制子模块的Git git checkout分支 name

可以做到这一点。但是这里的假设是,您已让Git从不受控制且不git push的上游存储库克隆子模块Git。因此,这不是足够,而Git提供的是命令:

git submodule update --remote

在这种情况下,超级项目Git将:

  • 输入子模块
  • 命令子模块Git运行git fetch
  • 由于该origin/master,等待git fetch或它在子模块中的更新
  • 根据上游(您无法控制,但您只是从中获取)来找到子模块origin/master(或其他任何东西)的新哈希ID

,然后再次将子模块Git git checkout 那个哈希ID ...作为独立的HEAD!

如果您确实要控制子模块,并想在其中进行新的提交,您需要cd 插入子模块,而只需{{1} }所需的任何分支名称,然后在此处进行工作。毕竟,该子模块是常规的旧Git存储库。您可以执行所需的任何工作,然后运行git checkout将所有更新的工作树文件复制到索引中-git add将使用索引中的内容-然后运行git commit进行创建新的提交。

然后,完成所有这些操作后,您可以立即将提交推送到上游,也可以等待并git commit回到超级项目。现在,无论哪种方式,您都可以执行超级项目中所需的任何工作,并cd修改任何文件和子模块的名称。您不仅要更新超级项目索引中的文件,还需要更新超级项目中的gitlink。现在,您已经完成了所有这些操作,您可以在超级项目中运行git add,以进行新提交,以存储更新的文件的gitlink。

现在您有了新的超级项目提交,可以在某个地方git commit进行新的超级项目提交。 如果您已经在子模块中使用了git push,那么这就是您需要做的。但是,如果没有,则应该首先git push子模块。原因很明显,一旦您考虑一下:新的超级项目提交说:进入该子模块时,请阅读此gitlink并提取提交git push (或它的任何哈希ID)。对于其他人,他们需要a987654... git fetch已被git push编辑的上游子模块,以便获取a987654...提交到其子模块Git!

请注意,这与git submodule update --remote动作匹配:他们将转到其子模块的上游,git fetch更新后的分支,然后git checkout进入相应的origin/branch哈希ID,这种情况a987654...,作为其子模块的独立HEAD。

这不是有史以来最流畅的过程,但是它就像Git本身所做的那样简单。 git submodule update还可以做其他几件事,但都是从这种思路开始的:子模块存储库是从其他地方克隆的,主要是其他地方提供了新功能。提交子模块。