【问题标题】:Why is my Git Submodule HEAD detached from master?为什么我的 Git 子模块 HEAD 与主模块分离?
【发布时间】:2013-09-17 05:03:07
【问题描述】:

我正在使用 Git 子模块。从服务器中提取更改后,我的子模块头多次与主分支分离。

为什么会这样?

我必须一直这样做:

git branch
git checkout master

如何确保我的子模块始终指向主分支?

【问题讨论】:

  • 你读过这个答案了吗? stackoverflow.com/questions/1777854/…
  • @bitoiu 我查看了 subtree 和 Google Repo。我还没有完美的解决方案:(
  • 我在 gitsubmodules 方面的经验,在 CI 环境中很糟糕,也许其他人有更好的经验。
  • @JohnnyZ 谢谢。我知道子模块指向提交而不是树的头部。但为什么脱离分支。如果你有一个分支,默认情况下不应该附加到它上面
  • 不要因为您听说子模块不好就立即关闭子模块。如果你想要持续集成,它们是一个糟糕的解决方案,但如果你想从外部项目嵌入代码并且你明确地管理所有拉取,它们是一个近乎完美的解决方案。如果您要与不受组织控制的非分叉模块集成,这通常是最佳实践。问题在于,在它们根本不能很好地工作的各种其他情况下,它们是一个诱人的解决方案。最好的建议是阅读它们的工作原理并评估您的方案。

标签: git git-submodules


【解决方案1】:

在这里查看我的答案: Git submodules: Specify a branch/tag

如果需要,您可以手动将“branch = master”行添加到您的 .gitmodules 文件中。阅读链接以了解我的意思。

编辑: 要跟踪分支中的现有子模块项目,请按照此处的 VonC 说明进行操作:

Git submodules: Specify a branch/tag

【讨论】:

  • 答案应该是内联的; IIRC 链接到答案是 Stack Overflow 失礼。
  • @TonyTopper 即使只是链接到另一个 SO 答案? IIRC 只有外部链接不受欢迎,因为这些链接可能会消失,然后链接就失效了,答案是,嗯,没用。然而,SO 答案没有这样的危险,它们永远不会消失,除非 SO 消失(并且无论发生什么都可以恢复)。他也回答了这个问题,因为branch = master" line into your .gitmodule 实际上是完整的答案,为我解决了这个问题。
【解决方案2】:

编辑:

请参阅@Simba Answer 以获得有效解决方案

submodule.<name>.update 是您要更改的内容,请参阅docs - 默认 checkout
submodule.<name>.branch 指定要跟踪的远程分支 - 默认 master


旧答案:

我个人讨厌这里直接指向外部链接的答案,这些链接可能会随着时间的推移而停止工作,并检查我的答案here(除非问题是重复的) - 指向确实涵盖行间主题的问题其他主题,但总体等于:“我不回答,请阅读文档。”

回到这个问题:为什么会这样?

你描述的情况

从服务器拉取更改后,我的子模块头多次与主分支分离。

当一个人不经常使用 submodules 或刚开始使用 submodules 时,这是一种常见的情况。我相信我的说法是正确的,我们都在某个时候我们的 子模块 的 HEAD 被分离了。

  • 原因:您的子模块未跟踪正确的分支(默认主模块)。
    解决方案:确保您的子模块正在跟踪正确的分支
$ cd <submodule-path>
# if the master branch already exists locally:
# (From git docs - branch)
# -u <upstream>
# --set-upstream-to=<upstream>
#    Set up <branchname>'s tracking information so <upstream>
#    is considered <branchname>'s upstream branch.
#    If no <branchname> is specified, then it defaults to the current branch.
$ git branch -u <origin>/<branch> <branch>
# else:
$ git checkout -b <branch> --track <origin>/<branch>
  • 原因:您的父仓库未配置为跟踪子模块分支。
    解决方案:通过使用以下两个命令添加新的子模块,使您的子模块跟踪其远程分支。
    • 首先你告诉 git 跟踪你的远程&lt;branch&gt;
    • 你告诉 git 执行 rebase 或 merge 而不是 checkout
    • 你告诉 git 从远程更新你的子模块。
    $ git submodule add -b <branch> <repository> [<submodule-path>]
    $ git config -f .gitmodules submodule.<submodule-path>.update rebase
    $ git submodule update --remote
  • 如果您还没有像这样添加现有的子模块,您可以轻松解决这个问题:
    • 首先,您要确保您的子模块已签出您要跟踪的分支。
    $ cd <submodule-path>
    $ git checkout <branch>
    $ cd <parent-repo-path>
    # <submodule-path> is here path releative to parent repo root
    # without starting path separator
    $ git config -f .gitmodules submodule.<submodule-path>.branch <branch>
    $ git config -f .gitmodules submodule.<submodule-path>.update <rebase|merge>

在常见情况下,您现在已经修复了 DETACHED HEAD,因为它与上述配置问题之一有关。

修复 .update = checkout 时的 DETACHED HEAD

$ cd <submodule-path> # and make modification to your submodule
$ git add .
$ git commit -m"Your modification" # Let's say you forgot to push it to remote.
$ cd <parent-repo-path>
$ git status # you will get
Your branch is up-to-date with '<origin>/<branch>'.
Changes not staged for commit:
    modified:   path/to/submodule (new commits)
# As normally you would commit new commit hash to your parent repo
$ git add -A
$ git commit -m"Updated submodule"
$ git push <origin> <branch>.
$ git status
Your branch is up-to-date with '<origin>/<branch>'.
nothing to commit, working directory clean
# If you now update your submodule
$ git submodule update --remote
Submodule path 'path/to/submodule': checked out 'commit-hash'
$ git status # will show again that (submodule has new commits)
$ cd <submodule-path>
$ git status
HEAD detached at <hash>
# as you see you are DETACHED and you are lucky if you found out now
# since at this point you just asked git to update your submodule
# from remote master which is 1 commit behind your local branch
# since you did not push you submodule chage commit to remote. 
# Here you can fix it simply by. (in submodules path)
$ git checkout <branch>
$ git push <origin>/<branch>
# which will fix the states for both submodule and parent since 
# you told already parent repo which is the submodules commit hash 
# to track so you don't see it anymore as untracked.

但是,如果您已经在本地对子模块进行了一些更改并已提交,将这些更改推送到远程,那么当您执行“git checkout”时,Git 会通知您:

$ git checkout <branch>
Warning: you are leaving 1 commit behind, not connected to any of your branches:
If you want to keep it by creating a new branch, this may be a good time to do so with:

创建临时分支的推荐选项可能很好,然后您可以合并这些分支等。但是在这种情况下,我个人只会使用git cherry-pick &lt;hash&gt;

$ git cherry-pick <hash> # hash which git showed you related to DETACHED HEAD
# if you get 'error: could not apply...' run mergetool and fix conflicts
$ git mergetool
$ git status # since your modifications are staged just remove untracked junk files
$ rm -rf <untracked junk file(s)>
$ git commit # without arguments
# which should open for you commit message from DETACHED HEAD
# just save it or modify the message.
$ git push <origin> <branch>
$ cd <parent-repo-path>
$ git add -A # or just the unstaged submodule
$ git commit -m"Updated <submodule>"
$ git push <origin> <branch>

虽然还有更多的情况可以让你的子模块进入 DETACHED HEAD 状态,但我希望你现在能更多地了解如何调试你的特殊情况。

【讨论】:

  • HEAD 分离是git submodule update --remote 的默认行为。请看一下辛巴的回答,我觉得应该是正确的答案。
  • 你的指令太复杂了。请简化。
【解决方案3】:

让子模块签出分支的另一种方法是转到根文件夹中的.gitmodules 文件,并在模块配置中添加字段branch,如下所示:

branch = &lt;branch-name-you-want-module-to-checkout&gt;

【讨论】:

  • 对我来说这不起作用。我已经正确设置了branch = my_wanted_branch。但是运行 git submodule update --remote 它仍然检查为分离头。
  • 这样做,然后 cd sudmodule&git co thebranche & cd..,然后 git submodule update --remote 就可以了!
  • 只有在以子模块递归方式克隆超级项目或初始化子模块时,'.gitmodules'不是处于活动使用状态(正在读取)吗?换句话说,您自己的存储库更新了文件,其中并不总是从放置到“.gitmodules”的子模块配置更新中受益。据我了解,'.gitmodules' 是在克隆 repo 时创建的配置模板。
【解决方案4】:

我厌倦了它总是分离,所以我只需使用 shell 脚本为我的所有模块构建它。我假设所有子模块都在master:这是脚本:

#!/bin/bash
echo "Good Day Friend, building all submodules while checking out from MASTER branch."

git submodule update 
git submodule foreach git checkout master 
git submodule foreach git pull origin master 

从你的父模块执行它

【讨论】:

  • git submodule foreach git pull origin master -- 这就是我要找的......荣誉
  • 简洁明了!谢谢!
【解决方案5】:

来自git submodule --helpHEAD 分离是git submodule update --remote 的默认行为。这与子模块中跟踪哪个分支无关。

如果你只想解决问题,直接跳到第二部分。

原因

我们需要了解什么是子模块。

子模块是一种将另一个项目包含到您当前项目中的方法。这并不是真正将这些文件添加到主项目的提交历史记录中,而是通过引用子模块的快照(提交)。

Quote from Starting with Submodules section in book Pro Git

虽然 sbmodule DbConnector 是您工作目录中的一个子目录,但 Git 将其视为一个子模块,并且当您不在该目录中时,它不会跟踪其内容。相反,Git 将其视为来自该存储库的特定提交

回购的每次提交都是当时代码的快照/状态。此时子模块的状态也必须是确定性的。你不能在这个提交中说,我包括另一个 repo 的主(或另一个)分支。您必须通过提交 id 指定子模块的状态

包含另一个 repos 作为子模块基本上是

git clone uri://another-repo path/to/submodule
cd path/to/submodule
git checkout <commit-id>

# git submodule system will add the reference commit id but not the files

当任何人将你的 repo 与子模块一起使用时,它会克隆子模块和checkout 指定的提交。

并检查提交结果 HEAD 分离。 Why did my Git repo enter a detached HEAD state?

解决方案

如果您希望子模块自动与远程分支合并,请使用--merge--rebase

man git-submodule

--合并

此选项仅对更新命令有效。将超级项目中记录的提交合并到子模块的当前分支中。如果给出此选项,子模块的 HEAD 将不会被分离

--rebase

将当前分支重新定位到超级项目中记录的提交。如果给出此选项,子模块的 HEAD 将不会被分离

如果您的子模块已经分离,请在使用以下两种解决方案之前修复分离状态。

cd path/to/submodule
# Assuming you're tracking the 'master' in the submodule
git checkout master

解决方案 1:在命令行中使用选项

# cd back to project root
git submodule update --remote --merge
# or
git submodule update --remote --rebase

推荐别名:

git config alias.supdate 'submodule update --remote --merge'

# do submodule update with
git supdate

解决方案 2:在配置文件中添加选项

另一种解决方案是通过将submodule.$name.update 设置为mergerebase 来更改gitmodule 文件中的子模块更新行为。 这基本上意味着您可以在不明确传递--merge--rebase 的情况下执行git submodule update --remote,但会自动从配置文件中读取。

这是一个关于如何在.gitmodule中配置子模块更新的默认更新行为的示例。

[submodule "bash/plugins/dircolors-solarized"]
    path = bash/plugins/dircolors-solarized
    url = https://github.com/seebi/dircolors-solarized.git
    update = merge # <-- this is what you need to add

或者通过命令行配置,

# replace $name with a real submodule name
git config -f .gitmodules submodule.$name.update merge

其他

.gitmodule 中添加branch 选项根本与子模块的分离行为无关。 mkungla 的旧答案不正确或已过时。

让我们明确一点,无需指定要跟踪的分支origin/master 是要跟踪的默认分支。

--远程

不要使用超级项目记录的 SHA-1 来更新子模块,而是使用子模块的远程跟踪分支的状态。使用的远程是分支的远程(branch.&lt;name&gt;.remote),默认为origin。使用的远程分支默认为master

参考文献

【讨论】:

  • 我使用git submodule update --remote --merge,它会拉下处于分离状态的子模块。还尝试了--rebase,结果相同。
  • @JoeStrout 如果您的子模块已经分离,请在使用上述命令进行更新之前修复分离状态。 cd 进入子模块,使用git checkout master 将子模块签出到特定分支。
  • 或者 - 如果这对于多个(递归)子模块来说太麻烦了 - 只需执行 git submodule foreach --recursive git checkout master
  • 我只理解部分“git 是如何工作的”描述。 TBH 我对理解 git 的工作原理并不感兴趣,我只是想使用它。现在我知道我可以用git submodule foreach --recursive git checkout master 修复分离的子模块。但是我怎样才能防止 git 总是分离它们呢?为每个子模块设置配置选项不是一个选项!
  • 当我克隆一个 repo 并执行 git submodule update --init --recursive 时,头部被分离。这个答案无助于解决它。我尝试进入子模块文件夹并执行 git checkout master 并退出子文件夹和 git status 并且 git 没有记录任何更新,因此无法使用您提供的详细信息更新父仓库。因此,任何克隆父 repo 并执行 init 的用户仍将面临 head detached 问题。这个答案没有解决它,或者我遗漏了一些东西。请指教,谢谢
【解决方案6】:

正如其他人所说,发生这种情况的原因是父 repo 仅包含对子模块中特定提交(的 SHA1)的引用——它对分支一无所知。它应该是这样工作的:提交时的分支可能已经向前(或向后)移动,如果父 repo 引用了该分支,那么它很容易在发生这种情况时中断。

但是,特别是如果您在父 repo 和子模块中都积极开发,detached HEAD 状态可能会令人困惑并存在潜在危险。如果您在子模块处于detached HEAD 状态时进行提交,这些将变得悬空,您很容易丢失您的工作。 (悬空提交通常可以使用git reflog 来挽救,但最好一开始就避免它们。)

如果你像我一样,那么大多数时候如果子模块中有一个分支指向被签出的提交,你宁愿签出那个分支而不是在相同的提交。 您可以通过将以下别名添加到您的 gitconfig 文件来做到这一点:

[alias]
    submodule-checkout-branch = "!f() { git submodule -q foreach 'branch=$(git branch --no-column --format=\"%(refname:short)\" --points-at `git rev-parse HEAD` | grep -v \"HEAD detached\" | head -1); if [[ ! -z $branch && -z `git symbolic-ref --short -q HEAD` ]]; then git checkout -q \"$branch\"; fi'; }; f"

现在,在完成git submodule update 之后,您只需要调用git submodule-checkout-branch,并且在提交时签出的任何具有指向它的分支的子模块都将签出该分支。如果你不经常有多个本地分支都指向同一个提交,那么这通常会做你想要的;如果不是,那么至少它会确保你所做的任何提交都进入一个实际的分支而不是悬空。

此外,如果您已将 git 设置为在结帐时自动更新子模块(使用git config --global submodule.recurse true,请参阅this answer),您可以创建一个自动调用此别名的结帐后挂钩:

$ cat .git/hooks/post-checkout 
#!/bin/sh
git submodule-checkout-branch

那么你不需要调用git submodule updategit submodule-checkout-branch,只需调用git checkout 就会将所有子模块更新到它们各自的提交并检查相应的分支(如果它们存在的话)。

【讨论】:

  • $ cat .git/hooks/post-checkout -- 应该是vim 等等?
  • 当然,您可以使用vim,或者您喜欢的任何其他文本编辑器。该代码 sn-p 只是为了显示结帐后挂钩文件的外观。
【解决方案7】:

最简单的解决方案是:

git clone --recursive git@github.com:name/repo.git

然后在 repo 目录下 cd 并:

git submodule update --init
git submodule foreach -q --recursive 'git checkout $(git config -f $toplevel/.gitmodules submodule.$name.branch || echo master)'
git config --global status.submoduleSummary true

补充阅读:Git submodules best practices.

【讨论】:

    【解决方案8】:

    我还在搞清楚 git 的内部结构,目前已经搞清楚了:

    1. HEAD 是 .git/ 目录中的一个文件,通常看起来像这样:
    % cat .git/HEAD
    ref: refs/heads/master
    
    1. refs/heads/master 本身就是一个通常具有最新提交的哈希值的文件:
    % cat .git/refs/heads/master 
    cbf01a8e629e8d884888f19ac203fa037acd901f
    
    1. 如果您 git checkout 一个在您的 master 之前的远程分支,这可能会导致您的 HEAD 文件被更新以包含远程 master 中最新提交的哈希:
    % cat .git/HEAD
    8e2c815f83231f85f067f19ed49723fd1dc023b7
    

    这称为分离的 HEAD。远程主机领先于本地主机。当您执行 git submodule --remote myrepo 以获取子模块的最新提交时,默认情况下会执行 checkout,这将更新 HEAD。由于您当前的分支 master 落后,可以说 HEAD 与您当前的分支“分离”。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-02-21
      • 1970-01-01
      • 2015-10-25
      • 1970-01-01
      • 1970-01-01
      • 2020-12-20
      • 2011-03-09
      相关资源
      最近更新 更多