submodule: prevent overwriting .gitmodules on path reuse

Adding a submodule at a path that previously hosted another submodule (e.g., 'child') reuses the submodule name derived from the path. If the original submodule was only moved (e.g., to 'child_old') and not renamed, this silently overwrites its configuration in .gitmodules. This behavior loses user configuration and causes confusion when the original submodule is expected to remain intact. It assumes that the path-derived name is always safe to reuse, even though the name might still be in use elsewhere in the repository. Teach module_add() to check if the computed submodule name already exists in the repository's submodule config, and if so, refuse the operation unless the user explicitly renames the submodule or uses the --force option, which will automatically generate a unique name by appending a number (e.g., child1). Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

K Jayatheerth committed Jul 24, 2025 at 20:54 UTC 1fa06ceddf1ea01bd85e277471ba79330666f037
3 files changed +56
Documentation/git-submodule.adoc
+7
@@ -307,6 +307,13 @@ OPTIONS
307 --force::
308 This option is only valid for add, deinit and update commands.
309 When running add, allow adding an otherwise ignored submodule path.
310 + This option is also used to bypass a check that the submodule's name
311 + is not already in use. By default, 'git submodule add' will fail if
312 + the proposed name (which is derived from the path) is already registered
313 + for another submodule in the repository. Using '--force' allows the command
314 + to proceed by automatically generating a unique name by appending a number
315 + to the conflicting name (e.g., if a submodule named 'child' exists, it will
316 + try 'child1', and so on).
317 When running deinit the submodule working trees will be removed even
318 if they contain local changes.
319 When running update (only effective with the checkout procedure),
builtin/submodule--helper.c
+27
@@ -3444,6 +3444,10 @@ static int module_add(int argc, const char **argv, const char *prefix,
3444 struct add_data add_data = ADD_DATA_INIT;
3445 const char *ref_storage_format = NULL;
3446 char *to_free = NULL;
3447 + const struct submodule *existing;
3448 + struct strbuf buf = STRBUF_INIT;
3449 + int i;
3450 + char *sm_name_to_free = NULL;
3451 struct option options[] = {
3452 OPT_STRING('b', "branch", &add_data.branch, N_("branch"),
3453 N_("branch of repository to add as submodule")),
@@ -3546,6 +3550,28 @@ static int module_add(int argc, const char **argv, const char *prefix,
3550 if(!add_data.sm_name)
3551 add_data.sm_name = add_data.sm_path;
3552
3553 + existing = submodule_from_name(the_repository,
3554 + null_oid(the_hash_algo),
3555 + add_data.sm_name);
3556 +
3557 + if (existing && strcmp(existing->path, add_data.sm_path)) {
3558 + if (!force) {
3559 + die(_("submodule name '%s' already used for path '%s'"),
3560 + add_data.sm_name, existing->path);
3561 + }
3562 + /* --force: build <name><n> until unique */
3563 + for (i = 1; ; i++) {
3564 + strbuf_reset(&buf);
3565 + strbuf_addf(&buf, "%s%d", add_data.sm_name, i);
3566 + if (!submodule_from_name(the_repository,
3567 + null_oid(the_hash_algo),
3568 + buf.buf)) {
3569 + break;
3570 + }
3571 + }
3572 + add_data.sm_name = sm_name_to_free = strbuf_detach(&buf, NULL);
3573 + }
3574 +
3575 if (check_submodule_name(add_data.sm_name))
3576 die(_("'%s' is not a valid submodule name"), add_data.sm_name);
3577
@@ -3561,6 +3587,7 @@ static int module_add(int argc, const char **argv, const char *prefix,
3587
3588 ret = 0;
3589 cleanup:
3590 + free(sm_name_to_free);
3591 free(add_data.sm_path);
3592 free(to_free);
3593 strbuf_release(&sb);
t/t7400-submodule-basic.sh
+22
@@ -1482,4 +1482,26 @@ test_expect_success '`submodule init` and `init.templateDir`' '
1482 )
1483 '
1484
1485 +test_expect_success 'submodule add fails when name is reused' '
1486 + git init test-submodule &&
1487 + (
1488 + cd test-submodule &&
1489 + git commit --allow-empty -m init &&
1490 +
1491 + git init ../child-origin &&
1492 + git -C ../child-origin commit --allow-empty -m init &&
1493 +
1494 + git submodule add ../child-origin child &&
1495 + git commit -m "Add submodule child" &&
1496 +
1497 + git mv child child_old &&
1498 + git commit -m "Move child to child_old" &&
1499 +
1500 + # Now adding a *new* repo at the old name must fail
1501 + git init ../child2-origin &&
1502 + git -C ../child2-origin commit --allow-empty -m init &&
1503 + test_must_fail git submodule add ../child2-origin child
1504 + )
1505 +'
1506 +
1507 test_done