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

hyrbngr7  于 2023-08-01  发布在  Git
关注(0)|答案(8)|浏览(113)

我正在使用Git子模块。从服务器拉取更改后,很多次我的子模块头从主分支分离。
为什么会这样呢?
我总是要做:

git branch
git checkout master

字符串
我怎样才能确保我的子模块总是指向master分支?

f3temu5u

f3temu5u1#

编辑:
**有效解决方案见@Simba Answer *

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

旧答案:

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

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

你描述的情况
从服务器拉取更改后,很多次我的子模块头从主分支分离。
这是一个常见的情况,当一个人不经常使用 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跟踪你的远程<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状态,但我希望您现在对如何调试您的特定情况有了更多的了解。

kd3sttzy

kd3sttzy2#

git submodule --help开始,HEAD detached是git submodule update --remote的默认行为。这与在子模块中跟踪哪个分支无关。
对于任何人只想要一个解决方案,直接跳到第二部分。

原因

我们需要了解什么是子模块。
子模块是一种将另一个项目包含到当前项目中的方法。它并不是真的将这些文件添加到主项目的提交历史中,而是引用子模块的快照(提交)。
Quote from Starting with Submodules section in book Pro Git
虽然submodule DbConnector是你工作目录中的一个子目录,但是Git将它视为一个子模块,当你不在该目录中时,它不会跟踪它的内容。相反,Git将其视为来自该仓库的特定提交
每一个repo的提交都是代码在那个时候的快照/状态。此时子模块的状态也必须是确定性。你不能说在这个提交中,我包含了另一个repo的master(或另一个)分支。您必须通过提交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和submodule时,它将克隆子模块和checkout指定的提交。
而检出一个提交会导致HEAD分离。Why did my Git repo enter a detached HEAD state?

解决方案

如果希望子模块自动与远程分支合并,请使用--merge--rebase
man git-submodule
--merge
此选项仅对update命令有效。将超级项目中记录的提交合并到子模块的当前分支中。如果给出这个选项,子模块的HEAD将不被分离
--rebase
将当前分支重定基到超级项目中记录的提交。如果给出此选项,则子模块的HEAD将不被分离
如果您的子模块已经被分离,请在使用以下2个解决方案之前修复分离状态。

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

方案一:在命令行中使用选项

# 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

方案二:将选项添加到配置文件

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

其他

.gitmodule中添加branch选项与子模块的分离行为完全无关。mkungla的旧答案是不正确的,或者说是过时的。
让我们清楚地说明不需要指定要跟踪的分支origin/master是要跟踪的默认分支。
--远程
不要使用超级项目记录的SHA-1更新子模块,而是使用子模块的远程跟踪分支的状态。使用的远程是分支的远程(branch.<name>.remote),默认为origin。使用的远程分支默认为master

引用

mm9b1k5b

mm9b1k5b3#

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

字符串
从父模块执行它

eeq64g8w

eeq64g8w4#

在这里看看我的答案:Git子模块:指定分支/标记
如果需要,可以手动将“分支= master”行添加到.gitmodules文件中。阅读链接,看看我的意思。
编辑:要跟踪分支中的现有子模块项目,请按照VonC的说明进行操作:
Git子模块:指定分支/标记

zd287kbt

zd287kbt5#

另一种使子模块 checkout 分支的方法是转到根文件夹中的.gitmodules文件,并在模块配置中添加字段branch,如下所示:
branch = <branch-name-you-want-module-to-checkout>

kh212irz

kh212irz6#

正如其他人所说,发生这种情况的原因是父repo只包含子模块中特定提交的引用(SHA1)-它不知道任何关于分支的信息。它应该是这样工作的:在该提交处的分支可能已经向前(或向后)移动,并且如果父存储库已经引用了该分支,则当发生这种情况时,它可能容易地中断。
但是,特别是如果您同时在父代码库和子模块中进行开发,detached HEAD状态可能会令人困惑并具有潜在的危险。如果您在子模块处于detached HEAD状态时进行提交,这些提交就会变成悬空状态,您很容易丢失您的工作。(通常可以使用git reflog来拯救悬置提交,但最好一开始就避免它们。
如果你像我一样,那么大多数时候**如果子模块中有一个分支指向正在 checkout 的提交,你宁愿 checkout 那个分支,而不是在同一个提交中处于分离的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,任何在提交时 checkout 的子模块都将 checkout 该分支。如果你不经常有多个本地分支都指向同一个提交,那么这通常会做你想要的;如果没有,那么至少它将确保你所做的任何提交都进入一个实际的分支,而不是被搁置。
此外,如果你已经设置了git在checkout时自动更新子模块(使用git config --global submodule.recurse true,参见this answer),你可以创建一个postcheckout钩子来自动调用这个别名:

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


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

3npbholx

3npbholx7#

最简单的解决方案是:

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

9o685dep

9o685dep8#

我也还在弄清楚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中最新提交的hash:

% cat .git/HEAD
8e2c815f83231f85f067f19ed49723fd1dc023b7


这被称为分离的HEAD。远程主服务器在本地主服务器之前。当你执行git submodule --remote myrepo来获取子模块的最新提交时,它会默认执行checkout,这会更新HEAD。因为你当前的分支master在后面,所以HEAD从你当前的分支中“分离”出来。

相关问题