]> git.ipfire.org Git - thirdparty/git.git/commitdiff
submodule: prevent overwriting .gitmodules entry on path reuse
authorK Jayatheerth <jayatheerthkulkarni2005@gmail.com>
Sun, 8 Jun 2025 03:27:04 +0000 (08:57 +0530)
committerJunio C Hamano <gitster@pobox.com>
Sun, 8 Jun 2025 20:35:50 +0000 (13:35 -0700)
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 or uses force to auto increment.

Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin/submodule--helper.c
t/t7400-submodule-basic.sh

index 53da2116ddf576bc565b29f043e8b703b8b1563b..9f6df833f0095c8c08b642d8105611ed3bb0cc50 100644 (file)
@@ -3444,6 +3444,10 @@ static int module_add(int argc, const char **argv, const char *prefix,
        struct add_data add_data = ADD_DATA_INIT;
        const char *ref_storage_format = NULL;
        char *to_free = NULL;
+       const struct submodule *existing;
+       struct strbuf buf = STRBUF_INIT;
+       int i;
+       char *sm_name_to_free = NULL;
        struct option options[] = {
                OPT_STRING('b', "branch", &add_data.branch, N_("branch"),
                           N_("branch of repository to add as submodule")),
@@ -3546,6 +3550,29 @@ static int module_add(int argc, const char **argv, const char *prefix,
        if(!add_data.sm_name)
                add_data.sm_name = add_data.sm_path;
 
+       existing = submodule_from_name(the_repository,
+                                       null_oid(the_hash_algo),
+                                       add_data.sm_name);
+
+       if (existing && strcmp(existing->path, add_data.sm_path)) {
+               if (!force) {
+                       die(_("submodule name '%s' already used for path '%s'"),
+                       add_data.sm_name, existing->path);
+               }
+
+               /* --force: build <name><n> until unique */
+               for (i = 1; ; i++) {
+                       strbuf_reset(&buf);
+                       strbuf_addf(&buf, "%s%d", add_data.sm_name, i);
+                       if (!submodule_from_name(the_repository,
+                                               null_oid(the_hash_algo),
+                                               buf.buf)) {
+                               break;
+                       }
+               }
+
+               add_data.sm_name = sm_name_to_free = strbuf_detach(&buf, NULL);
+       }
        if (check_submodule_name(add_data.sm_name))
                die(_("'%s' is not a valid submodule name"), add_data.sm_name);
 
@@ -3561,6 +3588,7 @@ static int module_add(int argc, const char **argv, const char *prefix,
 
        ret = 0;
 cleanup:
+       free(sm_name_to_free);
        free(add_data.sm_path);
        free(to_free);
        strbuf_release(&sb);
index d6a501d4535417c91723f223a662c8678f507b3f..f5514decab8d27159340d890640c744b0e80ffee 100755 (executable)
@@ -1482,4 +1482,27 @@ test_expect_success '`submodule init` and `init.templateDir`' '
        )
 '
 
+test_expect_success 'submodule add fails when name is reused' '
+       git init test-submodule &&
+       (
+               cd test-submodule &&
+               git commit --allow-empty -m init &&
+
+               git init ../child-origin &&
+               git -C ../child-origin commit --allow-empty -m init &&
+
+               git submodule add ../child-origin child &&
+               git commit -m "Add submodule child" &&
+
+               git mv child child_old &&
+               git commit -m "Move child to child_old" &&
+
+               # Now adding a *new* repo at the old name must fail
+               git init ../child2-origin &&
+               git -C ../child2-origin commit --allow-empty -m init &&
+               test_must_fail git submodule add ../child2-origin child
+       )
+'
+
+
 test_done