With Git 2.27 (Q2 2020), the situation should improve, and "git checkout --recurse-submodules
" works better with a nested submodule hierarchy.
See commit 846f34d, commit e84704f, commit 16f2b6b, commit 8d48dd1, commit d5779b6, commit bd35645 (17 Feb 2020) by Philippe Blain (phil-blain
).
(Merged by Junio C Hamano -- gitster
-- in commit fe87060, 27 Mar 2020)
unpack-trees
: check for missing submodule directory in merged_entry
Reported-by: Philippe Blain
Reported-by: Damien Robert
Signed-off-by: Philippe Blain
Using git checkout --recurse-submodules
to switch between a branch with no submodules and a branch with initialized nested submodules currently causes a fatal error:
$ git checkout --recurse-submodules branch-with-nested-submodules
fatal: exec '--super-prefix=submodule/nested/': cd to 'nested'
failed: No such file or directory
error: Submodule 'nested' could not be updated.
error: Submodule 'submodule/nested' cannot checkout new HEAD.
error: Submodule 'submodule' could not be updated.
M submodule
Switched to branch 'branch-with-nested-submodules'
The checkout succeeds, but the worktree and index of the first level submodule are left empty:
$ cd submodule
$ git -c status.submoduleSummary=1 status
HEAD detached at b3ce885
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
deleted: .gitmodules
deleted: first.t
deleted: nested
fatal: not a git repository: 'nested/.git'
Submodule changes to be committed:
* nested 1e96f59...0000000:
$ git ls-files -s
$ # empty
$ ls -A
.git
The reason for the fatal error during the checkout is that a child git process tries to cd
into the yet unexisting nested submodule directory.
The sequence is the following:
The main git process (the one running in the superproject) eventually reaches write_entry()
in entry.c
, which creates the first level submodule directory and then calls submodule_move_head()
in submodule.c
, which spawns git read-tree
in the submodule directory.
The first child git process (the one in the submodule of the superproject) eventually calls check_submodule_move_head()
at unpack_trees.c:2021
, which calls submodule_move_head
in dry-run mode, which spawns git read-tree
in the nested submodule directory.
The second child git process tries to chdir()
in the yet unexisting nested submodule directory in start_command()
at run-command.c
and dies before exec'ing.
The reason why check_submodule_move_head()
is reached in the first child and not in the main process is that it is inside an if(submodule_from_ce())
construct, and submodule_from_ce()
returns a valid struct submodule pointer, whereas it returns a null pointer in the main git process.
The reason why submodule_from_ce()
returns a null pointer in the main git
process is because the call to cache_lookup_path()
in config_from()
(called from submodule_from_path()
in submodule_from_ce()
) returns a null pointer since the hashmap "for_path"
in the submodule_cache
of the_repository
is not yet populated.
It is not populated because both repo_get_oid(repo, GITMODULES_INDEX, &oid)
and repo_get_oid(repo, GITMODULES_HEAD, &oid)
in config_from_gitmodules()
at submodule-config.c
return -1
, as at this stage of the operation, neither the HEAD of the superproject nor its index contain any .gitmodules
file.
In contrast, in the first child the hashmap is populated because repo_get_oid(repo, GITMODULES_HEAD, &oid)
returns 0 as the HEAD of the first level submodule, i.e. .git/modules/submodule/HEAD
, points to a commit where .gitmodules
is present and records 'nested' as a submodule.
Fix this bug by checking that the submodule directory exists before calling check_submodule_move_head()
in merged_entry()
in the if(!old)
branch, i.e. if going from a commit with no submodule to a commit with a submodule present.
Also protect the other call to check_submodule_move_head()
in merged_entry()
the same way as it is safer, even though the else if (!(old->ce_flags & CE_CONFLICTED))
branch of the code is not at play in the present bug.
The other calls to check_submodule_move_head()
in other functions in unpack_trees.c
are all already protected by calls to lstat()
somewhere in the program flow so we don't need additional protection for them.
All commands in the unpack_trees
machinery are affected, i.e. checkout, reset and read-tree when called with the --recurse-submodules
flag.
This bug was first reported here.
git submodule init
which registered the submodule back. – Spree