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


162

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

为什么会发生?

我必须总是这样做:

git branch
git checkout master

如何确保子模块始终指向master分支?



@bitoiu我看了子树和Google Repo。我还没有完美的解决方案:(
om471987 2013年

1
我在CI环境中使用gitsubmodules的经验很糟糕,也许其他人也有更好的经验。
bitoiu

@JohnnyZ谢谢。我知道子模块指向一个提交而不是树的头。但是为什么要脱离分支。如果您有一个分支,则默认情况下不应该将其附加到它
om471987 2013年

3
不要仅仅因为听说子模块很糟糕就赶快解散子模块。如果您要进行持续集成,那么它们是一个较差的解决方案,但是如果您要嵌入外部项目中的代码并且显式管理所有请求,则它们是近乎完美的解决方案。如果要与不受组织控制的非分支模块集成,这通常是最佳实践。问题在于它们在所有其他情况下都无法很好发挥作用的诱人解决方案。最好的建议是阅读它们的工作原理并评估您的方案。
莎拉·G

Answers:


176

编辑:

请参阅@Simba 答案以获取有效的解决方案

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


老答案:

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

回到问题:为什么会发生?

您描述的情况

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

这是当一个不使用常见的情况的子模块过于频繁或刚开始与子模块。我相信我的陈述是正确的,我们 都曾在某个时候分离过子模块的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跟踪您的remote <branch>
    • 你告诉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。

修复DETACHED HEAD时 .update = checkout

$ 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 <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状态,但我希望您现在对如何调试特定情况有更多的了解。


2
HEAD分离是的默认行为git submodule update --remote。请看一下Simba的答案,我认为这应该是正确的答案。
magomar

78

添加branch选项.gitmodule不涉及在所有子模块的分离行为。@mkungla的旧答案不正确或已过时。

git submodule --help头部卸下是默认行为git submodule update --remote

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

- 远程

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

为什么

那么为什么HEAD在之后脱离update呢?这是由默认模块更新行为checkout引起的。

- 退房

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

为了解释这种奇怪的更新行为,我们需要了解子模块如何工作?

引用Pro Git书中的从子模块开始

尽管sbmodule DbConnector是工作目录中的子目录,但是Git将其视为子模块,并且不在该目录中时不会跟踪其内容。相反,Git将其视为该存储库中特定提交

主仓库跟踪子模块及其在特定点的状态,即提交ID。因此,当您更新模块时,就是将提交ID更新为新的提交ID。

怎么样

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

- 合并

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

--rebase

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

您需要做的就是

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

通过将或设置为,也可以选择--merge--rebase作为默认行为。git submodule updatesubmodule.$name.updatemergerebase

这是有关如何在中配置子模块update的默认更新行为的示例.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

参考文献


6
我使用git submodule update --remote --merge,它以分离状态拉下子模块。还尝试--rebase了相同的结果。
Joe Strout

8
@JoeStrout如果您的子模块已经被分离,则在使用上述命令进行更新之前,请修复分离状态。cd进入子模块,使用将子模块签出到特定分支git checkout master
辛巴

2
或者-如果这对于多个(递归)子模块来说太麻烦了,那么只需执行即可git submodule foreach --recursive git checkout master
stefanct

1
我仅部分了解“ git如何工作”的描述。TBH我对了解git的工作方式并不真正感兴趣,我只想使用它。现在,我知道可以使用修复固定的子模块了git submodule foreach --recursive git checkout master。但是如何防止git总是将它们分离呢?不能为每个子模块设置配置选项!
尼古拉斯

对我来说,运行git submodule update --remote --merge并不会使子模块处于分离的HEAD状态,而是git submodule update在编辑我的.gitmodule文件后运行(如您指示的那样,DID使子模块处于分离的HEAD状态)。
bweber13

41

我已经厌倦了它总是分离,所以我只用一个shell脚本为我所有的模块构建它。我假设所有子模块都在主模块上:这是脚本:

#!/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
git submodule foreach git pull origin
master-

简单明了!谢谢!
zhekaus

12

在这里查看我的答案: Git子模块:指定分支/标签

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

编辑:要在分支跟踪现有的子模块项目,请改用VonC的说明:

Git子模块:指定分支/标签


14
答案应该是内联的;IIRC链接到答案是一个Stack Overflow伪造。
Tony Topper 2014年

1
@TonyTopper即使只是链接到另一个SO答案?IIRC只有外部链接会皱眉,因为这些链接可能会消失,然后链接消失,答案是毫无用处的。但是,SO答案没有这种危险,除非SO消失(无论发生什么情况都可以恢复),否则它们永远不会消失。他也回答了这个问题,branch = master" line into your .gitmodule实际上是完整的回答,为我解决了这个问题。
Mecki

9

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

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


15
对我来说这是行不通的。我已经正确设置了branch = my_wanted_branch。但是运行git submodule update --remote它仍然会退为头部。
Andrius

这样做,然后cd sudmodule&git co thebranche&cd ..,然后git子模块更新--remote,它可以工作了!
pdem

仅当以子模块递归方式克隆超级项目或初始化子模块时,才有效地使用“ .gitmodules”(正在读取)的方式吗?换句话说,您自己的存储库将更新文件,在该文件中,并不总是从子模块配置更新中获利。在我的理解中,'。gitmodules'是在克隆仓库等过程中为配置创建的模板。
Na13-c

3

正如其他人所说的,发生这种情况的原因是父仓库仅包含对子模块中特定提交(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,请参见此答案),则可以创建一个签出后挂钩,该挂钩会自动调用此别名:

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

然后,您无需调用git submodule updategit submodule-checkout-branch,只需这样做即可git checkout将所有子模块更新为各自的提交,并签出相应的分支(如果存在)。


0

最简单的解决方案是:

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子模块最佳实践

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.