submodule: hash the submodule name for the gitdir path
If none of the previous plain-text / encoding / derivation steps work and case 2.4 is reached, then try a hash of the submodule name to see if that can be a valid gitdir before giving up and throwing an error. This is a "last resort" type of measure to avoid conflicts since it loses the human readability of the gitdir path. This logic will be reached in rare cases, as can be seen in the test we added. Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Adrian Ratiu committed
Jan 12, 2026 at 20:46 UTC
82c36fa0a987c9c8617f5ded41834f7487e616e2
2 files changed
+78
builtin/submodule--helper.c
+19
@@ -465,6 +465,10 @@ static int validate_and_set_submodule_gitdir(struct strbuf *gitdir_path,
465
static void create_default_gitdir_config(const char *submodule_name)
466
{
467
struct strbuf gitdir_path = STRBUF_INIT;
468
+ struct git_hash_ctx ctx;
469
+ char hex_name_hash[GIT_MAX_HEXSZ + 1], header[128];
470
+ unsigned char raw_name_hash[GIT_MAX_RAWSZ];
471
+ int header_len;
472
473
/* Case 1: try the plain module name */
474
repo_git_path_append(the_repository, &gitdir_path, "modules/%s", submodule_name);
@@ -506,6 +510,21 @@ static void create_default_gitdir_config(const char *submodule_name)
510
return;
511
}
512
513
+ /* Case 2.4: If all the above failed, try a hash of the name as a last resort */
514
+ header_len = snprintf(header, sizeof(header), "blob %zu", strlen(submodule_name));
515
+ the_hash_algo->init_fn(&ctx);
516
+ the_hash_algo->update_fn(&ctx, header, header_len);
517
+ the_hash_algo->update_fn(&ctx, "\0", 1);
518
+ the_hash_algo->update_fn(&ctx, submodule_name, strlen(submodule_name));
519
+ the_hash_algo->final_fn(raw_name_hash, &ctx);
520
+ hash_to_hex_algop_r(hex_name_hash, raw_name_hash, the_hash_algo);
521
+ strbuf_reset(&gitdir_path);
522
+ repo_git_path_append(the_repository, &gitdir_path, "modules/%s", hex_name_hash);
523
+ if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) {
524
+ strbuf_release(&gitdir_path);
525
+ return;
526
+ }
527
+
528
/* Case 3: nothing worked, error out */
529
die(_("failed to set a valid default config for 'submodule.%s.gitdir'. "
530
"Please ensure it is set, for example by running something like: "
t/t7425-submodule-gitdir-path-extension.sh
+59
@@ -438,4 +438,63 @@ test_expect_success CASE_INSENSITIVE_FS 'verify case-folding conflicts are corre
438
verify_submodule_gitdir_path cloned-folding "fooBar" "modules/fooBar0"
439
'
440
441
+test_expect_success CASE_INSENSITIVE_FS 'verify hashing conflict resolution as a last resort' '
442
+ git clone -c extensions.submodulePathConfig=true main cloned-hash &&
443
+ (
444
+ cd cloned-hash &&
445
+
446
+ # conflict: add all submodule conflicting variants until we reach the
447
+ # final hashing conflict resolution for submodule "foo"
448
+ git submodule add ../new-sub "foo" &&
449
+ git submodule add ../new-sub "foo0" &&
450
+ git submodule add ../new-sub "foo1" &&
451
+ git submodule add ../new-sub "foo2" &&
452
+ git submodule add ../new-sub "foo3" &&
453
+ git submodule add ../new-sub "foo4" &&
454
+ git submodule add ../new-sub "foo5" &&
455
+ git submodule add ../new-sub "foo6" &&
456
+ git submodule add ../new-sub "foo7" &&
457
+ git submodule add ../new-sub "foo8" &&
458
+ git submodule add ../new-sub "foo9" &&
459
+ git submodule add ../new-sub "%46oo" &&
460
+ git submodule add ../new-sub "%46oo0" &&
461
+ git submodule add ../new-sub "%46oo1" &&
462
+ git submodule add ../new-sub "%46oo2" &&
463
+ git submodule add ../new-sub "%46oo3" &&
464
+ git submodule add ../new-sub "%46oo4" &&
465
+ git submodule add ../new-sub "%46oo5" &&
466
+ git submodule add ../new-sub "%46oo6" &&
467
+ git submodule add ../new-sub "%46oo7" &&
468
+ git submodule add ../new-sub "%46oo8" &&
469
+ git submodule add ../new-sub "%46oo9" &&
470
+ test_commit add-foo-variants &&
471
+ git submodule add ../new-sub "Foo" &&
472
+ test_commit add-uppercase-foo
473
+ ) &&
474
+ verify_submodule_gitdir_path cloned-hash "foo" "modules/foo" &&
475
+ verify_submodule_gitdir_path cloned-hash "foo0" "modules/foo0" &&
476
+ verify_submodule_gitdir_path cloned-hash "foo1" "modules/foo1" &&
477
+ verify_submodule_gitdir_path cloned-hash "foo2" "modules/foo2" &&
478
+ verify_submodule_gitdir_path cloned-hash "foo3" "modules/foo3" &&
479
+ verify_submodule_gitdir_path cloned-hash "foo4" "modules/foo4" &&
480
+ verify_submodule_gitdir_path cloned-hash "foo5" "modules/foo5" &&
481
+ verify_submodule_gitdir_path cloned-hash "foo6" "modules/foo6" &&
482
+ verify_submodule_gitdir_path cloned-hash "foo7" "modules/foo7" &&
483
+ verify_submodule_gitdir_path cloned-hash "foo8" "modules/foo8" &&
484
+ verify_submodule_gitdir_path cloned-hash "foo9" "modules/foo9" &&
485
+ verify_submodule_gitdir_path cloned-hash "%46oo" "modules/%46oo" &&
486
+ verify_submodule_gitdir_path cloned-hash "%46oo0" "modules/%46oo0" &&
487
+ verify_submodule_gitdir_path cloned-hash "%46oo1" "modules/%46oo1" &&
488
+ verify_submodule_gitdir_path cloned-hash "%46oo2" "modules/%46oo2" &&
489
+ verify_submodule_gitdir_path cloned-hash "%46oo3" "modules/%46oo3" &&
490
+ verify_submodule_gitdir_path cloned-hash "%46oo4" "modules/%46oo4" &&
491
+ verify_submodule_gitdir_path cloned-hash "%46oo5" "modules/%46oo5" &&
492
+ verify_submodule_gitdir_path cloned-hash "%46oo6" "modules/%46oo6" &&
493
+ verify_submodule_gitdir_path cloned-hash "%46oo7" "modules/%46oo7" &&
494
+ verify_submodule_gitdir_path cloned-hash "%46oo8" "modules/%46oo8" &&
495
+ verify_submodule_gitdir_path cloned-hash "%46oo9" "modules/%46oo9" &&
496
+ hash=$(printf "Foo" | git hash-object --stdin) &&
497
+ verify_submodule_gitdir_path cloned-hash "Foo" "modules/${hash}"
498
+'
499
+
500
test_done