git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v8 1/2] submodule: prevent overwriting .gitmodules entry on path reuse

From
D. Ben Knoble <ben.knoble@gmail.com>
Date
Jul 9, 2025, 02:50 UTC
Message-ID
<CALnO6CB5VezYHtp22RNihNtzLXQkP9Ly=hf0V=3A1EsTvKcFgg@mail.gmail.com>
In-Reply-To
<20250608032705.11990-2-jayatheerthkulkarni2005@gmail.com>

On Sat, Jun 7, 2025 at 11:28 PM K Jayatheerth <jayatheerthkulkarni2005@gmail.com> wrote:

Show 14 quoted lines
>
> 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.

I had to read the patch to figure out what "auto increment" meant—perhaps some accompanying docs in `git help submodule`?

Show 46 quoted lines
>
> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
> ---
>  builtin/submodule--helper.c | 28 ++++++++++++++++++++++++++++
>  t/t7400-submodule-basic.sh  | 23 +++++++++++++++++++++++
>  2 files changed, 51 insertions(+)
>
> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
> index 53da2116dd..9f6df833f0 100644
> --- a/builtin/submodule--helper.c
> +++ b/builtin/submodule--helper.c
> @@ -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;
> +                       }
> +               }

This isn't typically what I'd expect --force to do, personally, though in this case it allows me to proceed with an operation that wasn't allowed otherwise.

Still, I wonder if a user might be confused by "I said 'child' and got 'child2'?"

Show 41 quoted lines
> +
> +               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);
> diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
> index d6a501d453..f5514decab 100755
> --- a/t/t7400-submodule-basic.sh
> +++ b/t/t7400-submodule-basic.sh
> @@ -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

This makes sense, though I was hoping (when I'd only skimmed the message and not seen "refuse") that this would be permitted by some clever trick. Oh well.

Show 9 quoted lines
> +       )
> +'
> +
> +
>  test_done
> --
> 2.49.GIT
>
>

-- D. Ben Knoble

Previous: K JayatheerthNext: JAYATHEERTH K
Message 11 of 19 in “Avoid submodule overwritten and skip redundant active entries”
  1. 0/2 Avoid submodule overwritten and skip redundant active entriesK Jayatheerth, May 24, 2025
  2. 1/2 submodule: prevent overwriting .gitmodules entry on path reuseK Jayatheerth, May 24, 2025
  3. Junio C HamanoMay 27, 2025
  4. 2/2 submodule: skip redundant active entries when pattern covers pathK Jayatheerth, May 24, 2025
  5. Junio C HamanoMay 27, 2025
  6. JAYATHEERTH KMay 29, 2025
  7. Junio C HamanoJun 4, 2025
  8. JAYATHEERTH KJun 6, 2025
  9. 0/2 Avoid submodule overwritten and skip redundant active entriesK Jayatheerth, Jun 8, 2025
  10. 1/2 submodule: prevent overwriting .gitmodules entry on path reuseK Jayatheerth, Jun 8, 2025
  11. D. Ben KnobleJul 9, 2025
  12. JAYATHEERTH KJul 20, 2025
  13. Ben KnobleJul 25, 2025
  14. 2/2 submodule: skip redundant active entries when pattern covers pathK Jayatheerth, Jun 8, 2025
  15. D. Ben KnobleJul 9, 2025
  16. Junio C HamanoJul 9, 2025
  17. Junio C HamanoJul 17, 2025
  18. JAYATHEERTH KJul 18, 2025
  19. Junio C HamanoJul 7, 2025

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.