Teach submodules a config variable indicating the fact that they are a submodule. If this config is set to false or unset, Git may assume the current repo is not a submodule. Git commands can use this variable to decide whether to traverse the filesystem and look for a superproject at all. 'git rev-parse --show-superproject-working-tree' can learn to exit early if this config is unset or false. Other newly added or implicit behavior - like "git status" showing the submodule's status in relation to the superproject, or a config shared between the superproject and submodule - can use this config to decide whether to search the parent directory to find a superproject. Introduce this config everywhere we add a new submodule, or touch one that already exists, so that we can proliferate it in repos which are already out in the world using submodules. Signed-off-by: Emily Shaffer <emilyshaffer@xxxxxxxxxx> Helped-by: Junio C Hamano <gitster@xxxxxxxxx> --- Documentation/config/submodule.txt | 6 ++++ builtin/submodule--helper.c | 11 +++++++ submodule.c | 12 +++++++ t/t7400-submodule-basic.sh | 4 +++ t/t7406-submodule-update.sh | 8 +++++ t/t7412-submodule-absorbgitdirs.sh | 50 ++++++++++++++++++++++++++++-- 6 files changed, 89 insertions(+), 2 deletions(-) diff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt index ee454f8126..99d5260b8e 100644 --- a/Documentation/config/submodule.txt +++ b/Documentation/config/submodule.txt @@ -91,3 +91,9 @@ submodule.alternateErrorStrategy:: `ignore`, `info`, `die`. Default is `die`. Note that if set to `ignore` or `info`, and if there is an error with the computed alternate, the clone proceeds as if no alternate was specified. + +submodule.hasSuperproject:: + Indicates whether this repository is a submodule. If this config is set + to 'true', Git may traverse the filesystem above this submodule in order + to identify the superproject. It is set automatically during submodule + creation, update, and 'git submodule absorbgitdir'. diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index c5d3fc3817..eda9ed550e 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1839,6 +1839,11 @@ static int clone_submodule(struct module_clone_data *clone_data) git_config_set_in_file(p, "submodule.alternateErrorStrategy", error_strategy); + /* + * Teach the submodule that it's a submodule. + */ + git_config_set_in_file(p, "submodule.hasSuperproject", "true"); + free(sm_alternate); free(error_strategy); @@ -2617,6 +2622,12 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix) free(prefixed_path); + /* + * This entry point is always called from a submodule, so this is a + * good place to set a hint that this repo is a submodule. + */ + git_config_set("submodule.hasSuperproject", "true"); + if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force) return do_run_update_procedure(&update_data); diff --git a/submodule.c b/submodule.c index c689070524..aafbd628ad 100644 --- a/submodule.c +++ b/submodule.c @@ -2097,6 +2097,8 @@ static void relocate_single_git_dir_into_superproject(const char *path) char *old_git_dir = NULL, *real_old_git_dir = NULL, *real_new_git_dir = NULL; struct strbuf new_gitdir = STRBUF_INIT; const struct submodule *sub; + struct config_set sub_cs; + struct strbuf config_path = STRBUF_INIT, sb = STRBUF_INIT; if (submodule_uses_worktrees(path)) die(_("relocate_gitdir for submodule '%s' with " @@ -2127,6 +2129,16 @@ static void relocate_single_git_dir_into_superproject(const char *path) relocate_gitdir(path, real_old_git_dir, real_new_git_dir); + strbuf_addf(&config_path, "%s/config", real_new_git_dir); + git_configset_init(&sub_cs); + git_configset_add_file(&sub_cs, config_path.buf); + + git_config_set_in_file(config_path.buf, "submodule.hasSuperproject", + "true"); + + git_configset_clear(&sub_cs); + strbuf_release(&config_path); + strbuf_release(&sb); free(old_git_dir); free(real_old_git_dir); free(real_new_git_dir); diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 40cf8d89aa..53c8bf699d 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -115,6 +115,10 @@ inspect() { git -C "$sub_dir" rev-parse HEAD >head-sha1 && git -C "$sub_dir" update-index --refresh && git -C "$sub_dir" diff-files --exit-code && + + # Ensure that submodule.hasSuperproject is set. + test_cmp_config -C "$sub_dir" true --type=bool "submodule.hasSuperproject" + git -C "$sub_dir" clean -n -d -x >untracked } diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 11cccbb333..ec2397fc69 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -1061,4 +1061,12 @@ test_expect_success 'submodule update --quiet passes quietness to fetch with a s ) ' +test_expect_success 'submodule update adds submodule.hasSuperproject to older repos' ' + (cd super && + test_unconfig submodule.hasSuperproject && + git submodule update && + test_cmp_config -C submodule true --type=bool submodule.hasSuperproject + ) +' + test_done diff --git a/t/t7412-submodule-absorbgitdirs.sh b/t/t7412-submodule-absorbgitdirs.sh index 1cfa150768..4c33a98efa 100755 --- a/t/t7412-submodule-absorbgitdirs.sh +++ b/t/t7412-submodule-absorbgitdirs.sh @@ -30,7 +30,9 @@ test_expect_success 'absorb the git dir' ' git status >actual.1 && git -C sub1 rev-parse HEAD >actual.2 && test_cmp expect.1 actual.1 && - test_cmp expect.2 actual.2 + test_cmp expect.2 actual.2 && + + test_cmp_config -C sub1 true --type=bool submodule.hasSuperproject ' test_expect_success 'absorbing does not fail for deinitialized submodules' ' @@ -61,7 +63,9 @@ test_expect_success 'absorb the git dir in a nested submodule' ' git status >actual.1 && git -C sub1/nested rev-parse HEAD >actual.2 && test_cmp expect.1 actual.1 && - test_cmp expect.2 actual.2 + test_cmp expect.2 actual.2 && + + test_cmp_config -C sub1/nested true --type=bool submodule.hasSuperproject ' test_expect_success 're-setup nested submodule' ' @@ -130,4 +134,46 @@ test_expect_success 'absorbing fails for a submodule with multiple worktrees' ' test_i18ngrep "not supported" error ' +test_expect_success 'absorbgitdirs works when called from a superproject worktree' ' + # set up a worktree of the superproject + git worktree add wt && + ( + cd wt && + + # create a new unembedded git dir + git init sub4 && + test_commit -C sub4 first && + git submodule add ./sub4 && + test_tick && + + # absorb the git dir + git submodule absorbgitdirs sub4 && + + # make sure the submodule noted the superproject + test_cmp_config -C sub4 true --type=bool submodule.hasSuperproject + ) +' + +test_expect_success 'absorbgitdirs works with a submodule with worktree config' ' + # reuse the worktree of the superproject + ( + cd wt && + + # create a new unembedded git dir + git init sub5 && + test_commit -C sub5 first && + git submodule add ./sub5 && + test_tick && + + # turn on worktree configs for submodule + git -C sub5 config extensions.worktreeConfig true && + + # absorb the git dir + git submodule absorbgitdirs sub5 && + + # make sure the submodule noted the superproject + test_cmp_config -C sub5 true --type=bool submodule.hasSuperproject + ) +' + test_done -- 2.35.1.616.g0bdcbb4464-goog