[git] Why is my Git Submodule HEAD detached from master?

I am using Git submodules. After pulling changes from server, many times my submodule head gets detached from master branch.

Why does it happen?

I have to always do:

git branch
git checkout master

How can I make sure that my submodule is always pointing to master branch?

This question is related to git git-submodules

The answer is


I am also still figuring out the internals of git, and have figured out this so far:

  1. HEAD is a file in your .git/ directory that normally looks something like this:
% cat .git/HEAD
ref: refs/heads/master
  1. refs/heads/master is itself a file that normally has the hash value of the latest commit:
% cat .git/refs/heads/master 
cbf01a8e629e8d884888f19ac203fa037acd901f
  1. If you git checkout a remote branch that is ahead of your master, this may cause your HEAD file to be updated to contain the hash of the latest commit in the remote master:
% cat .git/HEAD
8e2c815f83231f85f067f19ed49723fd1dc023b7

This is called a detached HEAD. The remote master is ahead of your local master. When you do git submodule --remote myrepo to get the latest commit of your submodule, it will by default do a checkout, which will update HEAD. Since your current branch master is behind, HEAD becomes 'detached' from your current branch, so to speak.


The other way to make your submodule to check out the branch is to go the .gitmodules file in the root folder and add the field branch in the module configuration as following:

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


i got tired of it always detaching so i just use a shell script to build it out for all my modules. i assume all submodules are on master: here is the script:

#!/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 

execute it from your parent module


As other people have said, the reason this happens is that the parent repo only contains a reference to (the SHA1 of) a specific commit in the submodule – it doesn't know anything about branches. This is how it should work: the branch that was at that commit may have moved forward (or backwards), and if the parent repo had referenced the branch then it could easily break when that happens.

However, especially if you are actively developing in both the parent repo and the submodule, detached HEAD state can be confusing and potentially dangerous. If you make commits in the submodule while it's in detached HEAD state, these become dangling and you can easily lose your work. (Dangling commits can usually be rescued using git reflog, but it's much better to avoid them in the first place.)

If you're like me, then most of the time if there is a branch in the submodule that points to the commit being checked out, you would rather check out that branch than be in detached HEAD state at the same commit. You can do this by adding the following alias to your gitconfig file:

[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"

Now, after doing git submodule update you just need to call git submodule-checkout-branch, and any submodule that is checked out at a commit which has a branch pointing to it will check out that branch. If you don't often have multiple local branches all pointing to the same commit, then this will usually do what you want; if not, then at least it will ensure that any commits you do make go onto an actual branch instead of being left dangling.

Furthermore, if you have set up git to automatically update submodules on checkout (using git config --global submodule.recurse true, see this answer), you can make a post-checkout hook that calls this alias automatically:

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

Then you don't need to call either git submodule update or git submodule-checkout-branch, just doing git checkout will update all submodules to their respective commits and check out the corresponding branches (if they exist).


The simplest solution is:

git clone --recursive [email protected]:name/repo.git

Then cd in the repo directory and:

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

Additional reading: Git submodules best practices.


Adding a branch option in .gitmodule is NOT related to the detached behavior of submodules at all. The old answer from @mkungla is incorrect, or obsolete.

From git submodule --help, HEAD detached is the default behavior of git submodule update --remote.

First, there's no need to specify a branch to be tracked. origin/master is the default branch to be tracked.

--remote

Instead of using the superproject's recorded SHA-1 to update the submodule, use the status of the submodule's remote-tracking branch. The remote used is branch's remote (branch.<name>.remote), defaulting to origin. The remote branch used defaults to master.

Why

So why is HEAD detached after update? This is caused by the default module update behavior: checkout.

--checkout

Checkout the commit recorded in the superproject on a detached HEAD in the submodule. This is the default behavior, the main use of this option is to override submodule.$name.update when set to a value other than checkout.

To explain this weird update behavior, we need to understand how do submodules work?

Quote from Starting with Submodules in book Pro Git

Although sbmodule DbConnector is a subdirectory in your working directory, Git sees it as a submodule and doesn’t track its contents when you’re not in that directory. Instead, Git sees it as a particular commit from that repository.

The main repo tracks the submodule with its state at a specific point, the commit id. So when you update modules, you're updating the commit id to a new one.

How

If you want the submodule merged with remote branch automatically, use --merge or --rebase.

--merge

This option is only valid for the update command. Merge the commit recorded in the superproject into the current branch of the submodule. If this option is given, the submodule's HEAD will not be detached.

--rebase

Rebase the current branch onto the commit recorded in the superproject. If this option is given, the submodule's HEAD will not be detached.

All you need to do is,

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

Recommended alias:

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

# do submodule update with
git supdate

There's also an option to make --merge or --rebase as the default behavior of git submodule update, by setting submodule.$name.update to merge or rebase.

Here's an example about how to config the default update behavior of submodule update in .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

Or configure it in command line,

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

References


Check out my answer here: Git submodules: Specify a branch/tag

If you want, you can add the "branch = master" line into your .gitmodules file manually. Read the link to see what I mean.

EDIT: To track an existing submodule project at a branch, follow VonC's instructions here instead:

Git submodules: Specify a branch/tag