3131#include "commit-reach.h"
3232#include "read-cache-ll.h"
3333#include "setup.h"
34+ #include "url.h"
3435
3536static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF ;
3637static int initialized_fetch_ref_tips ;
@@ -2250,16 +2251,30 @@ int submodule_move_head(const char *path, const char *super_prefix,
22502251 return ret ;
22512252}
22522253
2254+ /*
2255+ * Find the last submodule name in the gitdir path (modules can be nested).
2256+ * Returns a pointer into `path` to the beginning of the name or NULL if not found.
2257+ */
2258+ static char * find_last_submodule_name (char * git_dir_path )
2259+ {
2260+ const char * modules_marker = "/modules/" ;
2261+ char * p = git_dir_path ;
2262+ char * last = NULL ;
2263+
2264+ while ((p = strstr (p , modules_marker ))) {
2265+ last = p + strlen (modules_marker );
2266+ p ++ ;
2267+ }
2268+
2269+ return last ;
2270+ }
2271+
22532272int validate_submodule_git_dir (char * git_dir , const char * submodule_name )
22542273{
22552274 size_t len = strlen (git_dir ), suffix_len = strlen (submodule_name );
2256- char * p ;
2257- int ret = 0 ;
2258-
2259- if (len <= suffix_len || (p = git_dir + len - suffix_len )[-1 ] != '/' ||
2260- strcmp (p , submodule_name ))
2261- BUG ("submodule name '%s' not a suffix of git dir '%s'" ,
2262- submodule_name , git_dir );
2275+ char * p = git_dir + len - suffix_len ;
2276+ bool suffixes_match = !strcmp (p , submodule_name );
2277+ int ret = 0 , config_ignorecase = 0 ;
22632278
22642279 /*
22652280 * We prevent the contents of sibling submodules' git directories to
@@ -2271,7 +2286,7 @@ int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
22712286 * but the latter directory is already designated to contain the hooks
22722287 * of the former.
22732288 */
2274- for (; * p ; p ++ ) {
2289+ for (; * p && suffixes_match ; p ++ ) {
22752290 if (is_dir_sep (* p )) {
22762291 char c = * p ;
22772292
@@ -2288,6 +2303,51 @@ int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
22882303 }
22892304 }
22902305
2306+ /* tests after this check are only for encoded names, when the extension is enabled */
2307+ if (!the_repository -> repository_format_submodule_encoding )
2308+ return 0 ;
2309+
2310+ /* Prevent the use of '/' in names */
2311+ p = find_last_submodule_name (git_dir );
2312+ if (p && strchr (p , '/' ) != NULL )
2313+ return error ("submodule gitdir name '%s' contains unexpected '/'" , p );
2314+
2315+ /* Prevent conflicts on case-folding filesystems */
2316+ repo_config_get_bool (the_repository , "core.ignorecase" , & config_ignorecase );
2317+ if (ignore_case || config_ignorecase ) {
2318+ char * lower_gitdir = xstrdup (git_dir );
2319+ char * module_name = find_last_submodule_name (lower_gitdir );
2320+
2321+ if (module_name ) {
2322+ for (p = module_name ; * p ; p ++ )
2323+ * p = tolower (* p );
2324+
2325+ /*
2326+ * If lower path is different and already exists, check for collision.
2327+ * Intentionally double-check to eliminate false-positives.
2328+ */
2329+ if (strcmp (lower_gitdir , git_dir ) && is_git_directory (lower_gitdir )) {
2330+ char * canonical = real_pathdup (git_dir , 0 );
2331+ if (canonical ) {
2332+ struct strbuf norm_git_dir = STRBUF_INIT ;
2333+ strbuf_addstr (& norm_git_dir , git_dir );
2334+ strbuf_normalize_path (& norm_git_dir );
2335+
2336+ if (strcmp (canonical , norm_git_dir .buf ))
2337+ ret = error (_ ("submodule git dir '%s' "
2338+ "collides with '%s'" ),
2339+ canonical , norm_git_dir .buf );
2340+
2341+ strbuf_release (& norm_git_dir );
2342+ FREE_AND_NULL (canonical );
2343+ }
2344+ }
2345+ }
2346+
2347+ FREE_AND_NULL (lower_gitdir );
2348+ return ret ;
2349+ }
2350+
22912351 return 0 ;
22922352}
22932353
@@ -2575,29 +2635,70 @@ int submodule_to_gitdir(struct repository *repo,
25752635 return ret ;
25762636}
25772637
2638+ static int validate_and_set_submodule_gitdir (struct strbuf * gitdir_path ,
2639+ const char * submodule_name )
2640+ {
2641+ char * key ;
2642+
2643+ if (validate_submodule_git_dir (gitdir_path -> buf , submodule_name ))
2644+ return -1 ;
2645+
2646+ key = xstrfmt ("submodule.%s.gitdir" , submodule_name );
2647+ repo_config_set_gently (the_repository , key , gitdir_path -> buf );
2648+ FREE_AND_NULL (key );
2649+
2650+ return 0 ;
2651+
2652+ }
2653+
25782654void submodule_name_to_gitdir (struct strbuf * buf , struct repository * r ,
25792655 const char * submodule_name )
25802656{
2657+ const char * gitdir ;
2658+ char * key ;
2659+
2660+ repo_git_path_append (r , buf , "modules/" );
2661+ strbuf_addstr (buf , submodule_name );
2662+
2663+ /* If extensions.submoduleEncoding is disabled, use the plain path set above */
2664+ if (!r -> repository_format_submodule_encoding )
2665+ return ;
2666+
2667+ /* Extension is enabled: use the gitdir config if it exists */
2668+ key = xstrfmt ("submodule.%s.gitdir" , submodule_name );
2669+ if (!repo_config_get_string_tmp (r , key , & gitdir )) {
2670+ strbuf_reset (buf );
2671+ strbuf_addstr (buf , gitdir );
2672+ FREE_AND_NULL (key );
2673+ return ;
2674+ }
2675+ FREE_AND_NULL (key );
2676+
25812677 /*
2582- * NEEDSWORK: The current way of mapping a submodule's name to
2583- * its location in .git/modules/ has problems with some naming
2584- * schemes. For example, if a submodule is named "foo" and
2585- * another is named "foo/bar" (whether present in the same
2586- * superproject commit or not - the problem will arise if both
2587- * superproject commits have been checked out at any point in
2588- * time), or if two submodule names only have different cases in
2589- * a case-insensitive filesystem.
2590- *
2591- * There are several solutions, including encoding the path in
2592- * some way, introducing a submodule.<name>.gitdir config in
2593- * .git/config (not .gitmodules) that allows overriding what the
2594- * gitdir of a submodule would be (and teach Git, upon noticing
2595- * a clash, to automatically determine a non-clashing name and
2596- * to write such a config), or introducing a
2597- * submodule.<name>.gitdir config in .gitmodules that repo
2598- * administrators can explicitly set. Nothing has been decided,
2599- * so for now, just append the name at the end of the path.
2678+ * The gitdir config does not exist, even though the extension is enabled.
2679+ * Therefore we are in one of the following cases:
26002680 */
2681+
2682+ /* Case 1: legacy migration of valid plain submodule names */
2683+ if (!validate_and_set_submodule_gitdir (buf , submodule_name ))
2684+ return ;
2685+
2686+ /* Case 2.1: Try URI-safe (RFC3986) encoding first, this fixes nested gitdirs */
2687+ strbuf_reset (buf );
26012688 repo_git_path_append (r , buf , "modules/" );
2602- strbuf_addstr (buf , submodule_name );
2689+ strbuf_addstr_urlencode (buf , submodule_name , is_rfc3986_unreserved );
2690+ if (!validate_and_set_submodule_gitdir (buf , submodule_name ))
2691+ return ;
2692+
2693+ /* Case 2.2: Try extended uppercase URI (RFC3986) encoding, to fix case-folding */
2694+ strbuf_reset (buf );
2695+ repo_git_path_append (r , buf , "modules/" );
2696+ strbuf_addstr_urlencode (buf , submodule_name , is_casefolding_rfc3986_unreserved );
2697+ if (!validate_and_set_submodule_gitdir (buf , submodule_name ))
2698+ return ;
2699+
2700+ /* Case 3: error out */
2701+ die (_ ("Cannot construct a valid gitdir path for submodule '%s': "
2702+ "please set a unique git config for 'submodule.%s.gitdir'." ),
2703+ submodule_name , submodule_name );
26032704}
0 commit comments