Skip to content

Unable to git checkout to specific branch in git submodule #1815

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
elmarsan opened this issue Jul 9, 2024 · 2 comments
Open

Unable to git checkout to specific branch in git submodule #1815

elmarsan opened this issue Jul 9, 2024 · 2 comments

Comments

@elmarsan
Copy link

elmarsan commented Jul 9, 2024

I am trying to achieve something that I am not sure is possible. I haven't been able to find any similar cases to mine, which is why I am creating this issue. The goal is to see if anyone has encountered this before and knows how to resolve it. I wouldn't say it's a problem per se, just a request for help.

Context

My repository has two git submodules and looks like this:

/src
/submodule_1
/submodule_2

My use case is a bit special because the repository is closely related to one of the git submodules. From now on, I will only mention the "git submodule" and ignore the other one since none of this concerns the other.

Basically, my workflow is triggered by pull requests, and I need to be able to clone the submodule and checkout to a branch with the same name (if a branch with the same name does not exist, master should be used).

This is how I try to checkout in the submodule:

cd submodule_folder
git fetch --all
git merge origin/master
# Try to checkout to branch with same name or master if it does not exist
git checkout -b "origin/$GITHUB_HEAD_REF" 2>&1 | grep -wi 'fatal' && git checkout master
git pull

Then after that, I need to execute some script from that submodule. The problem is I can't checkout to the branch I want (The branch exists in the origin of the git submodule repository, double-checked).

Action configuration:

# ....
# ....
steps:
  - uses: actions/checkout@v3
    with:
       submodules: recursive
       token: ${{ secrets.ACCESS_TOKEN }}
@eXpl0it3r
Copy link

Why do you need a specific branch in the submodule? Why is a detached head of a commit not sufficient?

Are you then later committing changes within the submodule?

@roryabraham
Copy link

roryabraham commented Apr 4, 2025

Ok, I've stumbled upon this issue, after finding https://github.com/orgs/community/discussions/26818.

My use case, if you're curious

My use-case is that I have a brownfield mobile app (some old native code mixed with newer React Native code). The React Native code is the "host app", and then we've got the old native code in a submodule. I'm building out a "synchronized" deploy & QA pipeline involving three primary branches: main, staging, and production. One of the design goals is that:

  • If I'm in the main branch of the parent repo, the submodule always "points" at the main branch of the child repo
  • If I'm in the staging branch of the parent repo, the submodule always "points" at the staging branch of the child repo
  • If I'm in the production branch of the parent repo, the submodule always "points" at the production branch of the child repo.

We also have to synchronize version bumps (we want to be able to build and deploy these apps separately for now too), and we are building out the ability to cherry-pick hotfixes to the staging and production branches. As part of that, we need to do workflows like this:

  1. Checkout main branch in both repos
  2. Bump version on main branch in submodule
  3. Bump version on main branch in parent repo
  4. Update the submodule hash in the parent repo (main branch)
  5. Checkout staging branch in submodule (this part doesn't work out of the box!)
  6. Cherry-pick version bump to staging branch in submodule
  7. Cherry-pick version bump to staging branch in parent repo
  8. Update the submodule hash in the parent repo (staging branch)

The solutions are well-explained in the discussion I linked, but the kicker here is that when you do actions/checkout with the default depth of 1, then git config -f .git/modules/SubmoduleDirectory/config remote.origin.fetch will produce +refs/heads/main:refs/remotes/origin/main, meaning you can only fetch the main branch of the submodule.

Meanwhile, if you do actions/checkout with depth: 0, then git config -f .git/modules/SubmoduleDirectory/config remote.origin.fetch, you'll see +refs/heads/*:refs/remotes/origin/*, meaning that you're free to fetch any branch.

This workflow run illustrates the difference in a minimal reproduction.

Also, I've observed that if you simply do a git clone of the parent repo, then git submodule sync, then git submodule update --init --force --depth=1, you'll find that git config -f .git/modules/SubmoduleDirectory/config remote.origin.fetch produces +refs/heads/main:refs/remotes/origin/main, so it appears to be the default behavior in git.

Next up, I'd like to look at the different commands run by actions/checkout with depth: 0 instead of depth: 1 and see what produces the difference. I imagine it's just a side-effect of the first fetch of the parent repo:

Depth: 1 Depth: 0
git fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +df8d0a98d60ecf496df1f3d51fc63fa080c296e7:refs/remotes/pull/59644/merge git fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/* +df8d0a98d60ecf496df1f3d51fc63fa080c296e7:refs/remotes/pull/59644/merge

I don't really know how git is deciding the fetch refspec for submodules.


If, like me, you don't want to wait for a fetch-depth: 0 (takes almost 3 mins in https://github.com/Expensify/App !!!), another fix is to run this in your action, right after checkout:

git submodule foreach 'git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"'

That will finish quickly and enable you to fetch any remote ref. If you do this, be aware that running git fetch will be as slow as using fetch-depth: 0, so you'll likely want to fetch just specific refs and specify a depth.

What sucks about this solution is that I have to remember to do it any time I'm checking out a different branch in my submodule.

I wonder if the maintainers would consider running git submodule foreach 'git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"' in actions/checkout, to remove this gotcha?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants