为什么我的GIT子模块HEAD与master分离?

时间:2013-09-12 17:18:42

标签: git git-submodules

我正在使用GIT子模块。从服务器提取更改后,很多时候我的子模块头与主分支分离。

为什么会这样?

我必须一直这样做:

git branch
git checkout master

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

8 个答案:

答案 0 :(得分:153)

就我个人而言,我讨厌这些可能会停止工作的外部链接的答案,并检查我的答案here (除非问题是重复的) - 指导哪些问题涵盖了主题之间其他主题的界限,但总体上等于:"我没有回答,请阅读文档。"

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

您描述的情况

  

从服务器提取更改后,很多时候我的子模块头与主分支分离。

这是一种常见情况,当一个人不经常使用子模块或刚刚开始使用子模块时。我相信我说的是正确的,我们都已经存在于我们的子模块的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跟踪您的远程<branch>
    • 其次告诉git从远程更新你的子模块。
    $ git submodule add -b <branch> <repository> [<submodule-path>]
    $ 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>

但是,即使您已将子模块配置为跟踪正确的分支,您仍然可以发现自己处于子模块获取的情况HEAD detached at <commit-hash>

在常见情况下,您现在已经修复了DETACHED HEAD,因为它与上述配置问题之一有关。但请记住,您的父存储库现在不再管理​​子模块的状态(使用提交给父存储库的提交哈希),因为您的子模块正在跟踪其自己的远程分支,这为失败提供了新的可能性。

在您的父级和子模块的路径中运行$ git status,以验证所有内容都已正确跟踪并且所有内容都是最新的,然后运行$ cd <parent-repo>git submodule update --remote。如你所见,如果你再次运行git status,现在一切都很好。

为了证明当所有东西看起来都配置正确并且你不希望得到脱离头的东西可能会出错时,让我们来看看以下内容:

$ 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&#39;时,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 <hash>

$ 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状态,但我希望你现在能更好地了解如何调试你的特定情况。

答案 1 :(得分:24)

我厌倦了它总是分离所以我只是使用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 

从父模块执行

答案 2 :(得分:9)

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

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

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

Git submodules: Specify a branch/tag

答案 3 :(得分:7)

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

branch = <branch-name-you-want-module-to-checkout>

答案 4 :(得分:3)

branch中添加.gitmodule选项不足以帮助您的子模块不分离。 @mkungla告诉你的话是错误的。

git submodule --help中,默认行为是git submodule update --remote 断开。

首先,无需指定要跟踪的分支origin/master是要跟踪的默认分支。

  

-远程

     

代替使用超级项目的记录的SHA-1更新子模块,而要使用子模块的远程跟踪分支的状态。使用的遥控器是分支机构的遥控器(branch.<name>.remote),默认为origin 。远程分支使用的默认值为master

为什么

那为什么在update之后分离HEAD?因为submodule.$name.update默认行为是checkout

  

-结帐

     

在子模块中的分离的HEAD 上签出超级项目中记录的提交。这是默认行为,此选项的主要用途是在设置为submodule.$name.update以外的值时覆盖checkout

如何

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

  

-合并

     

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

     

-重新设置

     

将当前分支重新基于超级项目中记录的提交。如果指定了此选项,则子模块的HEAD将不分离

您需要做的就是

git submodule update --remote --merge
# or
git submodule update --remote --rebase

通过将--merge设置为--rebasegit submodule update,还可以选择将submodule.$name.updatemerge设置为rebase的默认行为

下面是在.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

RTFM git submodule --help

答案 5 :(得分:2)

正如其他人所说的,发生这种情况的原因是父级回购仅包含对子模块中特定提交(其SHA1)的引用–它对分支一无所知。这就是它应该如何工作的:位于该提交的分支可能已向前移动(或向后移动),并且如果父仓库已引用该分支,则在发生这种情况时很容易中断。

但是,尤其是如果您同时在父存储库和子模块中进行开发时,detached HEAD状态可能会造成混乱,并可能带来危险。如果在子模块处于detached HEAD状态时在子模块中进行提交,则这些提交将变得悬而未决,您很容易丢失工作。 (通常可以使用git reflog挽救悬空的提交,但是最好首先避免它们。)

如果您像我一样,那么大多数时候如果子模块中有一个分支指向被检出的提交,您宁愿检出该分支,也不愿在分支处处于分离的HEAD状态。 。您可以通过在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即可将所有子模块更新为各自的提交并签出相应的分支(如果存在)

答案 6 :(得分:1)

最简单的解决方案是:

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

答案 7 :(得分:0)

我仍在弄清楚git的内部原理,并且到目前为止已经弄清楚了:

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

这称为分离的HEAD 。远程主服务器在本地主服务器之前。当您执行 git子模块--remote myrepo 以获取子模块的最新提交时,默认情况下它将执行结帐,这将更新HEAD。既然可以说,由于您当前的分支主管在后面,因此HEAD与您当前的分支“分离”。