Skip to content

submodule.update with init=True does not work if the submodule does not have a 'master' branch #1058

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
jpothier opened this issue Sep 10, 2020 · 5 comments

Comments

@jpothier
Copy link

We have encountered an issue with submodule.update that reproduces in a rather special circumstance:

Setup
Have a git repo, say test, containing one submodule, say test_submodule. test_submodule must have no master branch (let say the default branch is called develop instead).

Check out a revision of test that points to the tip of develop for test_submodule, but do not git submodule init the submodule. The directory hierarchy should look like this:

test/
   ... contents of repo ...
  test_submodule/
     <empty>

Now, create a Repo for test, and call repo.submodules[0].update(init=True)

Expected results
The tip of develop is checked out in test_submodule and the index is clean.

Actual results
test_submodule is pointing at the tip of develop, but all of the files are staged for deletion. Additionally, there is a warning printed:

Failed to checkout tracking branch refs/heads/master

My analysis of why this happens
Before any bad behavior happens, submodule.update clones the submodule repository with -n, which means the clone does not actually check out the commit the clone ends up with. Remember this for later.

After cloning, we begin the process of updating the submodule to match what the parent repo specifies. submodule.update takes an optional branch argument, which defaults to None. When branch is None, we assume the branch is master. Here, we try to find this branch and point HEAD to it, but of course in the repro case this fails because the branch does not exist. This is crucial, because this means we skip the line that marks the repo as "not checked out" by pointing the branch to the "NULL" sha.

Now, recall that a requirement of the repro is that test points to the tip of develop for test_submodule. Since we did not move the repo to the NULL sha beforehand, the repo is already at the desired sha when we arrive at this conditional. Therefore, the condition evaluates to false, and we skip all the code that actually checks out code.

Finally, recall our -n checkout from the first paragraph. Since we did not check out any code after cloning, the repo is left in an un-checked-out state, which is exactly the "Actual results" state described above.

Closing thoughts
We can workaround this issue simply by adding a dummy master branch to test_submodule, but this should not be required. Ideally, submodule.update does not require a valid branch to operate correctly, since git submodule update --init works just fine without one.

@Byron
Copy link
Member

Byron commented Sep 12, 2020

Thanks for taking the time for this fantastic issue description! Unfortunately, nothing I could write here would do it any justice because I wouldn't know how to fix that.
I am glad there is a workaround, which might as well be using repo.git(…) directly where needed.

@JonZeolla
Copy link

Thanks for the helpful write-up, this saved me a lot of troubleshooting time. It seems the cleanest approach (at least in my situation) was to specify the branch in the .gitmodules config, so it won't default to None and then assume master.

@cwbusacker
Copy link

cwbusacker commented Jul 13, 2023

A workaround that worked for me was the following:

    for submodule in repo.submodules:
        submodule.update(init=True)
        sub_repo = submodule.module()
        sub_repo.git.reset(hard=True)

Explanation: submodule.update(init=True) will target the branch to the sha required, but include the staged deleted content. sub_repo.git.reset(hard=True) will undelete the content.

hedger added a commit to flipperdevices/flipper-application-catalog that referenced this issue Dec 22, 2023
* Removed gitpython -
gitpython-developers/GitPython#1058 is a
blocker
* Now calling git directly
@jgoosens
Copy link

jgoosens commented Oct 8, 2024

A workaround that worked for me was the following:

    for submodule in repo.submodules:
        submodule.update(init=True)
        sub_repo = submodule.module()
        sub_repo.git.reset(hard=True)

Explanation: submodule.update(init=True) will target the branch to the sha required, but include the staged deleted content. sub_repo.git.reset(hard=True) will undelete the content.

Ran into a similar issue, an even shorter workaround is

for submodule in repo.submodules:
    submodule.update(init=True, force=True)

The warning will still print, but thanks to the force=True arg the deletion will be unstaged.

Alternatively, you can add the branch explicitly to your .gitmodules file first:

git submodule set-branch -b develop test_submodule
$ git diff
diff --git a/.gitmodules b/.gitmodules
index c5ebd90..1289daa 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
 [submodule "test_submodule"]
        path = test_submodule
        url = <remote URI>
+       branch = develop

and the warning won't appear at all.

@tomlin7
Copy link

tomlin7 commented Mar 13, 2025

Here's a hack I used for my text editor thing

# s = git.Submodule() instance

new = Submodule(
    s.repo,
    s.binsha,
    s.mode,
    s.path,
    s.name,
    s.parent_commit,
    s.url,
    # hack to get gitpython working with our setup
    # i.e default is `master` (unless something specified in .gitmodules) and GitHub uses `main`
    "refs/heads/main",
)
new.submodule_repo.update(init=True, force=True)

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

No branches or pull requests

6 participants