Can't check out a different branch on a submodule

I’m using actions/checkout@v2 to check out a repo and all its submodules. or From my workflow:

- name: Checkout repo with submodules (theme and public)
      uses: actions/checkout@v2
        token: ${{ secrets.SOME_PAT }}
        submodules: recursive

The main repo checks out the master branch, but my submodule checks out the latest commit as a detached HEAD. The submodule master also shows up when I list branches, but I have to explicitly check it out if I want that.

However, none of the other branches appear for the submodule, and I can’t find a way to check them out when I need to. I don’t see that I can do this through the checkout Action, but I’ve tried some other things I’d expect to at least do something. In the next Action step, I’ve tried these commands on the submodule.

git -C subfolder fetch --all
git -C subfolder remote -v update
git -C subfolder pull --all

But no matter what I do, I only see these:

$ git branch

* (HEAD detached at 01e4b10)

None of my other branches ever show up, and I can’t check them out. Any idea why? Is there some limitation in Actions with submodules? Am I just doing something silly and wrong?

Okay, I’ve tried lots of new things, and I figured something out. I can see the remote refs like this:

$ git ls-remote refs
[some hash number]        refs/heads/master
[some hash number]        refs/heads/test1

So the other branch does exist remotely. But still a branch list doesn’t show test1, so I can’t switch to it or fetch it.

$ git branch -a
* master
  remotes/origin/HEAD -> origin/master

This only occurs after using the checkout Action. When I clone with submodules locally everything is fine. I just don’t know if this is a bug for the checkout action or if I’m doing something wrong.

Hi @aormsby,

Submodules are not on a branch. They are just a pointer to a particular commit of the submodule’s repository.

After checkout the parent repo, you can update the submodule branch, and checkout the target branch then. Code sample as below(submodule : ticket7 with B1 branch)

      - name: checkout
        uses: actions/checkout@v2
          token: ${{ secrets.GITHUB_TOKEN }}    # you can use PAT token here.
      - name: update submodule branch
        run: |
           git config --file=.gitmodules submodule.Submod.branch B1  # change branch to 'B1' branch
           git submodule sync                        # sync
           git submodule update --init --recursive --remote   # pull the submodule
      - name: checkout submodule B1 branch
        run: |
          cd ticket7    # go to submodule repo
          git checkout -b B1 origin/B1   # checkout the target branch.

Please refer to my workflow for the details.



Ooooohhhhhh, this makes complete sense. I just didn’t realize I needed to set the branch property of .gitmodules to sync it that way. Nice solution.

There’s still one small snag for me. If the submodule is a private repo, I get a fatal error when I run the sync because the submodule URL doesn’t include user name and key information by default.

Cloning into '/home/runner/work/my/submod/path'...
fatal: could not read Username for '': No such device or address
fatal: clone of '' into submodule path '/home/runner/work/my/submod/path' failed

I may be able to manipulate the URL in .gitmodules to include that, but I think it would be better to use the secret token like in the checkout. Shouldn’t the token credentials persist from the checkout action?? The readme says they should by default, but maybe I’m doing something wrong there as well.

Edit: Also, could you explain why keeping submodules: recursive in the workflow causes a fatal error? The update command results in fatal: Needed a single revision

Okay, I’ve got it! The original solution I marked got me really close to the right functionality, but there were still some issues coming through. However, I want to thank @weide-zhou for their help. I couldn’t have come to these solutions without the push. :slight_smile:

The Underlying Problem

The git refspec for the submodules wasn’t configured to track any remote refs outside ‘master’ so I could never get the other remote refs to other branches.

Incorrect config:

[remote "origin"]
	fetch = +refs/heads/master:refs/remotes/origin/master
# tied to master refs only

Config that gets all refs:

[remote "origin"]
	fetch = +refs/heads/*:refs/remotes/origin/*
# gets all refs - *

The checkout action has a default fetch-depth: 1, which avoids pulling all the history of the repo. However, setting a specific depth triggers some command options during the checkout that limits all fetch refs to the ‘master’ branch in the config (or whatever other ref you set in the workflow).

So we gotta fix that. @weide-zhou was on the way to that, but I found two ways to do it that attack the problem more directly.

Solution #1 - change fetch-depth


    - name: Checkout repo with submodules (theme and public)
      uses: actions/checkout@v2
        token: ${{ secrets.YOUR_PAT}}  # for private repos (including submodules)
        submodules: 'recursive'
        fetch-depth: '0'    ***********

From the checkout readme:

0 indicates all history for all branches and tags.

Setting the value to 0 leaves the fetch config set to *, and all remote refs are available for checkout. This is the quick fix. The downside is that you’re pulling the entire history of your branches and tags for the main repo and all submodules, so I also came up with a more surgical adjustment.

Solution #2 - modify the config fetch by hand

This solution allows control over specific submodule fetch config so other submodule configurations are left untouched.

Workflow (same minus fetch-deptch):

    - name: Checkout repo with submodules (theme and public)
      uses: actions/checkout@v2
        token: ${{ secrets.YOUR_PAT}}  # for private repos (including submodules)
        submodules: 'recursive' 

Somewhere in your action you can run the following commands:

$ git -C submodPath config remote.origin.fetch +refs/heads/*:refs/remotes/origin/*
## yeah, that's it!

## after that key config update, the rest is what @weide-zhou recommended
$ git submodule sync --recursive submodPath
$ git submodule update --init --recursive --remote submodPath
$ git -C public checkout myBranch
  • submodPath should be replaced with the path of the submodule from the root
  • The * will allow fetching all refs in the submodule, but you can also replace it with the specific branch name you want if that’s all you need.

The main reason I had to build on @weide-zhou’s solution was to maintain the submodules: 'recursive' setting in the workflow. It’s easier to manage general submodule checkouts in the first step than to have to deal with them in my following actions. But yeah, mostly the same! I just needed that key change in the ref config.