{"thread":{"id":"65497","subject":"[PATCH 2/3] worktree: add --recurse-submodules flag to worktree add","startedAt":"2026-04-16T16:35:40Z","lastAt":"2026-04-16T17:27:26Z","messageCount":2,"participants":["Jimmy Aguilar Mena","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"541753","messageId":"aeEPk7m5gwPnmMUZ@RTX","threadId":"65497","inReplyTo":null,"subject":"[PATCH 2/3] worktree: add --recurse-submodules flag to worktree add","fromName":"Jimmy Aguilar Mena","fromEmail":"kratsbinovish@gmail.com","sentAt":"2026-04-16T16:35:36Z","receivedAt":"2026-04-16T16:35:40Z","isPatch":true,"body":"When \"git submodule update --init\" is run inside a linked worktree,\nsubmodule_name_to_gitdir() returns a per-worktree path:\n\n   $GIT_COMMON_DIR/worktrees/<id>/modules/<name>\n\nrather than the main worktree's:\n\n   $GIT_COMMON_DIR/modules/<name>\n\nIf the main worktree already has the submodule cloned, the per-worktree\ngitdir does not yet exist and clone_submodule() falls through to fetch\nfrom the remote URL -- wasting bandwidth and disk space when all the\nobjects are already present locally.\n\nDetect this case: when the target sm_gitdir does not exist, differs\nfrom the common-dir path, and the common-dir path is a valid git\ndirectory, skip the remote clone and instead run:\n\n   git clone --local --no-checkout --separate-git-dir <sm_gitdir> \\\n             <common_sm_gitdir> <working_tree_path>\n\n\"--local\" makes Git use hardlinks for pack files and loose objects, so\nno extra disk space is consumed for data that is already present.  The\nper-worktree gitdir gets its own HEAD, index, and config, just like any\nother worktree, while sharing the object store with the main worktree's\nsubmodule repository.  Mutable per-worktree files (HEAD, refs, index,\nconfig) start out as hardlinks but become independent on the first write\nvia Git's atomic rename(2) approach.\n\nSigned-off-by: Jimmy Aguilar Mena <kratsbinovish@gmail.com>\n---\n  builtin/submodule--helper.c | 54 +++++++++++++++++++++++++++++++++++++\n  1 file changed, 54 insertions(+)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 2f589e3b37..2da59e8c93 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1914,6 +1914,59 @@ static int clone_submodule(const struct module_clone_data *clone_data,\n  \t\tclone_data_path = to_free = xstrfmt(\"%s/%s\", repo_get_work_tree(the_repository),\n  \t\t\t\t\t\t    clone_data->path);\n  \n+\t/*\n+\t * If we are populating a submodule in a linked worktree and the main\n+\t * worktree already has this submodule cloned, reuse its objects via a\n+\t * local clone (hardlinks) instead of fetching from the network.  The\n+\t * common-dir path \"$GIT_COMMON_DIR/modules/<name>\" is where the main\n+\t * worktree stores the submodule gitdir; the per-worktree path returned\n+\t * by submodule_name_to_gitdir() diverges from it only in linked\n+\t * worktrees.\n+\t */\n+\t{\n+\t\tstruct strbuf common_sm_gitdir = STRBUF_INIT;\n+\t\tstrbuf_addf(&common_sm_gitdir, \"%s/modules/%s\",\n+\t\t\t    the_repository->commondir, clone_data->name);\n+\n+\t\tif (!file_exists(sm_gitdir) &&\n+\t\t    strcmp(sm_gitdir, common_sm_gitdir.buf) &&\n+\t\t    is_git_directory(common_sm_gitdir.buf)) {\n+\t\t\t/*\n+\t\t\t * Main worktree has the submodule; reuse it locally.\n+\t\t\t * git clone --local creates hardlinks for pack files so\n+\t\t\t * no extra disk space is needed for existing objects.\n+\t\t\t */\n+\t\t\tif (clone_data->require_init && !stat(clone_data_path, &st) &&\n+\t\t\t    !is_empty_dir(clone_data_path))\n+\t\t\t\tdie(_(\"directory not empty: '%s'\"), clone_data_path);\n+\n+\t\t\tif (safe_create_leading_directories_const(the_repository, sm_gitdir) < 0)\n+\t\t\t\tdie(_(\"could not create directory '%s'\"), sm_gitdir);\n+\n+\t\t\tstrvec_push(&cp.args, \"clone\");\n+\t\t\tstrvec_push(&cp.args, \"--local\");\n+\t\t\tstrvec_push(&cp.args, \"--no-checkout\");\n+\t\t\tif (clone_data->quiet)\n+\t\t\t\tstrvec_push(&cp.args, \"--quiet\");\n+\t\t\tstrvec_pushl(&cp.args, \"--separate-git-dir\", sm_gitdir, NULL);\n+\t\t\tstrvec_push(&cp.args, \"--\");\n+\t\t\tstrvec_push(&cp.args, common_sm_gitdir.buf);\n+\t\t\tstrvec_push(&cp.args, clone_data_path);\n+\n+\t\t\tcp.git_cmd = 1;\n+\t\t\tprepare_submodule_repo_env(&cp.env);\n+\t\t\tcp.no_stdin = 1;\n+\n+\t\t\tif (run_command(&cp))\n+\t\t\t\tdie(_(\"local clone of '%s' into submodule path '%s' failed\"),\n+\t\t\t\t    common_sm_gitdir.buf, clone_data_path);\n+\n+\t\t\tstrbuf_release(&common_sm_gitdir);\n+\t\t\tgoto connect_wt;\n+\t\t}\n+\t\tstrbuf_release(&common_sm_gitdir);\n+\t}\n+\n  \tif (!file_exists(sm_gitdir)) {\n  \t\tif (clone_data->require_init && !stat(clone_data_path, &st) &&\n  \t\t    !is_empty_dir(clone_data_path))\n@@ -2005,6 +2058,7 @@ static int clone_submodule(const struct module_clone_data *clone_data,\n  \t\t    sm_gitdir);\n  \t}\n  \n+connect_wt:\n  \tconnect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0);\n  \n  \tp = repo_submodule_path(the_repository, clone_data_path, \"config\");\n\n\n"},{"id":"541759","messageId":"xmqqpl3y24ub.fsf@gitster.g","threadId":"65497","inReplyTo":"aeEPk7m5gwPnmMUZ@RTX","subject":"Re: [PATCH 2/3] worktree: add --recurse-submodules flag to worktree add","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-16T17:27:24Z","receivedAt":"2026-04-16T17:27:26Z","isPatch":true,"body":"Jimmy Aguilar Mena <kratsbinovish@gmail.com> writes:\n\n> +\t/*\n> +\t * If we are populating a submodule in a linked worktree and the main\n> +\t * worktree already has this submodule cloned, reuse its objects via a\n> +\t * local clone (hardlinks) instead of fetching from the network.  The\n> +\t * common-dir path \"$GIT_COMMON_DIR/modules/<name>\" is where the main\n> +\t * worktree stores the submodule gitdir; the per-worktree path returned\n> +\t * by submodule_name_to_gitdir() diverges from it only in linked\n> +\t * worktrees.\n> +\t */\n> +\t{\n\nI think you identified the right place to hook into.  But instead of\n\"clone --local\", shoudln't you be running \"git worktree add\" here?\n\n\"clone --local\" means you have two logically separate repositories\nfor this single submodule that are used by two worktrees of the\nsuperproject.  If you add a new commit to one, shouldn't that commit\nbecome available in the other?  Two separate repositories created by\n\"clone --local\" will not allow you to do so.\n\n> +\t\tstruct strbuf common_sm_gitdir = STRBUF_INIT;\n> +\t\tstrbuf_addf(&common_sm_gitdir, \"%s/modules/%s\",\n> +\t\t\t    the_repository->commondir, clone_data->name);\n> +\n> +\t\tif (!file_exists(sm_gitdir) &&\n> +\t\t    strcmp(sm_gitdir, common_sm_gitdir.buf) &&\n> +\t\t    is_git_directory(common_sm_gitdir.buf)) {\n> +\t\t\t/*\n> +\t\t\t * Main worktree has the submodule; reuse it locally.\n> +\t\t\t * git clone --local creates hardlinks for pack files so\n> +\t\t\t * no extra disk space is needed for existing objects.\n> +\t\t\t */\n> +\t\t\tif (clone_data->require_init && !stat(clone_data_path, &st) &&\n> +\t\t\t    !is_empty_dir(clone_data_path))\n> +\t\t\t\tdie(_(\"directory not empty: '%s'\"), clone_data_path);\n> +\n> +\t\t\tif (safe_create_leading_directories_const(the_repository, sm_gitdir) < 0)\n> +\t\t\t\tdie(_(\"could not create directory '%s'\"), sm_gitdir);\n> +\n> +\t\t\tstrvec_push(&cp.args, \"clone\");\n> +\t\t\tstrvec_push(&cp.args, \"--local\");\n> +\t\t\tstrvec_push(&cp.args, \"--no-checkout\");\n> +\t\t\tif (clone_data->quiet)\n> +\t\t\t\tstrvec_push(&cp.args, \"--quiet\");\n> +\t\t\tstrvec_pushl(&cp.args, \"--separate-git-dir\", sm_gitdir, NULL);\n> +\t\t\tstrvec_push(&cp.args, \"--\");\n> +\t\t\tstrvec_push(&cp.args, common_sm_gitdir.buf);\n> +\t\t\tstrvec_push(&cp.args, clone_data_path);\n> +\n> +\t\t\tcp.git_cmd = 1;\n> +\t\t\tprepare_submodule_repo_env(&cp.env);\n> +\t\t\tcp.no_stdin = 1;\n> +\n> +\t\t\tif (run_command(&cp))\n> +\t\t\t\tdie(_(\"local clone of '%s' into submodule path '%s' failed\"),\n> +\t\t\t\t    common_sm_gitdir.buf, clone_data_path);\n> +\n> +\t\t\tstrbuf_release(&common_sm_gitdir);\n> +\t\t\tgoto connect_wt;\n> +\t\t}\n> +\t\tstrbuf_release(&common_sm_gitdir);\n> +\t}\n> +\n>   \tif (!file_exists(sm_gitdir)) {\n>   \t\tif (clone_data->require_init && !stat(clone_data_path, &st) &&\n>   \t\t    !is_empty_dir(clone_data_path))\n> @@ -2005,6 +2058,7 @@ static int clone_submodule(const struct module_clone_data *clone_data,\n>   \t\t    sm_gitdir);\n>   \t}\n>   \n> +connect_wt:\n>   \tconnect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0);\n>   \n>   \tp = repo_submodule_path(the_repository, clone_data_path, \"config\");\n"}]}