Blob Blame History Raw
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