简体   繁体   English

为什么git会说“未暂缓提交更改”并指示子模块文件夹?

[英]Why does git say 'Changes not staged for commit' and indicate the submodule folder?

I have a git submodule inside another module, added via git submodule add <...> (command issued from parent repo), so the .gitmodules file is automatically generated inside the parent repo. 我在另一个模块中有一个git子模块,可以通过git submodule add <...> (从父存储库发出的命令)添加,因此.gitmodules文件是在父.gitmodules库中自动生成的。

Suppose I make a change to the submodule (edit: and do not commit those changes) and then navigate back out to the parent and do git add -A and then git status , it says "Changes not staged for commit: submodule dir name ... etc". 假设我对子模块进行了更改(编辑:并且提交这些更改),然后导航回到父模块,并执行git add -Agit status ,它说:“未上演提交更改: 子模块dir name 。 ..等”。

I thought git would read .gitmodules file (which the parent git generated!), realise its a git submodule directory and therefore not mention its unstaged status when I ask the parent for its status? 我以为git会读取.gitmodules文件(由父git生成的文件!),实现了它的git子模块目录,因此当我向父文件询问其状态时不提及其未暂存状态?

What's going on here is that your submodule repository is on a different commit than the hash ID recorded in the superproject. 这里发生的是您的子模块存储库处于与超级项目中记录的哈希ID不同的提交。 Your git status , run in the superproject, is telling you this, without changing it, and your git add -A apparently did not change it either. 您在超级项目中运行的git status告诉您这一点,而没有更改,而git add -A显然也没有更改。

This last part seems wrong. 最后一部分似乎是错误的。 When I do something similar, and then use git add -A , I get: 当我做类似的事情,然后使用git add -A ,我得到:

Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

        modified:   [submodule path]

If I then run two more commands, it goes back, as I expect: 然后,如果我再运行两个命令,它会像我期望的那样返回:

$ git reset
Unstaged changes after reset:
M       [submodule path]
$ git submodule update
Submodule path [path]: checked out '[hash]'
$ git status
On branch ...
nothing to commit, working tree clean

