Git - submodules HEAD is always detached after running update?

1.4k views Asked by At

I have this configuration for submodule in .gitmodules file:

[submodule "sub"]
    shallow = true
    branch = master
    path = sub
    url = https://path/to/repo.git

Now I want when someone clones my repo and then runs these commands:

git submodule init
git submodule update

Is to get shallow master branch of submodule. But what happens is it does not checkout to master branch. It will always get to detached head, so then I need to manually run git checkout master. So instead of just those two commands, user needs to run one additional.

I looked into this: Why is my GIT Submodule HEAD detached from master?

But any advice that was on accepted answers, does not seem to help: I added branch I want in .gitmodules file, I added remote upstream to be master (this only works for already cloned/updated repository after I had to checkout to master myself).

So is this intended to always get detached HEAD if someone clones my repository and wants to set up submodule?

2

There are 2 answers

0
Simba On BEST ANSWER

Yes, you're right. The top answer maded by user mkungla from Why is my GIT Submodule HEAD detached from master? is nonsense.

Adding a branch option in .gitmodule is NOT related to the detached behavior of submodules at all.

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? Because the default behavior of submodule.$name.update is 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.

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

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

My whole answer is based on the manual. git submodule --help.

0
Carlo Wood On

I'm still investigating this, but here is the script that I came up with and am using now:

#! /bin/bash                                                                                                                                                                                 
# Written by Carlo Wood 2016                                                                                                                                                                 

echo "In \"$(pwd)\", entering $0 $*"                                                                                                                                                         

# This script should be run from the root of the parent project.                                                                                                                             
if ! test -e .git; then                                                                                                                                                                      
  echo "$0: $(pwd) is not a git repository."                                                                                                                                                 
  exit 1                                                                                                                                                                                     
fi                                                                                                                                                                                           

# Parse command line parameters.                                                                                                                                                             
opt_init=                                                                                                                                                                                    
opt_recursive=                                                                                                                                                                               
do_foreach=0                                                                                                                                                                                 
initial_call=1                                                                                                                                                                               
while [[ $# -gt 0 ]]                                                                                                                                                                         
do                                                                                                                                                                                           
  case $1 in                                                                                                                                                                                 
    --init)                                                                                                                                                                                  
      opt_init=$1                                                                                                                                                                            
      ;;                                                                                                                                                                                     
    --recursive)                                                                                                                                                                             
      opt_recursive=$1                                                                                                                                                                       
      do_foreach=1                                                                                                                                                                           
      ;;                                                                                                                                                                                     
    --reentry)
      initial_call=0
      ;;
    --)
      break;
      ;;
    -*)
      echo "Unknown option $1"
      exit 1
      ;;
    *)
      break
      ;;
  esac
  shift
done

# Determine the full path to this script.
if [[ ${0:0:1} = / ]]; then
  FULL_PATH="$0"
else
  FULL_PATH="$(realpath $0)"
fi

if test "$initial_call" -eq 1; then
  do_foreach=1
else
  # Script is called from git submodule foreach ...'
  name="$1"
  path="$2"
  sha1="$3"
  toplevel="$4"
  # Make sure we are in the right directory.
  cd "$toplevel/$path" || exit 1
  # Does the parent project want us to checkout a branch for this module?
  SUBMODULE_BRANCH=$(git config -f "$toplevel/.gitmodules" submodule.$name.branch)
  if test -n "$SUBMODULE_BRANCH"; then
    echo "Calling 'git checkout $SUBMODULE_BRANCH' in $(pwd)"
    git checkout $SUBMODULE_BRANCH || exit 1
    echo "Calling 'git pull' in $(pwd)"
    git pull || exit 1
    if test $(git rev-parse HEAD) != "$sha1"; then
      # Update the parent project to point to the head of this branch.
      pushd "$toplevel" >/dev/null
      SN1=$(git stash list | grep '^stash' | wc --lines)
      git stash save --quiet Automatic stash of parent project by update_submodules.sh
      SN2=$(git stash list | grep '^stash' | wc --lines)
      git add $name
      git commit -m "Update of submodule $name to current $SUBMODULE_BRANCH"
      if test $SN1 -ne $SN2; then
        git stash pop --quiet
      fi
      popd >/dev/null
    fi
  elif test $(git rev-parse HEAD) != "$sha1"; then
    # No submodule.$name.branch for this submodule. Just checkout the detached HEAD.
    git checkout $sha1
  fi
fi

echo "do_foreach=$do_foreach; opt_init=$opt_init; opt_recursive=$opt_recursive; name=$name; path=$path; sha1=$sha1; toplevel=$toplevel; pwd=$(pwd)"

if test $do_foreach -eq 1; then
  if test -n "$opt_init"; then
    echo "Calling 'git submodule init'"
    git submodule init
  fi
  # Make sure the submodules even exist.
  echo "Calling 'git submodule update'"
  git submodule update
  # Call this script recursively for all submodules.
  echo 'Calling '"'"'git submodule foreach '"$FULL_PATH --reentery $opt_init $opt_recursive"' $name $path $sha1 $toplevel'"'"
  git submodule foreach "$FULL_PATH --reentry $opt_init $opt_recursive"' $name $path $sha1 $toplevel'
fi

Calling this script will do the same as 'git submodule update' and even supports --init and --recursive. However, you can configure submodules to checkout and pull a branch instead by setting a 'branch' value; for example: git config -f .gitmodules submodule.NAME.branch master will cause submodule NAME to check out and pull branch master instead of whatever the current SHA1 is. It will then also update the parent project to point to the HEAD of that branch.