Subject: [PATCH v8 00/11] Add submodulePathConfig extension and gitdir encoding

From: Adrian Ratiu <[email-masked]>
Date: Thu, 8 Jan 2026 01:01:34 +0200

Hello everyone,

For those new to the series, we're implementing a submodule gitdir
extension which allows us to have a unified way to determine gitdirs
and do things like encode submodule paths to avoid FS conflicts.

v8 addresses nits and small code improvements, no more design changes
as the series is quieting down. Most of the range-diff is whitespace
fixes this time. :)

Patches 1-6 implement the basic mechanisms of the new extension.
Patches 7-11 improve filesystem conflict detection and resolution.

As always, this is based on the latest master branch, I've checkd
for conflicts with next/seen, pushed to Github [1] and succesfully
ran the CI [2].

1: https://github.com/10ne1/git/tree/dev/aratiu/encoding-v8
2: https://github.com/10ne1/git/actions/runs/20798198092

Changes in v8:
* Added a new test to ensure gitdir config path is relative (Patrick)
* Improved gitdir validation error message and added advice (Patrick)
* Renamed init.autoSetupSubmodulePathConfig to init.defaultSubmodulePathConfig
  and moved its init logic to initialize_repository_version() (Patrick)
* repositoryformatversion is only set to 1 if it's 0, so it doesn't
  overwrite potential future higher versions (Patrick)
* Fixed global init.defaultSubmodulePathConfig leak between tests (Adrian)
* Whitespace and other minor fixes (Junio, Patrick)