(I suspect that you've made some change(s) in the submodule but never committed them there.) (我怀疑您已经在子模块中进行了一些更改,但从未在此处提交过。)

What's going on, in fine grained detail that will let you diagnose the problem 发生了什么,细粒度的细节将使您诊断问题

We have one Git repository, called the superproject , that is controlling a second repository, called the submodule . 我们有一个名为superproject的 Git存储库,它控制着另一个名为submodule的存储库。 The superproject actually has three separate control knobs, one of which is present in each commit, and is therefore also found in the index (since the index controls what will go into the next commit). 超级项目实际上具有三个单独的控制旋钮,每个提交中都存在一个控制旋钮,因此也可以在索引中找到它(因为索引控制着下一次提交的内容)。

One of these control knobs is the file you mentioned, .gitmodules . 这些控制旋钮之一是您提到的文件.gitmodules It tells the superproject how to clone the submodule if the submodule is not yet git clone d. 如果子模块还不是git clone d,它将告诉超级项目如何克隆子模块。 Once the submodule is cloned, its main job is done. 克隆子模块后,便完成了其主要工作。

The second is your .git/config file. 第二个是您的.git/config文件。 It contains information copied out of the .gitmodules file, which you can update if needed, if the .gitmodules file is not quite right for your own purposes (which might differ from those of whoever's in charge of the .gitmodules file). 它包含从.gitmodules文件中复制的信息,如果.gitmodules文件不完全适合您自己的目的(可能与负责.gitmodules文件的人不同),您可以根据需要进行更新。 Any settings in your .git/config override those in .gitmodules . .git/config任何设置都将覆盖.gitmodules Otherwise these two places to put settings are essentially equivalent. 否则,这两个放置设置在本质上是等效的。

The last is the one causing the issue. 最后一个是导致问题的原因。 For a submodule to become checked out into your work-tree, and hence to be useful to you, the Git that's in control of the superproject spins up a second set of Git commands. 为了使子模块能够入您的工作树并因此对您有用,由超级项目控制的Git会启动第二组Git命令。 In general, you might run: 通常,您可以运行:

git submodule update --init

to get the submodule checked out (though if you use git clone --recursive , Git does this for you). 以检出子模块(尽管如果您使用git clone --recursive ,Git会为您完成此操作)。

At this point, the superproject Git has made an almost-empty directory with the correct path. 至此,超级项目Git已经建立了一个具有正确路径的几乎为空的目录。 (The directory contains a .git file naming the path to the cloned repository, or in the old days or using the old style backwards-compatibility mode, contains the actual .git directory itself.) The superproject Git chdir s into this directory and tells the submodule Git: (该目录包含一个.git文件,该文件为克隆存储库的路径命名,或者在过去或者使用旧样式的向后兼容模式时,都包含实际的.git目录本身。)超级项目Git chdir进入该目录,并告诉子模块Git:

  • run git checkout hash 运行git checkout hash

Once that's happened, the path is full of files extracted from the commit whose ID is hash , which mostly makes the outer Git (the superproject) "done" with the files. 一旦发生这种情况,路径中就会充满从ID为hash的提交中提取的文件,这大多数会使外部Git(超项目)与文件“完成”。 But there is a side effect, because the submodule is itself a full Git repository, with everything that this means. 但是有一个副作用,因为子模块本身就是一个完整的Git存储库,包含了这一切。

In particular, the subproject has its own HEAD . 特别是,子项目具有自己的HEAD This HEAD is now detached and the submodule's repository's current commit is hash , so that this is in the index and work-tree of the submodule, which is of course what we wanted: the work-tree of the submodule is the path in the superproject where all the submodule files go. 现在已经分离了这个HEAD ,并且子模块的存储库的当前提交是hash ,因此它位于子模块的索引和工作树中,这当然是我们想要的:子模块的工作树是超级项目中的路径所有子模块文件所在的位置。

But there's an interesting question to answer: Where did the superproject Git get the hash ID? 但是有一个有趣的问题要回答: 超级项目Git从哪里获得哈希ID? The answer is: it's stored in every snapshot —well, every snapshot that uses the submodule—in the superproject, the same way every snapshot has a full, complete copy of every file. 答案是:它存储在超级项目的每个快照中(好吧,每个使用子模块的快照),每个快照都具有每个文件的完整副本。 To make that happen, the index for the superproject contains a special entry of type gitlink . 为此,超级项目的索引包含类型gitlink的特殊条目。

This gitlink entry in the superproject index tells the superproject which hash ID to give to the submodule Git whenever the superproject tells the submodule Git: check out some particular commit . 当超级项目告诉子模块Git时,超级项目索引中的gitlink条目告诉超级项目哪个哈希ID赋予子模块Git: 检出一些特定的commit

If you, manually, navigate into the submodule, and git checkout a branch name, or any other commit by hash ID, the submodule repository's HEAD changes. 如果您手动导航到子模块,并git checkout出分支名称或任何其他通过哈希ID提交的信息,则子模块存储库的HEAD更改。 It either becomes attached to the branch name, or it points to the other commit, still in detached-HEAD mode. 它要么成为分支名称的附件,要么指向另一个提交,仍处于分离HEAD模式。

At this point, the submodule and the superproject are out of sync . 此时,子模块和超级项目不同步 The superproject Git does not do anything about this yet. 超级项目Git对此不做任何事情。 You are in control, you choose which commit you want. 您可以控制,选择所需的提交。 You can even make new commits and git push them to some upstream. 您甚至可以进行新的提交,然后git push它们git push到上游。 Once you've done all of the committing and git checkout -ing that you want, and have everything arranged correctly, you should climb out of the submodule work-tree back into your superproject. 完成所需的所有提交和git checkout ,并正确安排了所有内容,您应该爬出子模块工作树,回到超级项目。

Now git status and git diff will, by default—there are a ton of control knobs here too—tell you that the superproject is calling for some hash H , but the submodule has some other hash S checked-out. 现在git statusgit diff会在默认情况下,有一吨的控制旋钮的位置太告诉你,上层项目呼吁一些散列H,但子模块具有一些其他的哈希小号签出。 (They may or may not also tell you if the submodule itself needs a commit made, if you set the control knobs for this.) If you wish your next superproject commit to record, in the gitlink for this submodule, this new commit hash S , you run: (如果设置了控制旋钮,它们可能也可能不会告诉您子模块本身是否需要提交。)如果希望您的下一个超级项目提交在此子模块的gitlink中记录此新的提交哈希S, , 你跑:

git add path-to-submodule

(or git add -A should do the same thing, which is why this is puzzling). (或者git add -A 应该做同样的事情,这就是令人费解的原因)。 That will update the gitlink in your index to record hash ID S rather than H , so that the next superproject commit will, on a git submodule update command, tell the submodule Git: check out commit S, as your detached HEAD . 这将更新索引中的gitlink以记录哈希ID S而不是H ,以便下一个超级项目提交将在git submodule update命令上告诉子模块Git: 签出提交S,作为分离的HEAD

Once the index in the superproject matches the HEAD in the actual checked-out submodule, the submodule won't be listed in the changes not staged for commit section. 一旦超级项目中的索引与实际检出的子模块中的HEAD相匹配,该子模块将不会未暂存的提交更改部分中列出。 If the hash in the gitlink in the index does not match the hash in the gitlink in HEAD , git status will list the submodule's path in changes to be committed . 如果索引中gitlink中的哈希与HEAD中gitlink中的哈希不匹配,则git status 列出要提交的更改中子模块的路径。

and therefore not mention its unstaged status when I ask the parent for its status? 因此,当我向父母询问其身份时,没有提及其未分级的状态吗?

It would still report changes in the submodules, unless you are using (with Git 1.7.2 or more ): 除非您正在使用(与Git 1.7.2或更高版本一起使用),否则它仍将报告子模块中的更改:

You can see the original discussion (back in 2010, for Git 1.7.x) here , which lead to that feature: 您可以在此处看到原始讨论(针对Git 1.7.x,是在2010年) ,它导致了该功能:

By the way, I think that route of action would make the resulting git internally consistent in that everything by default will report submodules with untracked paths in its working tree as dirty. 顺便说一句,我认为该操作路线将使生成的git在内部保持一致,因为默认情况下,所有内容均会将其工作树中具有未跟踪路径的子模块报告为脏。

  • In the "Untracked" section of "git status" output, we list an untracked path in the superproject (ie the one in which " git status " was run) to remind the user that the path might be a new file forgotten to be added (unless of course it is ignored). 在“ git status”输出的“ Untracked”部分中,我们列出了超级项目中的未跟踪路径(即运行“ git status ”的路径),以提醒用户该路径可能是一个忘记添加的新文件。 (除非它当然被忽略)。
    But it does not make the working tree dirty. 但这不会使工作树变脏。

  • When you have an untracked path in a submodule: 当子模块中有未跟踪的路径时:

    • the submodule is listed in the "Changed but not updated" section. 该子模块在“已更改但未更新”部分中列出。
      This also makes the working tree of the superproject dirty, even though the working tree of the submodule is not . 即使子模块的工作树不是 ,这也会使超级项目的工作树变脏。

    • " git diff " output at the superproject level shows that the submodule has modifications (ie "-dirty" is shown), but when run inside the submodule, there is no change shown. 在超级项目级别的输出“ git diff ”表明该子模块已修改(即,显示了“ -dirty”),但是在子模块中运行时,未显示任何更改。

I think this is a misdesign at the UI level; 我认为这在UI级别是错误的设计; reporting an untracked and unignored path as potential mistake to remind the user is a good thing, but the current way " status " and " diff " does so does not make much sense to me. 将一条未跟踪且未被忽略的路径报告为提醒用户的潜在错误是一件好事,但是当前的方式“ status ”和“ diff ”这样做对我来说意义不大。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM