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 <path> <name>");
+ 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 <command>");
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