diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index cc79d059f2..29adde60bd 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -25,11 +25,32 @@ static int check_name(int argc, const char **argv, const char *prefix) return 0; } +/* + * Exit non-zero if the proposed submodule repository path is inside + * another submodules' git dir. + */ +static int validate_git_dir(int argc, const char **argv, const char *prefix) +{ + char *sm_gitdir; + + if (argc != 3) + usage("git submodule--helper validate-git-dir "); + sm_gitdir = xstrdup(argv[1]); + if (validate_submodule_git_dir(sm_gitdir, argv[2]) < 0) { + free(sm_gitdir); + return 1; + } + free(sm_gitdir); + return 0; +} + int cmd_submodule__helper(int argc, const char **argv, const char *prefix) { if (argc < 2) usage("git submodule--helper "); if (!strcmp(argv[1], "check-name")) return check_name(argc - 1, argv + 1, prefix); + if (!strcmp(argv[1], "validate-git-dir")) + return validate_git_dir(argc - 1, argv + 1, prefix); die(_("'%s' is not a valid submodule--helper subcommand"), argv[1]); } diff -ruNp a/git-submodule.sh b/git-submodule.sh --- a/git-submodule.sh 2020-01-09 20:01:48.885647299 +0100 +++ b/git-submodule.sh 2020-01-10 08:42:05.107514269 +0100 @@ -253,6 +253,11 @@ module_clone() gitdir_base="$gitdir/modules/$base_name" gitdir="$gitdir/modules/$name" + if ! git submodule--helper validate-git-dir "$gitdir" "$name" + then + die "$(eval_gettextln "refusing to create/use '\$gitdir' in another submodule's git dir")" + fi + if test -d "$gitdir" then mkdir -p "$sm_path" diff --git a/git-submodule.sh b/git-submodule.sh index ca16579c3c..bf5ffdb1f6 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -419,6 +419,11 @@ Use -f if you really want to add it." >&2 fi else + sm_gitdir=".git/modules/$sm_name" + if ! git submodule--helper validate-git-dir "$sm_gitdir" "$sm_name" + then + die "$(eval_gettextln "refusing to create/use '\$sm_gitdir' in another submodule's git dir")" + fi if test -d ".git/modules/$sm_name" then if test -z "$force" diff --git a/submodule.c b/submodule.c index 6337cab091..9927f56a33 100644 --- a/submodule.c +++ b/submodule.c @@ -1034,3 +1034,43 @@ int merge_submodule(unsigned char result[20], const char *path, free(merges.objects); return 0; } +int validate_submodule_git_dir(char *git_dir, const char *submodule_name) +{ + size_t len = strlen(git_dir), suffix_len = strlen(submodule_name); + char *p; + int ret = 0; + + if (len <= suffix_len || (p = git_dir + len - suffix_len)[-1] != '/' || + strcmp(p, submodule_name)) + die("BUG: submodule name '%s' not a suffix of git dir '%s'", + submodule_name, git_dir); + + /* + * We prevent the contents of sibling submodules' git directories to + * clash. + * + * Example: having a submodule named `hippo` and another one named + * `hippo/hooks` would result in the git directories + * `.git/modules/hippo/` and `.git/modules/hippo/hooks/`, respectively, + * but the latter directory is already designated to contain the hooks + * of the former. + */ + for (; *p; p++) { + if (is_dir_sep(*p)) { + char c = *p; + + *p = '\0'; + if (is_git_directory(git_dir)) + ret = -1; + *p = c; + + if (ret < 0) + return error(_("submodule git dir '%s' is " + "inside git dir '%.*s'"), + git_dir, + (int)(p - git_dir), git_dir); + } + } + + return 0; +} diff --git a/submodule.h b/submodule.h index 59dbdfbd17..9c99457a3f 100644 --- a/submodule.h +++ b/submodule.h @@ -37,6 +37,11 @@ int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_nam struct string_list *needs_pushing); int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name); +/* + * Make sure that no submodule's git dir is nested in a sibling submodule's. + */ +int validate_submodule_git_dir(char *git_dir, const char *submodule_name); + /* * Returns 0 if the name is syntactically acceptable as a submodule "name" * (e.g., that may be found in the subsection of a .gitmodules file) and -1 diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh index 6456d5ae43..29393de617 100755 --- a/t/t7415-submodule-names.sh +++ b/t/t7415-submodule-names.sh @@ -151,4 +151,27 @@ test_expect_success 'fsck detects symlinked .gitmodules file' ' ) ' +test_expect_success 'git dirs of sibling submodules must not be nested' ' + git init nested && + ( + cd nested && + test_commit nested && + cat >.gitmodules <<-EOF && + [submodule "hippo"] + url = . + path = thing1 + [submodule "hippo/hooks"] + url = . + path = thing2 + EOF + git clone . thing1 && + git clone . thing2 && + git add .gitmodules thing1 thing2 && + test_tick && + git commit -m nested + ) && + test_must_fail git clone --recurse-submodules nested clone 2>err && + test_i18ngrep "is inside git dir" err +' + test_done