209

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

为什么会这样?

我必须始终这样做:

git branch
git checkout master

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

4

8 回答 8

188

编辑:

有关有效解决方案,请参阅@Simba 答案

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


旧答案:

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

所以回到这个问题:为什么会发生?

你描述的情况

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

当一个人不经常使用子模块或刚开始使用模块时,这是一种常见的情况。我相信我的说法是正确的,我们 都曾在某个时候我们的submodule的 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,因为它与上述配置问题之一有关。

固定分离头时.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 状态,但我希望你现在能更多地了解如何调试你的特定情况。

于 2016-04-02T15:38:02.633 回答
130

git submodule --help, HEAD 分离git submodule update --remote. 这与在子模块中跟踪哪个分支无关。

对于任何只想要解决方案的人,直接跳到第二部分。

原因

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

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

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

尽管 sbmoduleDbConnector是您工作目录中的子目录,但 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 分离。为什么我的 Git 存储库进入了分离的 HEAD 状态?

解决方案

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

man git-submodule

- 合并

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

--rebase

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

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

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:在配置文件中添加选项

gitmodule另一种解决方案是通过设置submodule.$name.updatemerge或来更改文件中的子模块更新行为rebase。它基本上意味着你可以git submodule update --remote不通过--merge--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

或者通过命令行配置,

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

其他

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

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

- 偏僻的

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

参考

于 2019-04-08T10:10:48.290 回答
62

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

从您的父模块执行它

于 2018-04-24T08:42:42.463 回答
13

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

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

编辑:要跟踪分支中的现有子模块项目,请在此处遵循 VonC 的说明:

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

于 2013-09-14T02:37:24.900 回答
8

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

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

于 2017-05-09T14:30:57.963 回答
7

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

但是,特别是如果您在父 repo 和子模块中都积极开发,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 updateor git submodule-checkout-branch,只需这样做git checkout就会将所有子模块更新到它们各自的提交并检查相应的分支(如果它们存在)。

于 2019-08-28T00:31:08.150 回答
2

最简单的解决方案是:

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

然后 cd 在 repo 目录中,然后:

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

于 2020-06-04T10:31:28.010 回答
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一个在你的 master 之前的远程分支,这可能会导致你的 HEAD 文件被更新以包含远程 master 中最新提交的哈希:
% cat .git/HEAD
8e2c815f83231f85f067f19ed49723fd1dc023b7

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

于 2020-10-23T22:21:34.347 回答