* [PATCH] submodule: prevent overwriting .gitmodules entry on path reuse @ 2025-05-10 5:45 K Jayatheerth 2025-05-10 5:57 ` JAYATHEERTH K 2025-05-12 12:32 ` Junio C Hamano 0 siblings, 2 replies; 24+ messages in thread From: K Jayatheerth @ 2025-05-10 5:45 UTC (permalink / raw) To: git; +Cc: jayatheerthkulkarni2005 When a submodule is added at a path that previously hosted another submodule (e.g., 'child'), Git reuses the submodule name derived from the path and updates the corresponding entry in .gitmodules. This can silently overwrite existing configuration if the old submodule was only moved (e.g., to 'child_old') without renaming the submodule. This patch improves the `module_add()` logic by checking whether the submodule name already exists in the config but maps to a different path. In such a case, Git now errors out unless `--force` is specified, thus preventing accidental overwrites. To proceed safely, the user can provide a new name via `--name` or use `--force`. Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com> --- builtin/submodule--helper.c | 45 ++++++++++++++++++++++++++++--------- t/t7400-submodule-basic.sh | 23 +++++++++++++++++++ 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 53da2116dd..0f98ef122b 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -32,6 +32,8 @@ #include "advice.h" #include "branch.h" #include "list-objects-filter-options.h" +#include "wildmatch.h" + #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -3323,6 +3325,23 @@ static int config_submodule_in_gitmodules(const char *name, const char *var, con return ret; } +static int submodule_active_matches_path(const char *path) +{ + const struct string_list *values; + size_t i; + + if (git_config_get_string_multi("submodule.active", &values)) + return 0; + + for (i = 0; i < values->nr; i++) { + const char *pat = values->items[i].string; + if (!wildmatch(pat, path, 0)) + return 1; + } + + return 0; +} + static void configure_added_submodule(struct add_data *add_data) { char *key; @@ -3370,17 +3389,7 @@ static void configure_added_submodule(struct add_data *add_data) * is_submodule_active(), since that function needs to find * out the value of "submodule.active" again anyway. */ - if (!git_config_get("submodule.active")) { - /* - * If the submodule being added isn't already covered by the - * current configured pathspec, set the submodule's active flag - */ - if (!is_submodule_active(the_repository, add_data->sm_path)) { - key = xstrfmt("submodule.%s.active", add_data->sm_name); - git_config_set_gently(key, "true"); - free(key); - } - } else { + if (!submodule_active_matches_path(add_data->sm_path)) { key = xstrfmt("submodule.%s.active", add_data->sm_name); git_config_set_gently(key, "true"); free(key); @@ -3443,6 +3452,7 @@ static int module_add(int argc, const char **argv, const char *prefix, int force = 0, quiet = 0, progress = 0, dissociate = 0; struct add_data add_data = ADD_DATA_INIT; const char *ref_storage_format = NULL; + const struct submodule *existing; char *to_free = NULL; struct option options[] = { OPT_STRING('b', "branch", &add_data.branch, N_("branch"), @@ -3546,6 +3556,19 @@ 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' " + "(use --name to choose another or --force to overwrite)"), + add_data.sm_name, existing->path); + } + add_data.sm_name = xstrfmt("%s.%s", add_data.sm_name, basename(add_data.sm_path)); + } + if (check_submodule_name(add_data.sm_name)) die(_("'%s' is not a valid submodule name"), add_data.sm_name); diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index d6a501d453..5c3f471338 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 "initial commit" && + + git init ../child-origin && + git -C ../child-origin commit --allow-empty -m "initial commit" && + + 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" && + + # Create another submodule repo + git init ../child2-origin && + git -C ../child2-origin commit --allow-empty -m "initial commit" && + + test_must_fail git submodule add ../child2-origin child + ) +' + test_done -- 2.49.0.533.g80f4e02b4b ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-10 5:45 [PATCH] submodule: prevent overwriting .gitmodules entry on path reuse K Jayatheerth @ 2025-05-10 5:57 ` JAYATHEERTH K 2025-05-12 12:32 ` Junio C Hamano 1 sibling, 0 replies; 24+ messages in thread From: JAYATHEERTH K @ 2025-05-10 5:57 UTC (permalink / raw) To: git > +++ b/builtin/submodule--helper.c > @@ -32,6 +32,8 @@ > #include "advice.h" > #include "branch.h" > #include "list-objects-filter-options.h" > +#include "wildmatch.h" > + > > #define OPT_QUIET (1 << 0) > #define OPT_CACHED (1 << 1) > @@ -3323,6 +3325,23 @@ static int config_submodule_in_gitmodules(const char *name, const char *var, con > return ret; > } > > +static int submodule_active_matches_path(const char *path) > +{ > + const struct string_list *values; > + size_t i; > + > + if (git_config_get_string_multi("submodule.active", &values)) > + return 0; > + > + for (i = 0; i < values->nr; i++) { > + const char *pat = values->items[i].string; > + if (!wildmatch(pat, path, 0)) > + return 1; > + } > + > + return 0; > +} > + > I added this change in the same patch because t7413 was throwing errors at these specific lines of code I don't really have full understanding of these files yet but after this t7413 perfectly passed all the tests I tried these changed because there was a comment about this in the file /* * NEEDSWORK: In a multi-working-tree world this needs to be * set in the per-worktree config. */ /* * NEEDSWORK: In the longer run, we need to get rid of this * pattern of querying "submodule.active" before calling * is_submodule_active(), since that function needs to find * out the value of "submodule.active" again anyway. */ I can separate it into different patches if there is a requirement. Or change based on the feedback. -Jayatheerth ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-10 5:45 [PATCH] submodule: prevent overwriting .gitmodules entry on path reuse K Jayatheerth 2025-05-10 5:57 ` JAYATHEERTH K @ 2025-05-12 12:32 ` Junio C Hamano 2025-05-12 13:26 ` JAYATHEERTH K 1 sibling, 1 reply; 24+ messages in thread From: Junio C Hamano @ 2025-05-12 12:32 UTC (permalink / raw) To: K Jayatheerth; +Cc: git K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes: > When a submodule is added at a path that previously hosted another submodule > (e.g., 'child'), Git reuses the submodule name derived from the path and > updates the corresponding entry in .gitmodules. This can silently overwrite > existing configuration if the old submodule was only moved (e.g., to > 'child_old') without renaming the submodule. > > This patch improves the `module_add()` logic by checking whether the > submodule name already exists in the config but maps to a different path. Quite sensible description of the problem and the proposed course of improvement. I do not think `--force` that allows the same name to be reused a good idea at all, though. We shouldn't encourage its use to resolve such a case. If what used to be called `child` now sits elsewhere, perhaps because the tree structure was reorganized due to mass renaming, but if it still is being used in the project, there is no good reason to nuke the configuration recorded for that existing module. The module name used in .git/config is purely local so the user should just give a new one a name that does not conflict, or even better yet, perhaps the tool should pick a unique and nonconflicting name automatically, no? > In such a case, Git now errors out unless `--force` is specified, thus > preventing accidental overwrites. To proceed safely, the user can provide > a new name via `--name` or use `--force`. > > Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com> > --- > builtin/submodule--helper.c | 45 ++++++++++++++++++++++++++++--------- > t/t7400-submodule-basic.sh | 23 +++++++++++++++++++ > 2 files changed, 57 insertions(+), 11 deletions(-) > > diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c > index 53da2116dd..0f98ef122b 100644 > --- a/builtin/submodule--helper.c > +++ b/builtin/submodule--helper.c > @@ -32,6 +32,8 @@ > #include "advice.h" > #include "branch.h" > #include "list-objects-filter-options.h" > +#include "wildmatch.h" > + > > #define OPT_QUIET (1 << 0) > #define OPT_CACHED (1 << 1) > @@ -3323,6 +3325,23 @@ static int config_submodule_in_gitmodules(const char *name, const char *var, con > return ret; > } > > +static int submodule_active_matches_path(const char *path) > +{ > + const struct string_list *values; > + size_t i; > + > + if (git_config_get_string_multi("submodule.active", &values)) > + return 0; > + > + for (i = 0; i < values->nr; i++) { > + const char *pat = values->items[i].string; > + if (!wildmatch(pat, path, 0)) > + return 1; > + } > + > + return 0; > +} > + > static void configure_added_submodule(struct add_data *add_data) > { > char *key; > @@ -3370,17 +3389,7 @@ static void configure_added_submodule(struct add_data *add_data) > * is_submodule_active(), since that function needs to find > * out the value of "submodule.active" again anyway. > */ > - if (!git_config_get("submodule.active")) { > - /* > - * If the submodule being added isn't already covered by the > - * current configured pathspec, set the submodule's active flag > - */ > - if (!is_submodule_active(the_repository, add_data->sm_path)) { > - key = xstrfmt("submodule.%s.active", add_data->sm_name); > - git_config_set_gently(key, "true"); > - free(key); > - } > - } else { > + if (!submodule_active_matches_path(add_data->sm_path)) { > key = xstrfmt("submodule.%s.active", add_data->sm_name); > git_config_set_gently(key, "true"); > free(key); > @@ -3443,6 +3452,7 @@ static int module_add(int argc, const char **argv, const char *prefix, > int force = 0, quiet = 0, progress = 0, dissociate = 0; > struct add_data add_data = ADD_DATA_INIT; > const char *ref_storage_format = NULL; > + const struct submodule *existing; > char *to_free = NULL; > struct option options[] = { > OPT_STRING('b', "branch", &add_data.branch, N_("branch"), > @@ -3546,6 +3556,19 @@ 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' " > + "(use --name to choose another or --force to overwrite)"), > + add_data.sm_name, existing->path); > + } > + add_data.sm_name = xstrfmt("%s.%s", add_data.sm_name, basename(add_data.sm_path)); > + } > + > if (check_submodule_name(add_data.sm_name)) > die(_("'%s' is not a valid submodule name"), add_data.sm_name); > > diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh > index d6a501d453..5c3f471338 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 "initial commit" && > + > + git init ../child-origin && > + git -C ../child-origin commit --allow-empty -m "initial commit" && > + > + 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" && > + > + # Create another submodule repo > + git init ../child2-origin && > + git -C ../child2-origin commit --allow-empty -m "initial commit" && > + > + test_must_fail git submodule add ../child2-origin child > + ) > +' > + > test_done ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-12 12:32 ` Junio C Hamano @ 2025-05-12 13:26 ` JAYATHEERTH K 2025-05-13 3:34 ` [PATCH v2] " K Jayatheerth 0 siblings, 1 reply; 24+ messages in thread From: JAYATHEERTH K @ 2025-05-12 13:26 UTC (permalink / raw) To: Junio C Hamano; +Cc: git On Mon, May 12, 2025 at 6:02 PM Junio C Hamano <gitster@pobox.com> wrote: > > K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes: > > > When a submodule is added at a path that previously hosted another submodule > > (e.g., 'child'), Git reuses the submodule name derived from the path and > > updates the corresponding entry in .gitmodules. This can silently overwrite > > existing configuration if the old submodule was only moved (e.g., to > > 'child_old') without renaming the submodule. > > > > This patch improves the `module_add()` logic by checking whether the > > submodule name already exists in the config but maps to a different path. > > Quite sensible description of the problem and the proposed course of > improvement. > > I do not think `--force` that allows the same name to be reused a > good idea at all, though. We shouldn't encourage its use to resolve > such a case. If what used to be called `child` now sits elsewhere, > perhaps because the tree structure was reorganized due to mass > renaming, but if it still is being used in the project, there is no > good reason to nuke the configuration recorded for that existing > module. > > The module name used in .git/config is purely local so the user > should just give a new one a name that does not conflict, or even > better yet, perhaps the tool should pick a unique and nonconflicting > name automatically, no? > That makes sense I see the point in using --force to resolve name conflicts might not be ideal, especially when the previous submodule config may still be valid and in use. As a potential improvement, I was thinking of mimicking how duplicate filenames are handled: if the default submodule name (derived from the path) already exists and maps to a different path, we could automatically append an incrementing number (foo, foo1, foo2, etc.) until a non-conflicting name is found. -Jayatheerth ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v2] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-12 13:26 ` JAYATHEERTH K @ 2025-05-13 3:34 ` K Jayatheerth 2025-05-13 21:44 ` Junio C Hamano 0 siblings, 1 reply; 24+ messages in thread From: K Jayatheerth @ 2025-05-13 3:34 UTC (permalink / raw) To: jayatheerthkulkarni2005; +Cc: git, gitster When a submodule is added at a path that previously hosted another submodule (e.g., 'child'), Git reuses the submodule name derived from the path and updates the corresponding entry in .gitmodules. This can silently overwrite existing configuration if the old submodule was only moved (e.g., to 'child_old') without renaming the submodule. This patch improves the `module_add()` logic by checking whether the submodule name already exists in the config but maps to a different path. In such a case, Git now errors out unless `--force` is specified, thus preventing accidental overwrites. To proceed safely, the user can provide a new name via `--name` or use `--force`. Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com> --- builtin/submodule--helper.c | 58 ++++++++++++++++++++++++++++++------- t/t7400-submodule-basic.sh | 23 +++++++++++++++ 2 files changed, 70 insertions(+), 11 deletions(-) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 53da2116dd..e70aa584f1 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -32,6 +32,8 @@ #include "advice.h" #include "branch.h" #include "list-objects-filter-options.h" +#include "wildmatch.h" +#include "strbuf.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -3323,6 +3325,23 @@ static int config_submodule_in_gitmodules(const char *name, const char *var, con return ret; } +static int submodule_active_matches_path(const char *path) +{ + const struct string_list *values; + size_t i; + + if (git_config_get_string_multi("submodule.active", &values)) + return 0; + + for (i = 0; i < values->nr; i++) { + const char *pat = values->items[i].string; + if (!wildmatch(pat, path, 0)) + return 1; + } + + return 0; +} + static void configure_added_submodule(struct add_data *add_data) { char *key; @@ -3370,17 +3389,7 @@ static void configure_added_submodule(struct add_data *add_data) * is_submodule_active(), since that function needs to find * out the value of "submodule.active" again anyway. */ - if (!git_config_get("submodule.active")) { - /* - * If the submodule being added isn't already covered by the - * current configured pathspec, set the submodule's active flag - */ - if (!is_submodule_active(the_repository, add_data->sm_path)) { - key = xstrfmt("submodule.%s.active", add_data->sm_name); - git_config_set_gently(key, "true"); - free(key); - } - } else { + if (!submodule_active_matches_path(add_data->sm_path)) { key = xstrfmt("submodule.%s.active", add_data->sm_name); git_config_set_gently(key, "true"); free(key); @@ -3443,6 +3452,7 @@ static int module_add(int argc, const char **argv, const char *prefix, int force = 0, quiet = 0, progress = 0, dissociate = 0; struct add_data add_data = ADD_DATA_INIT; const char *ref_storage_format = NULL; + const struct submodule *existing; char *to_free = NULL; struct option options[] = { OPT_STRING('b', "branch", &add_data.branch, N_("branch"), @@ -3546,6 +3556,32 @@ 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 */ + struct strbuf buf = STRBUF_INIT; + strbuf_addstr(&buf, add_data.sm_name); + + for (int i = 1; ; i++) { + strbuf_setlen(&buf, 0); + 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 = strbuf_detach(&buf, NULL); + } + if (check_submodule_name(add_data.sm_name)) die(_("'%s' is not a valid submodule name"), add_data.sm_name); diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index d6a501d453..5c3f471338 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 "initial commit" && + + git init ../child-origin && + git -C ../child-origin commit --allow-empty -m "initial commit" && + + 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" && + + # Create another submodule repo + git init ../child2-origin && + git -C ../child2-origin commit --allow-empty -m "initial commit" && + + test_must_fail git submodule add ../child2-origin child + ) +' + test_done -- 2.49.GIT ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH v2] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-13 3:34 ` [PATCH v2] " K Jayatheerth @ 2025-05-13 21:44 ` Junio C Hamano 2025-05-14 2:01 ` [PATCH v3] " K Jayatheerth 0 siblings, 1 reply; 24+ messages in thread From: Junio C Hamano @ 2025-05-13 21:44 UTC (permalink / raw) To: K Jayatheerth; +Cc: git K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes: > When a submodule is added at a path that previously hosted another submodule > (e.g., 'child'), Git reuses the submodule name derived from the path and > updates the corresponding entry in .gitmodules. This can silently overwrite > existing configuration if the old submodule was only moved (e.g., to > 'child_old') without renaming the submodule. OK. > This patch improves the `module_add()` logic by checking whether the > submodule name already exists in the config but maps to a different path. We frown upon a patch that says "This patch does X"; just give an order to the codebase to "be like so". I.e. "Improve the module-add by doing X..." is how we phrase a proposed change. > In such a case, Git now errors out unless `--force` is specified, thus > preventing accidental overwrites. To proceed safely, the user can provide > a new name via `--name` or use `--force`. The above explains what happens in module_add() quite well. What is puzzling about this change is that the new helper function and changes to configure_added_submodule() is not described at all in the proposed log message. How are they relevant and why do we need them? > @@ -3443,6 +3452,7 @@ static int module_add(int argc, const char **argv, const char *prefix, > int force = 0, quiet = 0, progress = 0, dissociate = 0; > struct add_data add_data = ADD_DATA_INIT; > const char *ref_storage_format = NULL; > + const struct submodule *existing; > char *to_free = NULL; > struct option options[] = { > OPT_STRING('b', "branch", &add_data.branch, N_("branch"), > @@ -3546,6 +3556,32 @@ 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 the name is in use, and the submodule with that name is at a different path, then we are in trouble, OK. > + if (!force) > + die(_("submodule name '%s' already used for path '%s'"), > + add_data.sm_name, existing->path); > + > + /* --force: build <name><n> until unique */ > + struct strbuf buf = STRBUF_INIT; "-Wdeclaration-after-statement" > + strbuf_addstr(&buf, add_data.sm_name); > + > + for (int i = 1; ; i++) { > + strbuf_setlen(&buf, 0); > + 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 = strbuf_detach(&buf, NULL); > + } > + What is the memory ownership rule for add_data.sm_name? Earlier we saw in a pre-context of a hunk that this was assigned from add_data.sm_path, so in that codepath it is considered a borrowed piece of memory, but here the member has to be the one that owns the string detached from the strbuf, which eventually must be freed by somebody, or it would be a memory leak. ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v3] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-13 21:44 ` Junio C Hamano @ 2025-05-14 2:01 ` K Jayatheerth 2025-05-14 22:47 ` Junio C Hamano 0 siblings, 1 reply; 24+ messages in thread From: K Jayatheerth @ 2025-05-14 2:01 UTC (permalink / raw) To: gitster; +Cc: git, jayatheerthkulkarni2005 When a submodule is added at a path that previously hosted another submodule (e.g., 'child'), Git reuses the submodule name derived from the path and updates the corresponding entry in .gitmodules. This can silently overwrite existing configuration if the old submodule was only moved (e.g., to 'child_old') without renaming the submodule. Teach `module_add()` to look up the chosen submodule name in the repository existing submodule config. If that name is already in use and points at a *different* path, we now die with an error, prompting the user to supply `--name`. Increment the name to an appropriate unique name (Like file system) when --force is called upon. Add helper `submodule_active_matches_path()` so we can re-implement the old “is this path already covered by submodule.active?” logic without re-reading the config twice. Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com> --- builtin/submodule--helper.c | 62 ++++++++++++++++++++++++++++++------- t/t7400-submodule-basic.sh | 23 ++++++++++++++ 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 53da2116dd..ef9e733cfb 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -32,6 +32,8 @@ #include "advice.h" #include "branch.h" #include "list-objects-filter-options.h" +#include "wildmatch.h" +#include "strbuf.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -3323,6 +3325,23 @@ static int config_submodule_in_gitmodules(const char *name, const char *var, con return ret; } +static int submodule_active_matches_path(const char *path) +{ + const struct string_list *values; + size_t i; + + if (git_config_get_string_multi("submodule.active", &values)) + return 0; + + for (i = 0; i < values->nr; i++) { + const char *pat = values->items[i].string; + if (!wildmatch(pat, path, 0)) + return 1; + } + + return 0; +} + static void configure_added_submodule(struct add_data *add_data) { char *key; @@ -3370,17 +3389,7 @@ static void configure_added_submodule(struct add_data *add_data) * is_submodule_active(), since that function needs to find * out the value of "submodule.active" again anyway. */ - if (!git_config_get("submodule.active")) { - /* - * If the submodule being added isn't already covered by the - * current configured pathspec, set the submodule's active flag - */ - if (!is_submodule_active(the_repository, add_data->sm_path)) { - key = xstrfmt("submodule.%s.active", add_data->sm_name); - git_config_set_gently(key, "true"); - free(key); - } - } else { + if (!submodule_active_matches_path(add_data->sm_path)) { key = xstrfmt("submodule.%s.active", add_data->sm_name); git_config_set_gently(key, "true"); free(key); @@ -3443,7 +3452,11 @@ static int module_add(int argc, const char **argv, const char *prefix, int force = 0, quiet = 0, progress = 0, dissociate = 0; struct add_data add_data = ADD_DATA_INIT; const char *ref_storage_format = NULL; + const struct submodule *existing; char *to_free = NULL; + struct strbuf buf = STRBUF_INIT; + int i; + int allocated_sm_name = 0; struct option options[] = { OPT_STRING('b', "branch", &add_data.branch, N_("branch"), N_("branch of repository to add as submodule")), @@ -3546,6 +3559,31 @@ 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 = strbuf_detach(&buf, NULL); + allocated_sm_name = 1; + } + if (check_submodule_name(add_data.sm_name)) die(_("'%s' is not a valid submodule name"), add_data.sm_name); @@ -3561,6 +3599,8 @@ static int module_add(int argc, const char **argv, const char *prefix, ret = 0; cleanup: + if (allocated_sm_name) + free((char *)add_data.sm_name); 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..5c3f471338 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 "initial commit" && + + git init ../child-origin && + git -C ../child-origin commit --allow-empty -m "initial commit" && + + 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" && + + # Create another submodule repo + git init ../child2-origin && + git -C ../child2-origin commit --allow-empty -m "initial commit" && + + test_must_fail git submodule add ../child2-origin child + ) +' + test_done -- 2.49.GIT ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH v3] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-14 2:01 ` [PATCH v3] " K Jayatheerth @ 2025-05-14 22:47 ` Junio C Hamano 2025-05-16 8:51 ` JAYATHEERTH K 0 siblings, 1 reply; 24+ messages in thread From: Junio C Hamano @ 2025-05-14 22:47 UTC (permalink / raw) To: K Jayatheerth; +Cc: git K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes: > Add helper `submodule_active_matches_path()` so we can > re-implement the old “is this path already covered by > submodule.active?” logic without re-reading the config twice. Having duplicated code to implement what is supposed to be the same thing is a bug waiting to happen by them diverging from each other. Isn't the fact that our configuration reading code reads things just once and the caches the result good enough for the purpose of this code path? Do we have a measurement that tells us that the extra complexity is worth the maintenance headache? > @@ -3443,7 +3452,11 @@ static int module_add(int argc, const char **argv, const char *prefix, > int force = 0, quiet = 0, progress = 0, dissociate = 0; > struct add_data add_data = ADD_DATA_INIT; > const char *ref_storage_format = NULL; > + const struct submodule *existing; > char *to_free = NULL; > + struct strbuf buf = STRBUF_INIT; > + int i; > + int allocated_sm_name = 0; A separate flag is not wrong per-se, but the idiom used in this project more often is to have an extra pointer variable that points at an allocated piece of memory (or NULL), and free the piece of memory unconditionally. "git grep -e to_free" to see the idiom in action. Even better yet, this codepath already uses the idiom. By doing so > + if (allocated_sm_name) > + free((char *)add_data.sm_name); becomes free(sm_name_to_free); and we can keep the "add_data.sm_name is pointing at a borrowed piece of memory, and we will _never_ free anything through that pointer" memory ownership rule. We were borrowing from a separate variable sm_name_to_free, and we may free it when add_data is getting destroyed, or we may be borrowing from the .sm_path string, which we would free it when add_data is getting destroyed. > 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..5c3f471338 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 "initial commit" && > + > + git init ../child-origin && > + git -C ../child-origin commit --allow-empty -m "initial commit" && > + > + 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" && > + > + # Create another submodule repo > + git init ../child2-origin && > + git -C ../child2-origin commit --allow-empty -m "initial commit" && > + > + test_must_fail git submodule add ../child2-origin child > + ) > +' > + > test_done ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-14 22:47 ` Junio C Hamano @ 2025-05-16 8:51 ` JAYATHEERTH K 2025-05-16 17:49 ` [PATCH v4] " K Jayatheerth 0 siblings, 1 reply; 24+ messages in thread From: JAYATHEERTH K @ 2025-05-16 8:51 UTC (permalink / raw) To: Junio C Hamano; +Cc: git On Thu, May 15, 2025 at 4:18 AM Junio C Hamano <gitster@pobox.com> wrote: > > K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes: > > > Add helper `submodule_active_matches_path()` so we can > > re-implement the old “is this path already covered by > > submodule.active?” logic without re-reading the config twice. > > Having duplicated code to implement what is supposed to be the same > thing is a bug waiting to happen by them diverging from each other. > > Isn't the fact that our configuration reading code reads things just > once and the caches the result good enough for the purpose of this > code path? Do we have a measurement that tells us that the extra > complexity is worth the maintenance headache? > Well when I first sent this patch I didn't quite understand why test 4137 was failing then I read the tests and I didn't have a lot of idea of the code. But I think it' better to remove helper as you said I will send a new patch with a different approach as I've spent some time understanding this now. > > @@ -3443,7 +3452,11 @@ static int module_add(int argc, const char **argv, const char *prefix, > > int force = 0, quiet = 0, progress = 0, dissociate = 0; > > struct add_data add_data = ADD_DATA_INIT; > > const char *ref_storage_format = NULL; > > + const struct submodule *existing; > > char *to_free = NULL; > > + struct strbuf buf = STRBUF_INIT; > > + int i; > > + int allocated_sm_name = 0; > > A separate flag is not wrong per-se, but the idiom used in this > project more often is to have an extra pointer variable that points > at an allocated piece of memory (or NULL), and free the piece of > memory unconditionally. > > "git grep -e to_free" to see the idiom in action. Even better yet, > this codepath already uses the idiom. > > By doing so > > > + if (allocated_sm_name) > > + free((char *)add_data.sm_name); > > becomes > > free(sm_name_to_free); > > and we can keep the "add_data.sm_name is pointing at a borrowed > piece of memory, and we will _never_ free anything through that > pointer" memory ownership rule. We were borrowing from a separate > variable sm_name_to_free, and we may free it when add_data is > getting destroyed, or we may be borrowing from the .sm_path string, > which we would free it when add_data is getting destroyed. > Interesting, this is good, I'm going to copy this : ) Thank you, -Jayatheerth ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-16 8:51 ` JAYATHEERTH K @ 2025-05-16 17:49 ` K Jayatheerth 2025-05-16 17:53 ` JAYATHEERTH K 0 siblings, 1 reply; 24+ messages in thread From: K Jayatheerth @ 2025-05-16 17:49 UTC (permalink / raw) To: jayatheerthkulkarni2005; +Cc: git, gitster, Moritz When adding a submodule, Git may write `submodule.<name>.active = true` to the repository configuration to explicitly mark the submodule as active. However, if the submodule's path is already matched by a pattern in the `submodule.active` setting, this explicit entry is redundant. This change inlines the logic, using `wildmatch()` to check if the added submodule path is already covered by one of the patterns defined in `submodule.active`. If the path matches any pattern, Git now avoids writing the `submodule.<name>.active` entry. This prevents unnecessary configuration bloat and aligns the behavior with user expectations when using pattern-based submodule activation. Reported-by: Moritz <mlell08@gmail.com> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com> --- builtin/submodule--helper.c | 59 ++++++++++++++++++++++++++++++------- t/t7400-submodule-basic.sh | 23 +++++++++++++++ 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 53da2116dd..5a9c8bdc0c 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -32,6 +32,8 @@ #include "advice.h" #include "branch.h" #include "list-objects-filter-options.h" +#include "strbuf.h" +#include "wildmatch.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -3328,6 +3330,9 @@ static void configure_added_submodule(struct add_data *add_data) char *key; struct child_process add_submod = CHILD_PROCESS_INIT; struct child_process add_gitmodules = CHILD_PROCESS_INIT; + const struct string_list *values; + size_t i; + int matched = 0; key = xstrfmt("submodule.%s.url", add_data->sm_name); git_config_set_gently(key, add_data->realrepo); @@ -3370,20 +3375,25 @@ static void configure_added_submodule(struct add_data *add_data) * is_submodule_active(), since that function needs to find * out the value of "submodule.active" again anyway. */ - if (!git_config_get("submodule.active")) { - /* - * If the submodule being added isn't already covered by the - * current configured pathspec, set the submodule's active flag - */ - if (!is_submodule_active(the_repository, add_data->sm_path)) { + if (git_config_get("submodule.active") || /* key absent */ + git_config_get_string_multi("submodule.active", &values)) { + /* submodule.active is missing -> force-enable */ + key = xstrfmt("submodule.%s.active", add_data->sm_name); + git_config_set_gently(key, "true"); + free(key); + } else { + for (i = 0; i < values->nr; i++) { + const char *pat = values->items[i].string; + if (!wildmatch(pat, add_data->sm_path, 0)) { /* match found */ + matched = 1; + break; + } + } + if (!matched) { /* no pattern matched -> force-enable */ key = xstrfmt("submodule.%s.active", add_data->sm_name); git_config_set_gently(key, "true"); free(key); } - } else { - key = xstrfmt("submodule.%s.active", add_data->sm_name); - git_config_set_gently(key, "true"); - free(key); } } @@ -3443,7 +3453,11 @@ static int module_add(int argc, const char **argv, const char *prefix, int force = 0, quiet = 0, progress = 0, dissociate = 0; struct add_data add_data = ADD_DATA_INIT; const char *ref_storage_format = NULL; + const struct submodule *existing; char *to_free = NULL; + 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 +3560,30 @@ 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 +3599,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..5c3f471338 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 "initial commit" && + + git init ../child-origin && + git -C ../child-origin commit --allow-empty -m "initial commit" && + + 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" && + + # Create another submodule repo + git init ../child2-origin && + git -C ../child2-origin commit --allow-empty -m "initial commit" && + + test_must_fail git submodule add ../child2-origin child + ) +' + test_done -- 2.49.GIT ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH v4] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-16 17:49 ` [PATCH v4] " K Jayatheerth @ 2025-05-16 17:53 ` JAYATHEERTH K 2025-05-18 7:54 ` [PATCH v5] " K Jayatheerth 0 siblings, 1 reply; 24+ messages in thread From: JAYATHEERTH K @ 2025-05-16 17:53 UTC (permalink / raw) To: jayatheerthkulkarni2005; +Cc: git, gitster, Moritz I think the logic is almost similar as the helper function in this patch, But I tried to wrap it within the if else block Reason why this is needed is I initially thought that this was just a path issue Turned out tests like t4137 and t7413 failed These failed tests were because I didn't take submodule active into consideration And at the end since this is the only place where the helper is needed I just wrapped it up for this specific thing Maybe if someone might need it in future or someone builds an helper function then using that might make sense but for now I think this is the middle ground approach we could find. -Jayatheerth ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v5] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-16 17:53 ` JAYATHEERTH K @ 2025-05-18 7:54 ` K Jayatheerth 2025-05-18 7:58 ` JAYATHEERTH K 2025-05-19 15:41 ` Junio C Hamano 0 siblings, 2 replies; 24+ messages in thread From: K Jayatheerth @ 2025-05-18 7:54 UTC (permalink / raw) To: jayatheerthkulkarni2005; +Cc: git, gitster, mlell08 When a submodule is added, Git writes submodule.<name>.active = true to the repository configuration to mark it as active. This happens even when the submodule path already matches a pattern in submodule.active. This results in redundant configuration entries that are unnecessary and clutter the config, especially when pattern-based activation is used. Avoid writing the submodule.<name>.active entry if the path is already covered by a pattern in submodule.active. Reported-by: Moritz <mlell08@gmail.com> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com> --- builtin/submodule--helper.c | 59 ++++++++++++++++++++++++++++++------- t/t7400-submodule-basic.sh | 23 +++++++++++++++ 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 53da2116dd..5a9c8bdc0c 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -32,6 +32,8 @@ #include "advice.h" #include "branch.h" #include "list-objects-filter-options.h" +#include "strbuf.h" +#include "wildmatch.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -3328,6 +3330,9 @@ static void configure_added_submodule(struct add_data *add_data) char *key; struct child_process add_submod = CHILD_PROCESS_INIT; struct child_process add_gitmodules = CHILD_PROCESS_INIT; + const struct string_list *values; + size_t i; + int matched = 0; key = xstrfmt("submodule.%s.url", add_data->sm_name); git_config_set_gently(key, add_data->realrepo); @@ -3370,20 +3375,25 @@ static void configure_added_submodule(struct add_data *add_data) * is_submodule_active(), since that function needs to find * out the value of "submodule.active" again anyway. */ - if (!git_config_get("submodule.active")) { - /* - * If the submodule being added isn't already covered by the - * current configured pathspec, set the submodule's active flag - */ - if (!is_submodule_active(the_repository, add_data->sm_path)) { + if (git_config_get("submodule.active") || /* key absent */ + git_config_get_string_multi("submodule.active", &values)) { + /* submodule.active is missing -> force-enable */ + key = xstrfmt("submodule.%s.active", add_data->sm_name); + git_config_set_gently(key, "true"); + free(key); + } else { + for (i = 0; i < values->nr; i++) { + const char *pat = values->items[i].string; + if (!wildmatch(pat, add_data->sm_path, 0)) { /* match found */ + matched = 1; + break; + } + } + if (!matched) { /* no pattern matched -> force-enable */ key = xstrfmt("submodule.%s.active", add_data->sm_name); git_config_set_gently(key, "true"); free(key); } - } else { - key = xstrfmt("submodule.%s.active", add_data->sm_name); - git_config_set_gently(key, "true"); - free(key); } } @@ -3443,7 +3453,11 @@ static int module_add(int argc, const char **argv, const char *prefix, int force = 0, quiet = 0, progress = 0, dissociate = 0; struct add_data add_data = ADD_DATA_INIT; const char *ref_storage_format = NULL; + const struct submodule *existing; char *to_free = NULL; + 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 +3560,30 @@ 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 +3599,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..5c3f471338 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 "initial commit" && + + git init ../child-origin && + git -C ../child-origin commit --allow-empty -m "initial commit" && + + 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" && + + # Create another submodule repo + git init ../child2-origin && + git -C ../child2-origin commit --allow-empty -m "initial commit" && + + test_must_fail git submodule add ../child2-origin child + ) +' + test_done -- 2.49.GIT ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH v5] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-18 7:54 ` [PATCH v5] " K Jayatheerth @ 2025-05-18 7:58 ` JAYATHEERTH K 2025-05-19 15:41 ` Junio C Hamano 1 sibling, 0 replies; 24+ messages in thread From: JAYATHEERTH K @ 2025-05-18 7:58 UTC (permalink / raw) To: jayatheerthkulkarni2005; +Cc: git, gitster, mlell08 No separate change just changed the commit message to the format Added _order_ to the last line. Thank you, -Jayatheerth ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v5] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-18 7:54 ` [PATCH v5] " K Jayatheerth 2025-05-18 7:58 ` JAYATHEERTH K @ 2025-05-19 15:41 ` Junio C Hamano 2025-05-20 1:31 ` JAYATHEERTH K 1 sibling, 1 reply; 24+ messages in thread From: Junio C Hamano @ 2025-05-19 15:41 UTC (permalink / raw) To: K Jayatheerth; +Cc: git, mlell08 K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes: > When a submodule is added, Git writes submodule.<name>.active = true > to the repository configuration to mark it as active. This happens even > when the submodule path already matches a pattern in submodule.active. > This results in redundant configuration entries that are unnecessary > and clutter the config, especially when pattern-based activation is used. > > Avoid writing the submodule.<name>.active entry if the path is already > covered by a pattern in submodule.active. This explains why the part of the change that deals the .active bit makes sense. But now do we drop the other fix from the patch, namely "ouch, we are adding submodule at 'foo/' but the name 'foo' is taken by a different submodule that used to live there and moved elsewhere", or have you forgotten to describe that fix in the proposed log message? Stepping back a bit, perhaps this patch addresses two independent issues, both of which can trigger with"submodule add"? If so, would it make sense to have it in two separate patches? > +test_expect_success 'submodule add fails when name is reused' ' > + git init test-submodule && > + ( > + cd test-submodule && > + git commit --allow-empty -m "initial commit" && > + > + git init ../child-origin && > + git -C ../child-origin commit --allow-empty -m "initial commit" && > + > + 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" && > + > + # Create another submodule repo > + git init ../child2-origin && > + git -C ../child2-origin commit --allow-empty -m "initial commit" && > + > + test_must_fail git submodule add ../child2-origin child > + ) > +' The test seems to be about "the other issue". Shouldn't we also have a test about "we no longer add redundant configuration entries"? Thanks. ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v5] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-19 15:41 ` Junio C Hamano @ 2025-05-20 1:31 ` JAYATHEERTH K 2025-05-20 15:28 ` Junio C Hamano 0 siblings, 1 reply; 24+ messages in thread From: JAYATHEERTH K @ 2025-05-20 1:31 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, mlell08 On Mon, May 19, 2025 at 9:11 PM Junio C Hamano <gitster@pobox.com> wrote: > > K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes: > > > When a submodule is added, Git writes submodule.<name>.active = true > > to the repository configuration to mark it as active. This happens even > > when the submodule path already matches a pattern in submodule.active. > > This results in redundant configuration entries that are unnecessary > > and clutter the config, especially when pattern-based activation is used. > > > > Avoid writing the submodule.<name>.active entry if the path is already > > covered by a pattern in submodule.active. > > This explains why the part of the change that deals the .active bit > makes sense. > Hmm, I look at it this way, active and path problem are not different, It's just that this has to follow t7413 submodule active too. Therefore the submodule.<name>.active = true logic exists > >Avoid writing the submodule.<name>.active entry if the path is already > >covered by a pattern in submodule.active. I think this order makes sense to me, but I could change if you want me to. The path is the core issue and the active is just like a neat wrapper of following all the test cases in my opinion. > But now do we drop the other fix from the patch, namely "ouch, we > are adding submodule at 'foo/' but the name 'foo' is taken by a > different submodule that used to live there and moved elsewhere", or > have you forgotten to describe that fix in the proposed log message? > > Stepping back a bit, perhaps this patch addresses two independent > issues, both of which can trigger with"submodule add"? If so, would > it make sense to have it in two separate patches? > This would be the case if I wrote a separate helper function I guess but the core issue still lies at the if else block And the active part is just written to mark the submodule.<name>.active = true So I think these are a part of the same problem. > > +test_expect_success 'submodule add fails when name is reused' ' > > + git init test-submodule && > > + ( > > + cd test-submodule && > > + git commit --allow-empty -m "initial commit" && > > + > > + git init ../child-origin && > > + git -C ../child-origin commit --allow-empty -m "initial commit" && > > + > > + 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" && > > + > > + # Create another submodule repo > > + git init ../child2-origin && > > + git -C ../child2-origin commit --allow-empty -m "initial commit" && > > + > > + test_must_fail git submodule add ../child2-origin child > > + ) > > +' > > The test seems to be about "the other issue". Shouldn't we also > have a test about "we no longer add redundant configuration entries"? > Actually t7413 has a detailed coverage of the _active_ logic. I actually didn't consider submodule.<name>.active = true but looking what failed in 7413 made me realise we have to solve the core issue and then submodule.<name>.active = true it too. Therefore I didn't add extra test case too. > Thanks. I'm open to a review from your side on this I may have missed some logic here. Thank you, -Jayatheerth ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v5] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-20 1:31 ` JAYATHEERTH K @ 2025-05-20 15:28 ` Junio C Hamano 2025-05-24 6:48 ` [PATCH v6 0/2] Avoid submodule overwritten and skip redundant active entries K Jayatheerth 0 siblings, 1 reply; 24+ messages in thread From: Junio C Hamano @ 2025-05-20 15:28 UTC (permalink / raw) To: JAYATHEERTH K; +Cc: git, mlell08 JAYATHEERTH K <jayatheerthkulkarni2005@gmail.com> writes: > On Mon, May 19, 2025 at 9:11 PM Junio C Hamano <gitster@pobox.com> wrote: >> >> K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes: >> >> > When a submodule is added, Git writes submodule.<name>.active = true >> > to the repository configuration to mark it as active. This happens even >> > when the submodule path already matches a pattern in submodule.active. >> > This results in redundant configuration entries that are unnecessary >> > and clutter the config, especially when pattern-based activation is used. >> > >> > Avoid writing the submodule.<name>.active entry if the path is already >> > covered by a pattern in submodule.active. >> >> This explains why the part of the change that deals the .active bit >> makes sense. >> > > Hmm, I look at it this way, > active and path problem are not different, > It's just that this has to follow t7413 > submodule active too. > Therefore the submodule.<name>.active = true > logic exists > > >Avoid writing the submodule.<name>.active entry if the path is already > > >covered by a pattern in submodule.active. > I think this order makes sense to me, but I could change if you want me to. Do you mean the problem "we add a submodule at 'foo/' but the name 'foo' is taken already" does *not* happen when submodule.active patterns are *not* used? If so, I understand that these two problems are linked together, but I got an impression that it is not what is happening. And that is where this question ... >> Stepping back a bit, perhaps this patch addresses two independent >> issues, both of which can trigger with"submodule add"? If so, would >> it make sense to have it in two separate patches? ... comes from. The added test does not use submodule.active pattern, so I assumed that the pattern-based activation is a separate issue. The solution to pattern-based activation may have to happen by updating the same code path and it may turn out to be that having a single helper function that deals with both is the cleanest solution, but still if they are two different problems, a single patch should not try to solve both of them at the same time. Solve one by introducing a helper function and make sure that the fix for that one problem will not get broken by adding tests, and then on top, solve the other, perhaps by modifying the same helper function, and make sure that the fix for the other problem will not get broken by adding further tests, prehaps? > This would be the case if I wrote a separate helper function I guess > but the core issue still lies at the if else block > And the active part is just written to mark the submodule.<name>.active = true > So I think these are a part of the same problem. > >> > +test_expect_success 'submodule add fails when name is reused' ' >> > + git init test-submodule && >> > + ( >> > + cd test-submodule && >> > + git commit --allow-empty -m "initial commit" && >> > + >> > + git init ../child-origin && >> > + git -C ../child-origin commit --allow-empty -m "initial commit" && >> > + >> > + 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" && >> > + >> > + # Create another submodule repo >> > + git init ../child2-origin && >> > + git -C ../child2-origin commit --allow-empty -m "initial commit" && >> > + >> > + test_must_fail git submodule add ../child2-origin child >> > + ) >> > +' >> >> The test seems to be about "the other issue". Shouldn't we also >> have a test about "we no longer add redundant configuration entries"? > Actually t7413 has a detailed coverage of the _active_ logic. > I actually didn't consider submodule.<name>.active = true > but looking what failed in 7413 made me realise we have to > solve the core issue and then > submodule.<name>.active = true it too. > Therefore I didn't add extra test case too. When we re-read what you wrote as a proposed log message, you said that adding a new submodule that would match the "submodule.active" pattern will result in redundant configuration entries that are unnecessary. If you perceive it as a problem to be solved (and you do---otherwise you wouldn't have written that there), the existing tests do *not* consider it as a problem, so even if they have tests that does the pattern-based activation, they would not have any tests that make sure they do not create redundant configuration entries, would they? If we fixed that issue, then now we need to make sure that future changes will not break the fix and start adding redundant configuration entries, but because there is no test that does so (otherwise, you wouldn't have found a need to fix that "redundant configuration entries" problem in the first place), the patch that fixes the problem needs to add tests to do so, no? What "redundant" entries do you exactly mean? If it is "we end up with the same submodule.<name>.active appearing twice", then the test should make sure the resulting .git/config file has only one (perhaps with "git config --get-all"), for example. ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v6 0/2] Avoid submodule overwritten and skip redundant active entries 2025-05-20 15:28 ` Junio C Hamano @ 2025-05-24 6:48 ` K Jayatheerth 2025-05-24 6:48 ` [PATCH v6 1/2] submodule: prevent overwriting .gitmodules entry on path reuse K Jayatheerth 2025-05-24 6:48 ` [PATCH v6 2/2] submodule: skip redundant active entries when pattern covers path K Jayatheerth 0 siblings, 2 replies; 24+ messages in thread From: K Jayatheerth @ 2025-05-24 6:48 UTC (permalink / raw) To: gitster; +Cc: git, jayatheerthkulkarni2005, mlell08 This series of patch covers mainly two areas 1. The bug report where after submodule was moved and the path remained same when a new submodule was added then it directly was overwriting the moved submodule as the present submodule since the path matched. 2. The configure_added_submodule was writing submodule.<name>.active entry, even when the new path is already matched by submodule.active patterns. Below is a helper function and 2 new tests with fixes of the above problem. K Jayatheerth (2): submodule: prevent overwriting .gitmodules entry on path reuse submodule: skip redundant active entries when pattern covers path builtin/submodule--helper.c | 60 +++++++++++++++++++++++++++------- t/t7400-submodule-basic.sh | 23 +++++++++++++ t/t7413-submodule-is-active.sh | 15 +++++++++ 3 files changed, 87 insertions(+), 11 deletions(-) -- 2.49.GIT ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v6 1/2] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-24 6:48 ` [PATCH v6 0/2] Avoid submodule overwritten and skip redundant active entries K Jayatheerth @ 2025-05-24 6:48 ` K Jayatheerth 2025-05-24 6:48 ` [PATCH v6 2/2] submodule: skip redundant active entries when pattern covers path K Jayatheerth 1 sibling, 0 replies; 24+ messages in thread From: K Jayatheerth @ 2025-05-24 6:48 UTC (permalink / raw) To: gitster; +Cc: git, jayatheerthkulkarni2005, mlell08 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> --- 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; + } + } + + 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..07951f0c6d 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 + ) +' + + test_done -- 2.49.GIT ^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v6 2/2] submodule: skip redundant active entries when pattern covers path 2025-05-24 6:48 ` [PATCH v6 0/2] Avoid submodule overwritten and skip redundant active entries K Jayatheerth 2025-05-24 6:48 ` [PATCH v6 1/2] submodule: prevent overwriting .gitmodules entry on path reuse K Jayatheerth @ 2025-05-24 6:48 ` K Jayatheerth 2025-05-24 6:54 ` JAYATHEERTH K 1 sibling, 1 reply; 24+ messages in thread From: K Jayatheerth @ 2025-05-24 6:48 UTC (permalink / raw) To: gitster; +Cc: git, jayatheerthkulkarni2005, mlell08 configure_added_submodule always writes an explicit submodule.<name>.active entry, even when the new path is already matched by submodule.active patterns. This leads to unnecessary and cluttered configuration. Introduce a single helper to centralize wildmatch-based pattern lookup. In configure_added_submodule, wrap the active-entry write in a conditional that only fires when that helper reports no existing pattern covers the submodule’s path. Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com> --- builtin/submodule--helper.c | 32 +++++++++++++++++++++----------- t/t7413-submodule-is-active.sh | 15 +++++++++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 9f6df833f0..8872c0fce3 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -32,6 +32,8 @@ #include "advice.h" #include "branch.h" #include "list-objects-filter-options.h" +#include "wildmatch.h" +#include "strbuf.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -3323,6 +3325,24 @@ static int config_submodule_in_gitmodules(const char *name, const char *var, con return ret; } +static int submodule_active_matches_path(const char *path) +{ + const struct string_list *values; + size_t i; + + if (git_config_get_string_multi("submodule.active", &values)) + return 0; + + for (i = 0; i < values->nr; i++) { + const char *pat = values->items[i].string; + if (!wildmatch(pat, path, 0)) + return 1; + } + + return 0; +} + + static void configure_added_submodule(struct add_data *add_data) { char *key; @@ -3370,17 +3390,7 @@ static void configure_added_submodule(struct add_data *add_data) * is_submodule_active(), since that function needs to find * out the value of "submodule.active" again anyway. */ - if (!git_config_get("submodule.active")) { - /* - * If the submodule being added isn't already covered by the - * current configured pathspec, set the submodule's active flag - */ - if (!is_submodule_active(the_repository, add_data->sm_path)) { - key = xstrfmt("submodule.%s.active", add_data->sm_name); - git_config_set_gently(key, "true"); - free(key); - } - } else { + if (!submodule_active_matches_path(add_data->sm_path)) { key = xstrfmt("submodule.%s.active", add_data->sm_name); git_config_set_gently(key, "true"); free(key); diff --git a/t/t7413-submodule-is-active.sh b/t/t7413-submodule-is-active.sh index 9509dc18fd..a42060cac9 100755 --- a/t/t7413-submodule-is-active.sh +++ b/t/t7413-submodule-is-active.sh @@ -124,4 +124,19 @@ test_expect_success 'is-active, submodule.active and submodule add' ' git -C super2 config --get submodule.mod.active ' +test_expect_success 'submodule add skips redundant active entry' ' + git init repo && + ( + cd repo && + git config submodule.active "lib/*" && + git commit --allow-empty -m init && + + git init ../lib-origin && + git -C ../lib-origin commit --allow-empty -m init && + + git submodule add ../lib-origin lib/foo && + ! git config --get submodule.lib/foo.active + ) +' + test_done -- 2.49.GIT ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH v6 2/2] submodule: skip redundant active entries when pattern covers path 2025-05-24 6:48 ` [PATCH v6 2/2] submodule: skip redundant active entries when pattern covers path K Jayatheerth @ 2025-05-24 6:54 ` JAYATHEERTH K 2025-05-24 7:30 ` [PATCH v7 0/2] Avoid submodule overwritten and skip redundant active entries K Jayatheerth 0 siblings, 1 reply; 24+ messages in thread From: JAYATHEERTH K @ 2025-05-24 6:54 UTC (permalink / raw) To: gitster; +Cc: git, mlell08 Since I added the helper back I wrote the tests for the active part of the problem too and separated the patches for clarity. I also ran the tests and it passed the test 9 in t7413 and added test 10. Thank you, -Jayatheerth ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v7 0/2] Avoid submodule overwritten and skip redundant active entries 2025-05-24 6:54 ` JAYATHEERTH K @ 2025-05-24 7:30 ` K Jayatheerth 2025-05-24 7:30 ` [PATCH v7 1/2] The seventeenth batch K Jayatheerth 2025-05-24 7:30 ` [PATCH v7 2/2] submodule: prevent overwriting .gitmodules entry on path reuse K Jayatheerth 0 siblings, 2 replies; 24+ messages in thread From: K Jayatheerth @ 2025-05-24 7:30 UTC (permalink / raw) To: jayatheerthkulkarni2005; +Cc: git, gitster, mlell08 This series of patch covers mainly two areas 1. The bug report where after submodule was moved and the path remained same when a new submodule was added then it directly was overwriting the moved submodule as the present submodule since the path matched. 2. The configure_added_submodule was writing submodule.<name>.active entry, even when the new path is already matched by submodule.active patterns. Below is a helper function and 2 new tests with fixes of the above problem. V7 has added Tab spaces in the test rather than 4 spaces in V6 Junio C Hamano (1): The seventeenth batch K Jayatheerth (1): submodule: prevent overwriting .gitmodules entry on path reuse Documentation/RelNotes/2.50.0.adoc | 19 +++++++++++++++++++ builtin/submodule--helper.c | 28 ++++++++++++++++++++++++++++ t/t7400-submodule-basic.sh | 23 +++++++++++++++++++++++ 3 files changed, 70 insertions(+) -- 2.49.GIT ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v7 1/2] The seventeenth batch 2025-05-24 7:30 ` [PATCH v7 0/2] Avoid submodule overwritten and skip redundant active entries K Jayatheerth @ 2025-05-24 7:30 ` K Jayatheerth 2025-05-24 7:33 ` JAYATHEERTH K 2025-05-24 7:30 ` [PATCH v7 2/2] submodule: prevent overwriting .gitmodules entry on path reuse K Jayatheerth 1 sibling, 1 reply; 24+ messages in thread From: K Jayatheerth @ 2025-05-24 7:30 UTC (permalink / raw) To: jayatheerthkulkarni2005; +Cc: git, gitster, mlell08 From: Junio C Hamano <gitster@pobox.com> Signed-off-by: Junio C Hamano <gitster@pobox.com> --- Documentation/RelNotes/2.50.0.adoc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Documentation/RelNotes/2.50.0.adoc b/Documentation/RelNotes/2.50.0.adoc index bf73de114e..f721ea350d 100644 --- a/Documentation/RelNotes/2.50.0.adoc +++ b/Documentation/RelNotes/2.50.0.adoc @@ -72,6 +72,10 @@ UI, Workflows & Features * The `send-email` documentation has been updated with OAuth2.0 related examples. + * Two of the "scalar" subcommands that add a repository that hasn't + been under "scalar"'s control are taught an option not to enable the + scheduled maintenance on it. + Performance, Internal Implementation, Development Support etc. -------------------------------------------------------------- @@ -157,6 +161,12 @@ Performance, Internal Implementation, Development Support etc. * Build performance fix. + * Teach "git send-email" to also consult `hostname -f` for mail + domain to compute the identity given to SMTP servers. + + * The dependency on the_repository variable has been reduced from the + code paths in "git replay". + Fixes since v2.49 ----------------- @@ -306,6 +316,15 @@ Fixes since v2.49 * Use-after-free fix in the sequencer. (merge 5dbaec628d pw/sequencer-reflog-use-after-free later to maint). + * win+Meson CI pipeline, unlike other pipelines for Windows, + used to build artifacts in develper mode, which has been changed to + build them in release mode for consistency. + (merge 184abdcf05 js/ci-build-win-in-release-mode later to maint). + + * CI settings at GitLab has been updated to run MSVC based Meson job + automatically (as opposed to be done only upon manual request). + (merge 6389579b2f ps/ci-gitlab-enable-msvc-meson-job later to maint). + * Other code cleanup, docfix, build fix, etc. (merge 227c4f33a0 ja/doc-block-delimiter-markup-fix later to maint). (merge 2bfd3b3685 ab/decorate-code-cleanup later to maint). -- 2.49.GIT ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH v7 1/2] The seventeenth batch 2025-05-24 7:30 ` [PATCH v7 1/2] The seventeenth batch K Jayatheerth @ 2025-05-24 7:33 ` JAYATHEERTH K 0 siblings, 0 replies; 24+ messages in thread From: JAYATHEERTH K @ 2025-05-24 7:33 UTC (permalink / raw) To: jayatheerthkulkarni2005; +Cc: git, gitster, mlell08 Oh my god I'm so clumsy today Sorry bout that will send in a new thread. -Jayatheerth ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v7 2/2] submodule: prevent overwriting .gitmodules entry on path reuse 2025-05-24 7:30 ` [PATCH v7 0/2] Avoid submodule overwritten and skip redundant active entries K Jayatheerth 2025-05-24 7:30 ` [PATCH v7 1/2] The seventeenth batch K Jayatheerth @ 2025-05-24 7:30 ` K Jayatheerth 1 sibling, 0 replies; 24+ messages in thread From: K Jayatheerth @ 2025-05-24 7:30 UTC (permalink / raw) To: jayatheerthkulkarni2005; +Cc: git, gitster, mlell08 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> --- 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; + } + } + + 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 + ) +' + + test_done -- 2.49.GIT ^ permalink raw reply related [flat|nested] 24+ messages in thread
end of thread, other threads:[~2025-05-24 7:33 UTC | newest] Thread overview: 24+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-05-10 5:45 [PATCH] submodule: prevent overwriting .gitmodules entry on path reuse K Jayatheerth 2025-05-10 5:57 ` JAYATHEERTH K 2025-05-12 12:32 ` Junio C Hamano 2025-05-12 13:26 ` JAYATHEERTH K 2025-05-13 3:34 ` [PATCH v2] " K Jayatheerth 2025-05-13 21:44 ` Junio C Hamano 2025-05-14 2:01 ` [PATCH v3] " K Jayatheerth 2025-05-14 22:47 ` Junio C Hamano 2025-05-16 8:51 ` JAYATHEERTH K 2025-05-16 17:49 ` [PATCH v4] " K Jayatheerth 2025-05-16 17:53 ` JAYATHEERTH K 2025-05-18 7:54 ` [PATCH v5] " K Jayatheerth 2025-05-18 7:58 ` JAYATHEERTH K 2025-05-19 15:41 ` Junio C Hamano 2025-05-20 1:31 ` JAYATHEERTH K 2025-05-20 15:28 ` Junio C Hamano 2025-05-24 6:48 ` [PATCH v6 0/2] Avoid submodule overwritten and skip redundant active entries K Jayatheerth 2025-05-24 6:48 ` [PATCH v6 1/2] submodule: prevent overwriting .gitmodules entry on path reuse K Jayatheerth 2025-05-24 6:48 ` [PATCH v6 2/2] submodule: skip redundant active entries when pattern covers path K Jayatheerth 2025-05-24 6:54 ` JAYATHEERTH K 2025-05-24 7:30 ` [PATCH v7 0/2] Avoid submodule overwritten and skip redundant active entries K Jayatheerth 2025-05-24 7:30 ` [PATCH v7 1/2] The seventeenth batch K Jayatheerth 2025-05-24 7:33 ` JAYATHEERTH K 2025-05-24 7:30 ` [PATCH v7 2/2] submodule: prevent overwriting .gitmodules entry on path reuse K Jayatheerth
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).