Range-diff between v7 -> v8:
 1:  87bdd023f8 =  1:  5aae0df74b submodule--helper: use submodule_name_to_gitdir in add_submodule
 2:  8ea022eb6f =  2:  041d921487 submodule: always validate gitdirs inside submodule_name_to_gitdir
 3:  2092faf443 =  3:  12ff77be2d builtin/submodule--helper: add gitdir command
 4:  b72e8d3610 !  4:  faaea085d2 submodule: introduce extensions.submodulePathConfig
    @@ Documentation/config/extensions.adoc: relativeWorktrees:::
     +	This extension is for the minority of users who:
     ++
     +--
    -+* Encounter errors like	`refusing to create ... in another submodule's git dir`
    ++* Encounter errors like `refusing to create ... in another submodule's git dir`
     +  due to a number of reasons, like case-insensitive filesystem conflicts when
     +  creating modules named `foo` and `Foo`.
     +* Require more flexible submodule layouts, for example due to nested names like
    @@ Documentation/config/submodule.adoc: submodule.<name>.active::
     +	This sets the gitdir path for submodule <name>. This configuration is
     +	respected when `extensions.submodulePathConfig` is enabled, otherwise it
     +	has no effect. When enabled, this config becomes the single source of
    -+	truth for submodule gitdir paths and git will error if it is missing.
    ++	truth for submodule gitdir paths and Git will error if it is missing.
     +	See linkgit:git-config[1] for details.
     +
      submodule.active::
    @@ builtin/submodule--helper.c: struct init_cb {
     +	if (validate_submodule_git_dir(gitdir_path->buf, submodule_name))
     +		return -1;
     +
    -+	 key = xstrfmt("submodule.%s.gitdir", submodule_name);
    ++	key = xstrfmt("submodule.%s.gitdir", submodule_name);
     +
     +	/* Nothing to do if the config already exists. */
     +	if (!repo_config_get_string_tmp(the_repository, key, &value)) {
    @@ setup.h: struct repository_format {
      	int compat_hash_algo;
     
      ## submodule.c ##
    +@@
    + #include "commit-reach.h"
    + #include "read-cache-ll.h"
    + #include "setup.h"
    ++#include "advice.h"
    + 
    + static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
    + static int initialized_fetch_ref_tips;
     @@ submodule.c: int submodule_move_head(const char *path, const char *super_prefix,
      				if (validate_submodule_git_dir(git_dir,
      							       sub->name) < 0)
    @@ submodule.c: int submodule_to_gitdir(struct repository *repo,
     +		const char *gitdir;
     +		char *key;
     +		int ret;
    -+
    + 
    +-	if (validate_submodule_git_dir(buf->buf, submodule_name) < 0)
     +		/* Otherwise the extension is enabled, so use the gitdir config. */
     +		key = xstrfmt("submodule.%s.gitdir", submodule_name);
     +		ret = repo_config_get_string_tmp(r, key, &gitdir);
    @@ submodule.c: int submodule_to_gitdir(struct repository *repo,
     +
     +		strbuf_addstr(buf, gitdir);
     +	}
    - 
    --	if (validate_submodule_git_dir(buf->buf, submodule_name) < 0)
    --		die(_("refusing to create/use '%s' in another submodule's "
    --		      "git dir"), buf->buf);
    ++
     +	/* validate because users might have modified the config */
    -+	if (validate_submodule_git_dir(buf->buf, submodule_name))
    -+		die(_("invalid 'submodule.%s.gitdir' config: '%s' please check "
    -+		      "if it is unique or conflicts with another module"),
    -+		    submodule_name, buf->buf);
    ++	if (validate_submodule_git_dir(buf->buf, submodule_name)) {
    ++		advise(_("enabling extensions.submodulePathConfig might fix the "
    ++			 "following error, if it's not already enabled."));
    + 		die(_("refusing to create/use '%s' in another submodule's "
    +-		      "git dir"), buf->buf);
    ++		      " git dir."), buf->buf);
    ++	}
      }
     
      ## t/lib-verify-submodule-gitdir-path.sh (new) ##
    @@ t/lib-verify-submodule-gitdir-path.sh (new)
     +# might or might not exist (e.g. when adding a new submodule), so this only
     +# checks the expected configuration path, which might be overridden by the user.
     +
    -+verify_submodule_gitdir_path() {
    ++verify_submodule_gitdir_path () {
     +	repo="$1" &&
     +	name="$2" &&
     +	path="$3" &&
    @@ t/t7425-submodule-gitdir-path-extension.sh (new)
     +'
     +
     +test_expect_success 'verify new submodule gitdir config' '
    -+	git -C main config submodule."New Sub".gitdir > actual &&
    -+	echo ".git/modules/New Sub" > expect &&
    ++	git -C main config submodule."New Sub".gitdir >actual &&
    ++	echo ".git/modules/New Sub" >expect &&
     +	test_cmp expect actual &&
     +	verify_submodule_gitdir_path main "New Sub" "modules/New Sub"
     +'
    @@ t/t7425-submodule-gitdir-path-extension.sh (new)
     +	# was added before the extension was enabled. Add and test it.
     +	test_must_fail git -C main config submodule.legacy.gitdir &&
     +	git -C main config submodule.legacy.gitdir .git/modules/legacy &&
    -+	git -C main config submodule.legacy.gitdir > actual &&
    -+	echo ".git/modules/legacy" > expect &&
    ++	git -C main config submodule.legacy.gitdir >actual &&
    ++	echo ".git/modules/legacy" >expect &&
     +	test_cmp expect actual &&
     +	verify_submodule_gitdir_path main "legacy" "modules/legacy"
     +'
     +
    ++test_expect_success 'gitdir config path is relative for both absolute and relative urls' '
    ++	test_when_finished "rm -rf relative-cfg-path-test" &&
    ++	git init -b main relative-cfg-path-test &&
    ++	(
    ++		cd relative-cfg-path-test &&
    ++		git config core.repositoryformatversion 1 &&
    ++		git config extensions.submodulePathConfig true &&
    ++
    ++		# Test with absolute URL
    ++		git submodule add "$TRASH_DIRECTORY/new-sub" sub-abs &&
    ++		git config submodule.sub-abs.gitdir >actual &&
    ++		echo ".git/modules/sub-abs" >expect &&
    ++		test_cmp expect actual &&
    ++
    ++		# Test with relative URL
    ++		git submodule add ../new-sub sub-rel &&
    ++		git config submodule.sub-rel.gitdir >actual &&
    ++		echo ".git/modules/sub-rel" >expect &&
    ++		test_cmp expect actual
    ++	)
    ++'
    ++
     +test_expect_success 'clone from repo with both legacy and new-style submodules' '
     +	git clone --recurse-submodules main cloned-non-extension &&
     +	(
 5:  8017e1ca16 !  5:  3a65c86a38 submodule: allow runtime enabling extensions.submodulePathConfig
    @@ Metadata
      ## Commit message ##
         submodule: allow runtime enabling extensions.submodulePathConfig
     
    -    Add a new config `init.autoSetupSubmodulePathConfig` which allows
    +    Add a new config `init.defaultSubmodulePathConfig` which allows
         enabling `extensions.submodulePathConfig` for new submodules by
         default (those created via git init or clone).
     
    -    Important: setting init.autoSetupSubmodulePathConfig = true does
    +    Important: setting init.defaultSubmodulePathConfig = true does
         not globally enable `extensions.submodulePathConfig`. Existing
         repositories will still have the extension disabled and will
         require migration (for example via git submodule--helper command
    @@ Documentation/config/extensions.adoc: Git will error out if a module does not ha
      `git config submodule.<name>.gitdir .git/modules/<name>`.
     ++
     +The extension can be enabled automatically for new repositories by setting
    -+`init.autoSetupSubmodulePathConfig` to `true`, for example by running
    -+`git config --global init.autoSetupSubmodulePathConfig true`.
    ++`init.defaultSubmodulePathConfig` to `true`, for example by running
    ++`git config --global init.defaultSubmodulePathConfig true`.
      
      worktreeConfig:::
      	If enabled, then worktrees will load config settings from the
    @@ Documentation/config/init.adoc: endif::[]
      	option and the `GIT_DEFAULT_REF_FORMAT` environment variable take
      	precedence over this config.
     +
    -+init.autoSetupSubmodulePathConfig::
    ++init.defaultSubmodulePathConfig::
     +	A boolean that specifies if `git init` and `git clone` should
     +	automatically set `extensions.submodulePathConfig` to `true`. This
     +	allows all new repositories to automatically use the submodule path
     +	extension. Defaults to `false` when unset.
     
      ## setup.c ##
    -@@ setup.c: int init_db(const char *git_dir, const char *real_git_dir,
    - 	    const char *initial_branch,
    - 	    int init_shared_repository, unsigned int flags)
    +@@ setup.c: void initialize_repository_version(int hash_algo,
      {
    --	int reinit;
    -+	int reinit, auto_setup_submodule_path_config = 0;
    - 	int exist_ok = flags & INIT_DB_EXIST_OK;
    - 	char *original_git_dir = real_pathdup(git_dir, 1);
    - 	struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
    -@@ setup.c: int init_db(const char *git_dir, const char *real_git_dir,
    - 					  initial_branch, flags & INIT_DB_QUIET);
    - 	create_object_directory();
    + 	struct strbuf repo_version = STRBUF_INIT;
    + 	int target_version = GIT_REPO_VERSION;
    ++	int default_submodule_path_config = 0;
      
    -+	repo_config_get_bool(the_repository, "init.autoSetupSubmodulePathConfig",
    -+			     &auto_setup_submodule_path_config);
    -+	if (auto_setup_submodule_path_config) {
    -+		int version = 0;
    -+		repo_config_get_int(the_repository, "core.repositoryformatversion", &version);
    -+		if (version < 1)
    -+			repo_config_set(the_repository, "core.repositoryformatversion", "1");
    + 	/*
    + 	 * Note that we initialize the repository version to 1 when the ref
    +@@ setup.c: void initialize_repository_version(int hash_algo,
    + 		clear_repository_format(&repo_fmt);
    + 	}
    + 
    ++	repo_config_get_bool(the_repository, "init.defaultSubmodulePathConfig",
    ++			     &default_submodule_path_config);
    ++	if (default_submodule_path_config) {
    ++		/* extensions.submodulepathconfig requires at least version 1 */
    ++		if (target_version == 0)
    ++			target_version = 1;
     +		repo_config_set(the_repository, "extensions.submodulepathconfig", "true");
     +	}
     +
    - 	if (repo_settings_get_shared_repository(the_repository)) {
    - 		char buf[10];
    - 		/* We do not spell "group" and such, so that
    + 	strbuf_addf(&repo_version, "%d", target_version);
    + 	repo_config_set(the_repository, "core.repositoryformatversion", repo_version.buf);
    + 
     
      ## t/t7425-submodule-gitdir-path-extension.sh ##
     @@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success 'fetch mixed submodule changes and verify updates' '
      	)
      '
      
    -+test_expect_success '`git init` respects init.autoSetupSubmodulePathConfig' '
    -+	git config --global init.autoSetupSubmodulePathConfig true &&
    ++test_expect_success '`git init` respects init.defaultSubmodulePathConfig' '
    ++	git config --global init.defaultSubmodulePathConfig true &&
     +	git init repo-init &&
     +	git -C repo-init config extensions.submodulePathConfig > actual &&
     +	echo true > expect &&
    @@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success 'fetch mixed sub
     +		echo ".git/modules/sub" > expect &&
     +		test_cmp expect actual
     +	) &&
    -+	git config --global --unset init.autoSetupSubmodulePathConfig
    ++	git config --global --unset init.defaultSubmodulePathConfig
     +'
     +
     +test_expect_success '`git init` does not set extension by default' '
    @@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success 'fetch mixed sub
     +
     +'
     +
    -+test_expect_success '`git clone` respects init.autoSetupSubmodulePathConfig' '
    ++test_expect_success '`git clone` respects init.defaultSubmodulePathConfig' '
     +	test_when_finished "rm -rf repo-clone" &&
    -+	git config --global init.autoSetupSubmodulePathConfig true &&
    ++	git config --global init.defaultSubmodulePathConfig true &&
     +	git clone upstream repo-clone &&
     +	(
     +		cd repo-clone &&
    @@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success 'fetch mixed sub
     +		echo ".git/modules/sub" > expect &&
     +		test_cmp expect actual
     +	) &&
    -+	git config --global --unset init.autoSetupSubmodulePathConfig
    ++	git config --global --unset init.defaultSubmodulePathConfig
     +'
     +
    -+test_expect_success '`git clone --recurse-submodules` respects init.autoSetupSubmodulePathConfig' '
    ++test_expect_success '`git clone --recurse-submodules` respects init.defaultSubmodulePathConfig' '
     +	test_when_finished "rm -rf repo-clone-recursive" &&
    -+	git config --global init.autoSetupSubmodulePathConfig true &&
    ++	git config --global init.defaultSubmodulePathConfig true &&
     +	git clone  --recurse-submodules upstream repo-clone-recursive &&
     +	(
     +		cd repo-clone-recursive &&
    @@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success 'fetch mixed sub
     +		echo ".git/modules/new-sub" > expect &&
     +		test_cmp expect actual
     +	) &&
    -+	git config --global --unset init.autoSetupSubmodulePathConfig
    ++	git config --global --unset init.defaultSubmodulePathConfig
     +'
     +
      test_done
 6:  ce96f85456 !  6:  c62db6b32f submodule--helper: add gitdir migration command
    @@ Documentation/config/extensions.adoc: Git will error out if a module does not ha
     +submodules and attempts to migrate them.
      +
      The extension can be enabled automatically for new repositories by setting
    - `init.autoSetupSubmodulePathConfig` to `true`, for example by running
    + `init.defaultSubmodulePathConfig` to `true`, for example by running
     
      ## builtin/submodule--helper.c ##
     @@ builtin/submodule--helper.c: static int module_gitdir(int argc, const char **argv, const char *prefix UNUSED,
    @@ builtin/submodule--helper.c: static int module_gitdir(int argc, const char **arg
     +	struct strbuf module_dir = STRBUF_INIT;
     +	DIR *dir;
     +	struct dirent *de;
    ++	int repo_version = 0;
     +
     +	repo_git_path_append(repo, &module_dir, "modules/");
     +
    @@ builtin/submodule--helper.c: static int module_gitdir(int argc, const char **arg
     +	closedir(dir);
     +	strbuf_release(&module_dir);
     +
    -+	if (repo_config_set_gently(repo, "core.repositoryformatversion", "1"))
    ++	repo_config_get_int(the_repository, "core.repositoryformatversion", &repo_version);
    ++	if (repo_version == 0 &&
    ++	    repo_config_set_gently(repo, "core.repositoryformatversion", "1"))
     +		die(_("could not set core.repositoryformatversion to 1. "
    -+		      "Please enable it for migration to work, for example: "
    ++		      "Please set it for migration to work, for example: "
     +		      "git config core.repositoryformatversion 1"));
     +
     +	if (repo_config_set_gently(repo, "extensions.submodulePathConfig", "true"))
    @@ builtin/submodule--helper.c: int cmd_submodule__helper(int argc,
      		OPT_SUBCOMMAND("add", &fn, module_add),
     
      ## t/t7425-submodule-gitdir-path-extension.sh ##
    -@@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success '`git clone --recurse-submodules` respects init.autoSetupSub
    - 	git config --global --unset init.autoSetupSubmodulePathConfig
    +@@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success 'fetch mixed submodule changes and verify updates' '
    + test_expect_success '`git init` respects init.defaultSubmodulePathConfig' '
    + 	git config --global init.defaultSubmodulePathConfig true &&
    + 	git init repo-init &&
    +-	git -C repo-init config extensions.submodulePathConfig > actual &&
    +-	echo true > expect &&
    ++	git -C repo-init config extensions.submodulePathConfig >actual &&
    ++	echo true >expect &&
    + 	test_cmp expect actual &&
    + 	# create a submodule and check gitdir
    + 	(
    +@@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success '`git init` respects init.defaultSubmodulePathConfig' '
    + 		git init -b main sub &&
    + 		test_commit -C sub sub-initial &&
    + 		git submodule add ./sub sub &&
    +-		git config submodule.sub.gitdir > actual &&
    +-		echo ".git/modules/sub" > expect &&
    ++		git config submodule.sub.gitdir >actual &&
    ++		echo ".git/modules/sub" >expect &&
    + 		test_cmp expect actual
    + 	) &&
    + 	git config --global --unset init.defaultSubmodulePathConfig
    +@@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success '`git clone` respects init.defaultSubmodulePathConfig' '
    + 		cd repo-clone &&
    + 
    + 		# verify new repo extension is inherited from global config
    +-		git config extensions.submodulePathConfig > actual &&
    +-		echo true > expect &&
    ++		git config extensions.submodulePathConfig >actual &&
    ++		echo true >expect &&
    + 		test_cmp expect actual &&
    + 
    + 		# new submodule has a gitdir config
    + 		git submodule add ../sub sub &&
    + 		test_path_is_dir .git/modules/sub &&
    +-		git config submodule.sub.gitdir > actual &&
    +-		echo ".git/modules/sub" > expect &&
    ++		git config submodule.sub.gitdir >actual &&
    ++		echo ".git/modules/sub" >expect &&
    + 		test_cmp expect actual
    + 	) &&
    + 	git config --global --unset init.defaultSubmodulePathConfig
    +@@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success '`git clone --recurse-submodules` respects init.defaultSubmo
    + 		cd repo-clone-recursive &&
    + 
    + 		# verify new repo extension is inherited from global config
    +-		git config extensions.submodulePathConfig > actual &&
    +-		echo true > expect &&
    ++		git config extensions.submodulePathConfig >actual &&
    ++		echo true >expect &&
    + 		test_cmp expect actual &&
    + 
    + 		# previous submodules should exist
    +@@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success '`git clone --recurse-submodules` respects init.defaultSubmo
    + 		# create another submodule and check that gitdir is created
    + 		git submodule add ../sub new-sub &&
    + 		test_path_is_dir .git/modules/new-sub &&
    +-		git config submodule.new-sub.gitdir > actual &&
    +-		echo ".git/modules/new-sub" > expect &&
    ++		git config submodule.new-sub.gitdir >actual &&
    ++		echo ".git/modules/new-sub" >expect &&
    + 		test_cmp expect actual
    + 	) &&
    + 	git config --global --unset init.defaultSubmodulePathConfig
      '
      
     +test_expect_success 'submodule--helper migrates legacy modules' '
    @@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success '`git clone --re
     +		test_cmp expect actual &&
     +
     +		# repository extension is enabled after migration
    -+		git config extensions.submodulePathConfig > actual &&
    -+		echo "true" > expect &&
    ++		git config extensions.submodulePathConfig >actual &&
    ++		echo "true" >expect &&
     +		test_cmp expect actual
     +	)
     +'
    @@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success '`git clone --re
     +	(
     +		cd repo-clone-recursive &&
     +
    -+		# init.autoSetupSubmodulePathConfig was disabled before clone, so
    ++		# init.defaultSubmodulePathConfig was disabled before clone, so
     +		# the repo extension config should also be off, the migration ignored
     +		test_must_fail git config extensions.submodulePathConfig &&
     +
    @@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success '`git clone --re
     +	rm -rf repo-clone-recursive &&
     +
     +	# enable the extension, then retry the clone
    -+	git config --global init.autoSetupSubmodulePathConfig true &&
    ++	git config --global init.defaultSubmodulePathConfig true &&
     +	git clone --recurse-submodules upstream repo-clone-recursive &&
     +	(
     +		cd repo-clone-recursive &&
     +
     +		# repository extension is enabled
    -+		git config extensions.submodulePathConfig > actual &&
    -+		echo "true" > expect &&
    ++		git config extensions.submodulePathConfig >actual &&
    ++		echo "true" >expect &&
     +		test_cmp expect actual &&
     +
     +		# gitdir configs exist for submodules
    @@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success '`git clone --re
     +		git config submodule.sub2.gitdir &&
     +		test_path_is_dir .git/modules/sub1 &&
     +		test_path_is_dir .git/modules/sub2
    -+	)
    ++	) &&
    ++	git config --global --unset init.defaultSubmodulePathConfig
     +'
     +
      test_done
 7:  2f96ff248f =  7:  c554017f83 builtin/credential-store: move is_rfc3986_unreserved to url.[ch]
 8:  5aa986bd40 !  8:  7a794b9b61 submodule--helper: fix filesystem collisions by encoding gitdir paths
    @@ builtin/submodule--helper.c: static void create_default_gitdir_config(const char
     
      ## submodule.c ##
     @@
    - #include "commit-reach.h"
      #include "read-cache-ll.h"
      #include "setup.h"
    + #include "advice.h"
     +#include "url.h"
      
      static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
    @@ submodule.c: int validate_submodule_git_dir(char *git_dir, const char *submodule
     
      ## t/t7425-submodule-gitdir-path-extension.sh ##
     @@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success '`git clone --recurse-submodules` works after migration' '
    - 	)
    + 	git config --global --unset init.defaultSubmodulePathConfig
      '
      
     +test_expect_success 'setup submodules with nested git dirs' '
    @@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success '`git clone --re
     +	git clone -c extensions.submodulePathConfig=true --recurse-submodules nested clone_nested &&
     +
     +	verify_submodule_gitdir_path clone_nested hippo modules/hippo &&
    -+	git -C clone_nested config submodule.hippo.gitdir > actual &&
    ++	git -C clone_nested config submodule.hippo.gitdir >actual &&
     +	test_grep "\.git/modules/hippo$" actual &&
     +
     +	verify_submodule_gitdir_path clone_nested hippo/hooks modules/hippo%2fhooks &&
    -+	git -C clone_nested config submodule.hippo/hooks.gitdir > actual &&
    ++	git -C clone_nested config submodule.hippo/hooks.gitdir >actual &&
     +	test_grep "\.git/modules/hippo%2fhooks$" actual
     +'
     +
    @@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success '`git clone --re
     +	git clone -c extensions.submodulePathConfig=true --recurse-submodules --jobs=2 nested clone_parallel &&
     +
     +	verify_submodule_gitdir_path clone_parallel hippo modules/hippo &&
    -+	git -C clone_nested config submodule.hippo.gitdir > actual &&
    ++	git -C clone_nested config submodule.hippo.gitdir >actual &&
     +	test_grep "\.git/modules/hippo$" actual &&
     +
     +	verify_submodule_gitdir_path clone_parallel hippo/hooks modules/hippo%2fhooks &&
    -+	git -C clone_nested config submodule.hippo/hooks.gitdir > actual &&
    ++	git -C clone_nested config submodule.hippo/hooks.gitdir >actual &&
     +	test_grep "\.git/modules/hippo%2fhooks$" actual
     +'
     +
 9:  044d7de8a0 =  9:  142a85a1af submodule: fix case-folding gitdir filesystem collisions
10:  90d78b23cd = 10:  bafde20354 submodule: hash the submodule name for the gitdir path
11:  4e596a2a90 ! 11:  e3fe1f7529 submodule: detect conflicts with existing gitdir configs
    @@ submodule.c: static int check_casefolding_conflict(const char *git_dir,
     +
     +	return cb->conflict_found;
     +}
    ++
     +static bool submodule_conflicts_with_existing(const char *gitdir, const char *submodule_name)
     +{
     +	struct submodule_from_gitdir_cb cb = { 0 };
    @@ t/t7425-submodule-gitdir-path-extension.sh: test_expect_success CASE_INSENSITIVE
     +	(
     +		cd super_with_encoded &&
     +
    ++		git config core.repositoryformatversion 1 &&
     +		git config extensions.submodulePathConfig true &&
     +
     +		# Add a submodule with a nested path

Adrian Ratiu (11):
  submodule--helper: use submodule_name_to_gitdir in add_submodule
  submodule: always validate gitdirs inside submodule_name_to_gitdir
  builtin/submodule--helper: add gitdir command
  submodule: introduce extensions.submodulePathConfig
  submodule: allow runtime enabling extensions.submodulePathConfig
  submodule--helper: add gitdir migration command
  builtin/credential-store: move is_rfc3986_unreserved to url.[ch]
  submodule--helper: fix filesystem collisions by encoding gitdir paths
  submodule: fix case-folding gitdir filesystem collisions
  submodule: hash the submodule name for the gitdir path
  submodule: detect conflicts with existing gitdir configs

 Documentation/config/extensions.adoc       |  29 ++
 Documentation/config/init.adoc             |   6 +
 Documentation/config/submodule.adoc        |   7 +
 builtin/credential-store.c                 |   7 +-
 builtin/submodule--helper.c                | 204 +++++++-
 repository.c                               |   1 +
 repository.h                               |   1 +
 setup.c                                    |  17 +
 setup.h                                    |   1 +
 submodule.c                                | 223 +++++++--
 t/lib-verify-submodule-gitdir-path.sh      |  24 +
 t/meson.build                              |   1 +
 t/t7425-submodule-gitdir-path-extension.sh | 532 +++++++++++++++++++++
 t/t9902-completion.sh                      |   1 +
 url.c                                      |  13 +
 url.h                                      |  14 +
 16 files changed, 1030 insertions(+), 51 deletions(-)
 create mode 100644 t/lib-verify-submodule-gitdir-path.sh
 create mode 100755 t/t7425-submodule-gitdir-path-extension.sh

-- 
2.51.2