* [PATCH 0/3] submodule: support worktree-specific configuration
@ 2026-11-12 12:03 Yoichi NAKAYAMA via GitGitGadget
2026-11-12 12:03 ` [PATCH 1/3] submodule: make submodule.<name>.gitdir relative for worktrees Yoichi NAKAYAMA via GitGitGadget
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Yoichi NAKAYAMA via GitGitGadget @ 2026-11-12 12:03 UTC (permalink / raw)
To: git; +Cc: Yoichi NAKAYAMA
When extensions.worktreeConfig is enabled, this series stores submodule
settings that can differ between worktrees in the worktree config.
The changes include:
* [1/3] Keep submodule.<name>.gitdir relative for linked worktrees.
* [2/3] Store submodule settings in the worktree config when
extensions.worktreeConfig is enabled.
* [3/3] Make git submodule init copy an existing gitdir setting to the
current worktree config only when it belongs to that worktree.
Yoichi NAKAYAMA (3):
submodule: make submodule.<name>.gitdir relative for worktrees
submodule: store settings in worktree config when enabled
submodule init: copy submodule.<name>.gitdir to config.worktree
Documentation/config/submodule.adoc | 3 +-
builtin/submodule--helper.c | 197 +++++++++++++++------
builtin/worktree.c | 23 +++
t/t2405-worktree-submodule.sh | 129 ++++++++++++++
t/t7425-submodule-gitdir-path-extension.sh | 131 ++++++++++++++
5 files changed, 431 insertions(+), 52 deletions(-)
base-commit: fbd2d1e765da4d2931cadc4c62d3ca5d5b590e7e
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2306%2Fyoichi%2Fsubmodule-with-worktree-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2306/yoichi/submodule-with-worktree-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2306
--
gitgitgadget
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/3] submodule: make submodule.<name>.gitdir relative for worktrees
2026-11-12 12:03 [PATCH 0/3] submodule: support worktree-specific configuration Yoichi NAKAYAMA via GitGitGadget
@ 2026-11-12 12:03 ` Yoichi NAKAYAMA via GitGitGadget
2026-11-12 12:03 ` [PATCH 2/3] submodule: store settings in worktree config when enabled Yoichi NAKAYAMA via GitGitGadget
2026-11-12 12:03 ` [PATCH 3/3] submodule init: copy submodule.<name>.gitdir to config.worktree Yoichi NAKAYAMA via GitGitGadget
2 siblings, 0 replies; 4+ messages in thread
From: Yoichi NAKAYAMA via GitGitGadget @ 2026-11-12 12:03 UTC (permalink / raw)
To: git; +Cc: Yoichi NAKAYAMA, Yoichi NAKAYAMA
From: Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>
For a linked worktree, always keep `submodule.<name>.gitdir` relative
to ensure it continues to work when the worktree is moved along with
the main worktree. (This benefit also requires
worktree.useRelativePaths to be set to true.)
Signed-off-by: Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>
---
builtin/submodule--helper.c | 68 +++++++++++++---------
t/t7425-submodule-gitdir-path-extension.sh | 19 ++++++
2 files changed, 61 insertions(+), 26 deletions(-)
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 461b682d0d..56d7720e93 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -37,6 +37,7 @@
#include "wildmatch.h"
#include "strbuf.h"
#include "url.h"
+#include "worktree.h"
#define OPT_QUIET (1 << 0)
#define OPT_CACHED (1 << 1)
@@ -507,50 +508,60 @@ static int validate_and_set_submodule_gitdir(struct strbuf *gitdir_path,
static void create_default_gitdir_config(const char *submodule_name)
{
+ struct worktree *wt = get_current_worktree(the_repository);
+ const char *modules_dir;
+ struct strbuf tmp = STRBUF_INIT;
struct strbuf gitdir_path = STRBUF_INIT;
struct git_hash_ctx ctx;
char hex_name_hash[GIT_MAX_HEXSZ + 1], header[128];
unsigned char raw_name_hash[GIT_MAX_RAWSZ];
int header_len;
- /* Case 1: try the plain module name */
- repo_git_path_append(the_repository, &gitdir_path, "modules/%s", submodule_name);
- if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) {
- strbuf_release(&gitdir_path);
- return;
+ modules_dir = worktree_git_path(wt, "modules/");
+ if (is_absolute_path(modules_dir)) {
+ modules_dir = relative_path(modules_dir, wt->path, &tmp);
}
+ /* Case 1: try the plain module name */
+ strbuf_addstr(&gitdir_path, modules_dir);
+ strbuf_addstr(&gitdir_path, submodule_name);
+ if (!validate_and_set_submodule_gitdir(&gitdir_path,
+ submodule_name))
+ goto cleanup;
+
/* Case 2.1: Try URI-safe (RFC3986) encoding first, this fixes nested gitdirs */
strbuf_reset(&gitdir_path);
- repo_git_path_append(the_repository, &gitdir_path, "modules/");
+ strbuf_addstr(&gitdir_path, modules_dir);
strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_rfc3986_unreserved);
- if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) {
- strbuf_release(&gitdir_path);
- return;
- }
+ if (!validate_and_set_submodule_gitdir(&gitdir_path,
+ submodule_name))
+ goto cleanup;
/* Case 2.2: Try extended uppercase URI (RFC3986) encoding, to fix case-folding */
strbuf_reset(&gitdir_path);
- repo_git_path_append(the_repository, &gitdir_path, "modules/");
+ strbuf_addstr(&gitdir_path, modules_dir);
strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_casefolding_rfc3986_unreserved);
- if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name))
- return;
+ if (!validate_and_set_submodule_gitdir(&gitdir_path,
+ submodule_name))
+ goto cleanup;
/* Case 2.3: Try some derived gitdir names, see if one sticks */
for (char c = '0'; c <= '9'; c++) {
strbuf_reset(&gitdir_path);
- repo_git_path_append(the_repository, &gitdir_path, "modules/");
+ strbuf_addstr(&gitdir_path, modules_dir);
strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_rfc3986_unreserved);
strbuf_addch(&gitdir_path, c);
- if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name))
- return;
+ if (!validate_and_set_submodule_gitdir(&gitdir_path,
+ submodule_name))
+ goto cleanup;
strbuf_reset(&gitdir_path);
- repo_git_path_append(the_repository, &gitdir_path, "modules/");
+ strbuf_addstr(&gitdir_path, modules_dir);
strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_casefolding_rfc3986_unreserved);
strbuf_addch(&gitdir_path, c);
- if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name))
- return;
+ if (!validate_and_set_submodule_gitdir(&gitdir_path,
+ submodule_name))
+ goto cleanup;
}
/* Case 2.4: If all the above failed, try a hash of the name as a last resort */
@@ -563,17 +574,22 @@ static void create_default_gitdir_config(const char *submodule_name)
git_hash_final(raw_name_hash, &ctx);
hash_to_hex_algop_r(hex_name_hash, raw_name_hash, the_hash_algo);
strbuf_reset(&gitdir_path);
- repo_git_path_append(the_repository, &gitdir_path, "modules/%s", hex_name_hash);
- if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) {
- strbuf_release(&gitdir_path);
- return;
- }
+ strbuf_addstr(&gitdir_path, modules_dir);
+ strbuf_addstr(&gitdir_path, hex_name_hash);
+ if (!validate_and_set_submodule_gitdir(&gitdir_path,
+ submodule_name))
+ goto cleanup;
/* Case 3: nothing worked, error out */
die(_("failed to set a valid default config for 'submodule.%s.gitdir'. "
"Please ensure it is set, for example by running something like: "
- "'git config submodule.%s.gitdir .git/modules/%s'"),
- submodule_name, submodule_name, submodule_name);
+ "'git config submodule.%s.gitdir %s%s'"),
+ submodule_name, submodule_name, modules_dir, submodule_name);
+
+cleanup:
+ strbuf_release(&gitdir_path);
+ strbuf_release(&tmp);
+ free_worktree(wt);
}
static void init_submodule(const char *path, const char *prefix,
diff --git a/t/t7425-submodule-gitdir-path-extension.sh b/t/t7425-submodule-gitdir-path-extension.sh
index ea86ecf7ee..02123b8468 100755
--- a/t/t7425-submodule-gitdir-path-extension.sh
+++ b/t/t7425-submodule-gitdir-path-extension.sh
@@ -279,6 +279,25 @@ test_expect_success '`git clone --recurse-submodules` respects init.defaultSubmo
)
'
+test_expect_success 'git submodule init make gitdir relative' '
+ test_when_finished "rm -rf repo-clone-init-wt" &&
+ git clone upstream repo-clone-init-wt &&
+ (
+ cd repo-clone-init-wt &&
+ git config core.repositoryformatversion 1 &&
+ git config extensions.submodulePathConfig true &&
+ git worktree add wt &&
+ cd wt &&
+ git submodule init &&
+ git config submodule.sub1.gitdir >actual &&
+ echo "../.git/worktrees/wt/modules/sub1" >expect &&
+ test_cmp expect actual &&
+ git config submodule.sub2.gitdir >actual &&
+ echo "../.git/worktrees/wt/modules/sub2" >expect &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success 'submodule--helper migrates legacy modules' '
(
cd upstream &&
--
gitgitgadget
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/3] submodule: store settings in worktree config when enabled
2026-11-12 12:03 [PATCH 0/3] submodule: support worktree-specific configuration Yoichi NAKAYAMA via GitGitGadget
2026-11-12 12:03 ` [PATCH 1/3] submodule: make submodule.<name>.gitdir relative for worktrees Yoichi NAKAYAMA via GitGitGadget
@ 2026-11-12 12:03 ` Yoichi NAKAYAMA via GitGitGadget
2026-11-12 12:03 ` [PATCH 3/3] submodule init: copy submodule.<name>.gitdir to config.worktree Yoichi NAKAYAMA via GitGitGadget
2 siblings, 0 replies; 4+ messages in thread
From: Yoichi NAKAYAMA via GitGitGadget @ 2026-11-12 12:03 UTC (permalink / raw)
To: git; +Cc: Yoichi NAKAYAMA, Yoichi NAKAYAMA
From: Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>
Submodule URLs can differ between branches. For example, one branch
may use the upstream repository while another uses a fork for the same
submodule. When these branches are checked out in separate worktrees,
storing `submodule.<name>.url` in the shared repository config
prevents each worktree from using its own URL.
Store submodule settings such as `submodule.<name>.active` and
`submodule.<name>.url` in the worktree config when
`extensions.worktreeConfig` is enabled.
Settings shared between worktrees can remain in the repository
config. When such a setting exists there, 'git submodule deinit' sets
`submodule.<name>.active` to false in the worktree config to
unregister the submodule.
Unlike other settings, `submodule.<name>.gitdir` should not be shared
between worktrees, so exclude it from the config copied by 'git
worktree add'.
Signed-off-by: Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>
---
Documentation/config/submodule.adoc | 3 +-
builtin/submodule--helper.c | 43 ++++---
builtin/worktree.c | 23 ++++
t/t2405-worktree-submodule.sh | 129 +++++++++++++++++++++
t/t7425-submodule-gitdir-path-extension.sh | 49 ++++++++
5 files changed, 227 insertions(+), 20 deletions(-)
diff --git a/Documentation/config/submodule.adoc b/Documentation/config/submodule.adoc
index 4c424017b4..1e294147db 100644
--- a/Documentation/config/submodule.adoc
+++ b/Documentation/config/submodule.adoc
@@ -58,7 +58,8 @@ submodule.<name>.gitdir::
respected when `extensions.submodulePathConfig` is enabled, otherwise it
has no effect. When enabled, this config becomes the single source of
truth for submodule gitdir paths and Git will error if it is missing.
- See linkgit:git-config[1] for details.
+ When `extensions.worktreeConfig` is enabled, this setting is stored in
+ each worktree's config. See linkgit:git-config[1] for details.
submodule.active::
A repeated field which contains a pathspec used to match against a
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 56d7720e93..f249582ca6 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -497,7 +497,7 @@ static int validate_and_set_submodule_gitdir(struct strbuf *gitdir_path,
return 0;
}
- if (repo_config_set_gently(the_repository, key, gitdir_path->buf)) {
+ if (repo_config_set_worktree_gently(the_repository, key, gitdir_path->buf)) {
free(key);
return -1;
}
@@ -609,15 +609,10 @@ static void init_submodule(const char *path, const char *prefix,
die(_("No url found for submodule path '%s' in .gitmodules"),
displaypath);
- /*
- * NEEDSWORK: In a multi-working-tree world, this needs to be
- * set in the per-worktree config.
- *
- * Set active flag for the submodule being initialized
- */
+ /* Set active flag for the submodule being initialized */
if (!is_submodule_active(the_repository, path)) {
strbuf_addf(&sb, "submodule.%s.active", sub->name);
- repo_config_set_gently(the_repository, sb.buf, "true");
+ repo_config_set_worktree_gently(the_repository, sb.buf, "true");
strbuf_reset(&sb);
}
@@ -643,7 +638,7 @@ static void init_submodule(const char *path, const char *prefix,
free(oldurl);
}
- if (repo_config_set_gently(the_repository, sb.buf, url))
+ if (repo_config_set_worktree_gently(the_repository, sb.buf, url))
die(_("Failed to register url for submodule path '%s'"),
displaypath);
if (!(flags & OPT_QUIET))
@@ -665,7 +660,7 @@ static void init_submodule(const char *path, const char *prefix,
upd = submodule_update_type_to_string(sub->update_strategy.type);
}
- if (repo_config_set_gently(the_repository, sb.buf, upd))
+ if (repo_config_set_worktree_gently(the_repository, sb.buf, upd))
die(_("Failed to register update mode for submodule path '%s'"), displaypath);
}
@@ -1491,7 +1486,7 @@ static void sync_submodule(const char *path, const char *prefix,
strbuf_reset(&sb);
strbuf_addf(&sb, "submodule.%s.url", sub->name);
- if (repo_config_set_gently(the_repository, sb.buf, super_config_url))
+ if (repo_config_set_worktree_gently(the_repository, sb.buf, super_config_url))
die(_("failed to register url for submodule path '%s'"),
displaypath);
@@ -1670,17 +1665,31 @@ static void deinit_submodule(const char *path, const char *prefix,
/* remove the .git/config entries (unless the user already did it) */
if (!capture_command(&cp_config, &sb_config, 0) && sb_config.len) {
+ char *file = the_repository->repository_format_worktree_config ?
+ repo_git_path(the_repository, "config.worktree") :
+ repo_git_path(the_repository, "config");
char *sub_key = xstrfmt("submodule.%s", sub->name);
/*
* remove the whole section so we have a clean state when
* the user later decides to init this submodule again
*/
- repo_config_rename_section_in_file(the_repository, NULL, sub_key, NULL);
+ repo_config_rename_section_in_file(the_repository, file, sub_key, NULL);
if (!(flags & OPT_QUIET))
printf(_("Submodule '%s' (%s) unregistered for path '%s'\n"),
sub->name, sub->url, displaypath);
+ free(file);
free(sub_key);
+
+ if (the_repository->repository_format_worktree_config) {
+ char *key = xstrfmt("submodule.%s.active", sub->name);
+
+ repo_config_clear(the_repository);
+ if (is_submodule_active(the_repository, sub->name))
+ repo_config_set_worktree_gently(the_repository,
+ key, "false");
+ free(key);
+ }
}
cleanup:
@@ -3580,7 +3589,7 @@ static void configure_added_submodule(struct add_data *add_data)
int matched = 0;
key = xstrfmt("submodule.%s.url", add_data->sm_name);
- repo_config_set_gently(the_repository, key, add_data->realrepo);
+ repo_config_set_worktree_gently(the_repository, key, add_data->realrepo);
free(key);
add_submod.git_cmd = 1;
@@ -3610,10 +3619,6 @@ static void configure_added_submodule(struct add_data *add_data)
if (run_command(&add_gitmodules))
die(_("Failed to register submodule '%s'"), add_data->sm_path);
- /*
- * 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
@@ -3627,7 +3632,7 @@ static void configure_added_submodule(struct add_data *add_data)
* current configured pathspec, set the submodule's active flag
*/
key = xstrfmt("submodule.%s.active", add_data->sm_name);
- repo_config_set_gently(the_repository, key, "true");
+ repo_config_set_worktree_gently(the_repository, key, "true");
free(key);
} else {
for (size_t i = 0; i < values->nr; i++) {
@@ -3639,7 +3644,7 @@ static void configure_added_submodule(struct add_data *add_data)
}
if (!matched) { /* no pattern matched -> force-enable */
key = xstrfmt("submodule.%s.active", add_data->sm_name);
- repo_config_set_gently(the_repository, key, "true");
+ repo_config_set_worktree_gently(the_repository, key, "true");
free(key);
}
}
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 77ecd0f71f..ef03ef979a 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -363,6 +363,9 @@ static void copy_filtered_worktree_config(const char *worktree_git_dir)
if (file_exists(from_file)) {
struct config_set cs = { { 0 } };
int bare;
+ struct string_list keys = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
+ int i;
if (safe_create_leading_directories(the_repository, to_file) ||
copy_file(the_repository, to_file, from_file, 0666)) {
@@ -386,6 +389,26 @@ static void copy_filtered_worktree_config(const char *worktree_git_dir)
error(_("failed to unset '%s' in '%s'"),
"core.worktree", to_file);
+ /* unset "submodule.*.gitdir" */
+ for (i = 0; i < cs.list.nr; i++) {
+ const char *var = cs.list.items[i].e->key;
+ const char *subsection, *key;
+ size_t subsection_len;
+
+ if (!parse_config_key(var, "submodule",
+ &subsection, &subsection_len, &key) &&
+ subsection && !strcmp(key, "gitdir"))
+ string_list_insert(&keys, var);
+ }
+ for_each_string_list_item(item, &keys) {
+ if (repo_config_set_in_file_gently(the_repository,
+ to_file, item->string,
+ NULL, NULL))
+ error(_("failed to unset '%s' in '%s'"),
+ item->string, to_file);
+ }
+ string_list_clear(&keys, 0);
+
git_configset_clear(&cs);
}
diff --git a/t/t2405-worktree-submodule.sh b/t/t2405-worktree-submodule.sh
index f5c94d490b..d46d565ad6 100755
--- a/t/t2405-worktree-submodule.sh
+++ b/t/t2405-worktree-submodule.sh
@@ -91,4 +91,133 @@ test_expect_success 'unsetting core.worktree does not prevent running commands d
git -C main/.git/worktrees/checkout-recurse/modules/sub log
'
+test_expect_success 'submodule config in worktree config' '
+ test_when_finished "rm -rf wt-config" &&
+ git clone "$base_path/origin/main" wt-config &&
+ (
+ cd wt-config &&
+ git config extensions.worktreeConfig true &&
+ git worktree add wt &&
+ cat .git/config >.git/expect-config &&
+
+ git submodule update --init &&
+ test_cmp .git/expect-config .git/config &&
+ echo "true" >expect &&
+ git config --worktree submodule.sub.active >actual &&
+ test_cmp expect actual &&
+ echo "$base_path/origin/sub" >expect &&
+ git config --worktree submodule.sub.url >actual &&
+ test_cmp expect actual &&
+
+ git submodule deinit sub &&
+ test_cmp .git/expect-config .git/config &&
+ test_must_be_empty .git/config.worktree &&
+
+ git submodule add ../sub sub2 &&
+ test_cmp .git/expect-config .git/config &&
+ echo "true" >expect &&
+ git config --worktree submodule.sub2.active >actual &&
+ test_cmp expect actual &&
+ echo "$base_path/origin/sub" >expect &&
+ git config --worktree submodule.sub2.url >actual &&
+ test_cmp expect actual &&
+
+ git config --file=.gitmodules submodule.sub2.url new_url &&
+ git submodule sync &&
+ test_cmp .git/expect-config .git/config &&
+ echo "new_url" >expect &&
+ git config --worktree submodule.sub2.url >actual &&
+ test_cmp expect actual &&
+
+ cd wt &&
+
+ git submodule update --init &&
+ test_cmp ../.git/expect-config ../.git/config &&
+ echo "true" >expect &&
+ git config --worktree submodule.sub.active >actual &&
+ test_cmp expect actual &&
+ echo "$base_path/origin/sub" >expect &&
+ git config --worktree submodule.sub.url >actual &&
+ test_cmp expect actual &&
+
+ git submodule deinit sub &&
+ test_cmp ../.git/expect-config ../.git/config &&
+ test_must_be_empty ../.git/worktrees/wt/config.worktree &&
+
+ git submodule add ../sub sub2 &&
+ test_cmp ../.git/expect-config ../.git/config &&
+ echo "true" >expect &&
+ git config --worktree submodule.sub2.active >actual &&
+ test_cmp expect actual &&
+ echo "$base_path/origin/sub" >expect &&
+ git config --worktree submodule.sub2.url >actual &&
+ test_cmp expect actual &&
+
+ git config --file=.gitmodules submodule.sub2.url new_url &&
+ git submodule sync &&
+ test_cmp ../.git/expect-config ../.git/config &&
+ echo "new_url" >expect &&
+ git config --worktree submodule.sub2.url >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'deinit overwrites active config' '
+ test_when_finished "rm -rf repo-wt-config" &&
+ git clone "$base_path/origin/main" repo-wt-config &&
+ (
+ cd repo-wt-config &&
+ git submodule update --init &&
+ git config extensions.worktreeConfig true &&
+ git worktree add wt &&
+ cat .git/config >.git/expect-config &&
+
+ git submodule deinit sub &&
+ test_cmp .git/expect-config .git/config &&
+ echo "false" >expect &&
+ git config --worktree submodule.sub.active >actual &&
+ test_cmp expect actual &&
+
+ cd wt &&
+
+ git submodule deinit sub &&
+ test_cmp ../.git/expect-config ../.git/config &&
+ echo "false" >expect &&
+ git config --worktree submodule.sub.active >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'do not copy gitdir in config.worktree' '
+ test_when_finished "rm -rf copy-wt-config" &&
+ git clone "$base_path/origin/main" copy-wt-config &&
+ (
+ cd copy-wt-config &&
+ git config core.repositoryformatversion 1 &&
+ git config extensions.submodulePathConfig true &&
+ git config extensions.worktreeConfig true &&
+
+ git submodule update --init &&
+ echo "true" >expect &&
+ git config --worktree submodule.sub.active >actual &&
+ test_cmp expect actual &&
+ echo "$base_path/origin/sub" >expect &&
+ git config --worktree submodule.sub.url >actual &&
+ test_cmp expect actual &&
+ echo ".git/modules/sub" >expect &&
+ git config --worktree submodule.sub.gitdir >actual &&
+ test_cmp expect actual &&
+
+ git worktree add wt &&
+ cd wt &&
+ echo "true" >expect &&
+ git config --worktree submodule.sub.active >actual &&
+ test_cmp expect actual &&
+ echo "$base_path/origin/sub" >expect &&
+ git config --worktree submodule.sub.url >actual &&
+ test_cmp expect actual &&
+ test_must_fail git config --worktree submodule.sub.gitdir
+ )
+'
+
test_done
diff --git a/t/t7425-submodule-gitdir-path-extension.sh b/t/t7425-submodule-gitdir-path-extension.sh
index 02123b8468..3bae4c4ef1 100755
--- a/t/t7425-submodule-gitdir-path-extension.sh
+++ b/t/t7425-submodule-gitdir-path-extension.sh
@@ -78,6 +78,55 @@ test_expect_success 'gitdir config path is relative for both absolute and relati
)
'
+test_expect_success 'gitdir for the main worktree is stored in worktree config' '
+ test_when_finished "rm -rf relative-cfg-path-test-main" &&
+ git init -b main relative-cfg-path-test-main &&
+ (
+ cd relative-cfg-path-test-main &&
+ git config core.repositoryformatversion 1 &&
+ git config extensions.submodulePathConfig true &&
+ git config extensions.worktreeConfig true &&
+
+ # Test with absolute URL
+ git submodule add "$TRASH_DIRECTORY/new-sub" sub-abs &&
+ git config --worktree submodule.sub-abs.gitdir >actual &&
+ echo ".git/modules/sub-abs" >expect &&
+ test_cmp expect actual &&
+
+ # Test with relative URL
+ git submodule add ../new-sub sub-rel &&
+ git config --worktree submodule.sub-rel.gitdir >actual &&
+ echo ".git/modules/sub-rel" >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'gitdir for a linked worktree is stored in worktree config' '
+ test_when_finished "rm -rf relative-cfg-path-test-wt" &&
+ git init -b main relative-cfg-path-test-wt &&
+ (
+ cd relative-cfg-path-test-wt &&
+ git config core.repositoryformatversion 1 &&
+ git config extensions.submodulePathConfig true &&
+ git config extensions.worktreeConfig true &&
+
+ git worktree add wt &&
+ cd wt &&
+
+ # Test with absolute URL
+ git submodule add "$TRASH_DIRECTORY/new-sub" sub-abs &&
+ git config --worktree submodule.sub-abs.gitdir >actual &&
+ echo "../.git/worktrees/wt/modules/sub-abs" >expect &&
+ test_cmp expect actual &&
+
+ # Test with relative URL
+ git submodule add ../../new-sub sub-rel &&
+ git config --worktree submodule.sub-rel.gitdir >actual &&
+ echo "../.git/worktrees/wt/modules/sub-rel" >expect &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success 'clone from repo with both legacy and new-style submodules' '
git clone --recurse-submodules main cloned-non-extension &&
(
--
gitgitgadget
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 3/3] submodule init: copy submodule.<name>.gitdir to config.worktree
2026-11-12 12:03 [PATCH 0/3] submodule: support worktree-specific configuration Yoichi NAKAYAMA via GitGitGadget
2026-11-12 12:03 ` [PATCH 1/3] submodule: make submodule.<name>.gitdir relative for worktrees Yoichi NAKAYAMA via GitGitGadget
2026-11-12 12:03 ` [PATCH 2/3] submodule: store settings in worktree config when enabled Yoichi NAKAYAMA via GitGitGadget
@ 2026-11-12 12:03 ` Yoichi NAKAYAMA via GitGitGadget
2 siblings, 0 replies; 4+ messages in thread
From: Yoichi NAKAYAMA via GitGitGadget @ 2026-11-12 12:03 UTC (permalink / raw)
To: git; +Cc: Yoichi NAKAYAMA, Yoichi NAKAYAMA
From: Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>
If a repository already has gitdir settings in its repository config
when extensions.worktreeConfig is enabled, those settings belong to a
particular worktree. Make 'git submodule init' copy an existing
setting to the worktree config if it belongs to the current worktree;
otherwise, generate a new setting.
Signed-off-by: Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>
---
builtin/submodule--helper.c | 100 ++++++++++++++++++---
t/t7425-submodule-gitdir-path-extension.sh | 65 +++++++++++++-
2 files changed, 151 insertions(+), 14 deletions(-)
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index f249582ca6..f34a22238f 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -480,10 +480,41 @@ struct init_cb {
};
#define INIT_CB_INIT { 0 }
+static int validate_submodule_git_dir_with_worktree(const char *gitdir)
+{
+ struct strbuf tmp = STRBUF_INIT;
+ struct strbuf submodule = STRBUF_INIT;
+ char *super;
+ int ret = 1;
+
+ if (is_absolute_path(gitdir))
+ strbuf_addstr(&submodule, gitdir);
+ else if (!repo_worktree_path_append(the_repository, &submodule,
+ "%s", gitdir))
+ return -1;
+
+ if (strbuf_normalize_path(&submodule)) {
+ strbuf_release(&submodule);
+ return -1;
+ }
+
+ super = absolute_pathdup(repo_get_git_dir(the_repository));
+
+ if (starts_with(relative_path(submodule.buf, super, &tmp), "modules/"))
+ ret = 0;
+
+ strbuf_release(&submodule);
+ strbuf_release(&tmp);
+ free(super);
+ return ret;
+}
+
static int validate_and_set_submodule_gitdir(struct strbuf *gitdir_path,
- const char *submodule_name)
+ const char *submodule_name,
+ struct worktree *wt)
{
- const char *value;
+ const char *value = gitdir_path->buf;
+ char *value_to_free = NULL;
char *key;
if (validate_submodule_git_dir(gitdir_path->buf, submodule_name))
@@ -491,18 +522,55 @@ static int validate_and_set_submodule_gitdir(struct strbuf *gitdir_path,
key = xstrfmt("submodule.%s.gitdir", submodule_name);
- /* Nothing to do if the config already exists. */
- if (!repo_config_get_string_tmp(the_repository, key, &value)) {
- free(key);
- return 0;
+ if (the_repository->repository_format_worktree_config) {
+ struct config_set cs = { 0 };
+ const char *filename = worktree_git_path(wt, "config.worktree");
+ char *tmp;
+
+ git_configset_init(&cs);
+ git_configset_add_file(&cs, filename);
+
+ /*
+ * Nothing to do if the config already exists in the config set
+ * for the current worktree.
+ */
+ if (!git_configset_get(&cs, key)) {
+ git_configset_clear(&cs);
+ free(key);
+ return 0;
+ }
+
+ git_configset_clear(&cs);
+
+ /*
+ * If the worktree config does not have this key, check the
+ * effective value from lower-priority config scopes and copy
+ * it to the worktree config if it belongs to the current
+ * worktree.
+ */
+ if (!repo_config_get_string(the_repository, key, &tmp)) {
+ if (validate_submodule_git_dir_with_worktree(tmp) ||
+ validate_submodule_git_dir(tmp, submodule_name))
+ free(tmp);
+ else
+ value = value_to_free = tmp;
+ }
+ } else {
+ /* Nothing to do if the config already exists. */
+ if (!repo_config_get_string_tmp(the_repository, key, &value)) {
+ free(key);
+ return 0;
+ }
}
- if (repo_config_set_worktree_gently(the_repository, key, gitdir_path->buf)) {
+ if (repo_config_set_worktree_gently(the_repository, key, value)) {
free(key);
+ free(value_to_free);
return -1;
}
free(key);
+ free(value_to_free);
return 0;
}
@@ -526,7 +594,8 @@ static void create_default_gitdir_config(const char *submodule_name)
strbuf_addstr(&gitdir_path, modules_dir);
strbuf_addstr(&gitdir_path, submodule_name);
if (!validate_and_set_submodule_gitdir(&gitdir_path,
- submodule_name))
+ submodule_name,
+ wt))
goto cleanup;
/* Case 2.1: Try URI-safe (RFC3986) encoding first, this fixes nested gitdirs */
@@ -534,7 +603,8 @@ static void create_default_gitdir_config(const char *submodule_name)
strbuf_addstr(&gitdir_path, modules_dir);
strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_rfc3986_unreserved);
if (!validate_and_set_submodule_gitdir(&gitdir_path,
- submodule_name))
+ submodule_name,
+ wt))
goto cleanup;
/* Case 2.2: Try extended uppercase URI (RFC3986) encoding, to fix case-folding */
@@ -542,7 +612,8 @@ static void create_default_gitdir_config(const char *submodule_name)
strbuf_addstr(&gitdir_path, modules_dir);
strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_casefolding_rfc3986_unreserved);
if (!validate_and_set_submodule_gitdir(&gitdir_path,
- submodule_name))
+ submodule_name,
+ wt))
goto cleanup;
/* Case 2.3: Try some derived gitdir names, see if one sticks */
@@ -552,7 +623,8 @@ static void create_default_gitdir_config(const char *submodule_name)
strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_rfc3986_unreserved);
strbuf_addch(&gitdir_path, c);
if (!validate_and_set_submodule_gitdir(&gitdir_path,
- submodule_name))
+ submodule_name,
+ wt))
goto cleanup;
strbuf_reset(&gitdir_path);
@@ -560,7 +632,8 @@ static void create_default_gitdir_config(const char *submodule_name)
strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_casefolding_rfc3986_unreserved);
strbuf_addch(&gitdir_path, c);
if (!validate_and_set_submodule_gitdir(&gitdir_path,
- submodule_name))
+ submodule_name,
+ wt))
goto cleanup;
}
@@ -577,7 +650,8 @@ static void create_default_gitdir_config(const char *submodule_name)
strbuf_addstr(&gitdir_path, modules_dir);
strbuf_addstr(&gitdir_path, hex_name_hash);
if (!validate_and_set_submodule_gitdir(&gitdir_path,
- submodule_name))
+ submodule_name,
+ wt))
goto cleanup;
/* Case 3: nothing worked, error out */
diff --git a/t/t7425-submodule-gitdir-path-extension.sh b/t/t7425-submodule-gitdir-path-extension.sh
index 3bae4c4ef1..36aac0f578 100755
--- a/t/t7425-submodule-gitdir-path-extension.sh
+++ b/t/t7425-submodule-gitdir-path-extension.sh
@@ -328,7 +328,47 @@ test_expect_success '`git clone --recurse-submodules` respects init.defaultSubmo
)
'
-test_expect_success 'git submodule init make gitdir relative' '
+test_expect_success 'git submodule init inherits related repository config for main' '
+ test_when_finished "rm -rf repo-clone-init-main" &&
+ git clone upstream repo-clone-init-main &&
+ (
+ cd repo-clone-init-main &&
+ git config core.repositoryformatversion 1 &&
+ git config extensions.submodulePathConfig true &&
+ git submodule init &&
+ git config submodule.sub1.gitdir >actual &&
+ echo ".git/modules/sub1" >expect &&
+ test_cmp expect actual &&
+ git config submodule.sub2.gitdir >actual &&
+ echo ".git/modules/sub2" >expect &&
+ test_cmp expect actual &&
+
+ # Modify values so that they can be distinguished from the default
+ git config submodule.sub1.gitdir .git/modules/sub1_custom &&
+ git config submodule.sub2.gitdir .git/modules/sub2_custom &&
+
+ git config extensions.worktreeConfig true &&
+ git submodule init &&
+ git config --worktree submodule.sub1.gitdir >actual &&
+ echo ".git/modules/sub1_custom" >expect &&
+ test_cmp expect actual &&
+ git config --worktree submodule.sub2.gitdir >actual &&
+ echo ".git/modules/sub2_custom" >expect &&
+ test_cmp expect actual &&
+
+ git worktree add wt &&
+ cd wt &&
+ git submodule init &&
+ git config --worktree submodule.sub1.gitdir >actual &&
+ echo "../.git/worktrees/wt/modules/sub1" >expect &&
+ test_cmp expect actual &&
+ git config --worktree submodule.sub2.gitdir >actual &&
+ echo "../.git/worktrees/wt/modules/sub2" >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'git submodule init inherits related repository config for worktree' '
test_when_finished "rm -rf repo-clone-init-wt" &&
git clone upstream repo-clone-init-wt &&
(
@@ -343,6 +383,29 @@ test_expect_success 'git submodule init make gitdir relative' '
test_cmp expect actual &&
git config submodule.sub2.gitdir >actual &&
echo "../.git/worktrees/wt/modules/sub2" >expect &&
+ test_cmp expect actual &&
+
+ # Modify values so that they can be distinguished from the default
+ git config submodule.sub1.gitdir ../.git/worktrees/wt/modules/sub1_custom &&
+ # Use an absolute path to simulate a value written by older Git
+ git config submodule.sub2.gitdir "$(cd .. && pwd -P)/.git/worktrees/wt/modules/sub2_custom" &&
+
+ git config extensions.worktreeConfig true &&
+ git submodule init &&
+ git config --worktree submodule.sub1.gitdir >actual &&
+ echo "../.git/worktrees/wt/modules/sub1_custom" >expect &&
+ test_cmp expect actual &&
+ git config --worktree submodule.sub2.gitdir >actual &&
+ echo "$(cd .. && pwd -P)/.git/worktrees/wt/modules/sub2_custom" >expect &&
+ test_cmp expect actual &&
+
+ cd .. &&
+ git submodule init &&
+ git config --worktree submodule.sub1.gitdir >actual &&
+ echo ".git/modules/sub1" >expect &&
+ test_cmp expect actual &&
+ git config --worktree submodule.sub2.gitdir >actual &&
+ echo ".git/modules/sub2" >expect &&
test_cmp expect actual
)
'
--
gitgitgadget
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-12 12:03 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-12 12:03 [PATCH 0/3] submodule: support worktree-specific configuration Yoichi NAKAYAMA via GitGitGadget
2026-11-12 12:03 ` [PATCH 1/3] submodule: make submodule.<name>.gitdir relative for worktrees Yoichi NAKAYAMA via GitGitGadget
2026-11-12 12:03 ` [PATCH 2/3] submodule: store settings in worktree config when enabled Yoichi NAKAYAMA via GitGitGadget
2026-11-12 12:03 ` [PATCH 3/3] submodule init: copy submodule.<name>.gitdir to config.worktree Yoichi NAKAYAMA via GitGitGadget
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.