{"thread":{"id":"64785","subject":"[PATCH] Fetch missing submodule objects from default remote","startedAt":"2026-01-12T21:36:26Z","lastAt":"2026-03-09T23:40:47Z","messageCount":36,"participants":["Nasser Grainawi","Jacob Keller","Ben Knoble","D. Ben Knoble","Junio C Hamano","Ramsay Jones"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"533687","messageId":"20260112213612.2761854-1-nasser.grainawi@oss.qualcomm.com","threadId":"64785","inReplyTo":null,"subject":"[PATCH] Fetch missing submodule objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-01-12T21:36:12Z","receivedAt":"2026-01-12T21:36:26Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"When be76c2128234d94b47f7087152ee55d08bb65d88 added support for fetching\na missing submodule object by id, it hardcoded the remote name as\n\"origin\" and deferred anything more complicated for a later patch.\nImplement the NEEDSWORK item to remove the hardcoded assumption by\nadding and using a submodule helper subcmd 'get-default-remote'. Fixing\nthis lets 'git fetch --recurse-submodules' succeed when the fetched\ncommit(s) in the superproject trigger a submodule fetch, and that\nsubmodule's default remote name is not \"origin\".\n\nAdd non-\"origin\" remote tests to t5526-fetch-submodules.sh and\nt5572-pull-submodule.sh demonstrating this works as expected and add\ndedicated tests for get-default-remote.\n\nSigned-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n---\nThe original commit was a bit over 7 years ago, so I guess it worked\nfine for most users. I've now run into cases where it doesn't work while\nusing 'repo' tool manifests that use a non-\"origin\" remote name and\ncontain projects with submodules.\n\nI kept this as a single commit because most of the code delta is in the new\ntest for the new submodule helper command, but it could easily be split into\ntwo commits if that's preferred.\n\n builtin/submodule--helper.c             |  38 +++++\n submodule.c                             |  17 ++-\n t/meson.build                           |   1 +\n t/t5526-fetch-submodules.sh             |  52 +++++++\n t/t5572-pull-submodule.sh               |  21 ++-\n t/t7425-submodule-get-default-remote.sh | 186 ++++++++++++++++++++++++\n 6 files changed, 312 insertions(+), 3 deletions(-)\n create mode 100755 t/t7425-submodule-get-default-remote.sh\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex d537ab087a..b180a24091 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -112,6 +112,43 @@ static int get_default_remote_submodule(const char *module_path, char **default_\n \treturn 0;\n }\n \n+static int module_get_default_remote(int argc, const char **argv, const char *prefix,\n+\t\t\t\t     struct repository *repo UNUSED)\n+{\n+\tconst char *path;\n+\tchar *resolved_path = NULL;\n+\tchar *default_remote = NULL;\n+\tint code;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper get-default-remote <path>\"),\n+\t\tNULL\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\tif (argc != 1)\n+\t\tusage_with_options(usage, options);\n+\n+\tpath = argv[0];\n+\tif (prefix && *prefix && !is_absolute_path(path)) {\n+\t\tresolved_path = xstrfmt(\"%s%s\", prefix, path);\n+\t\tpath = resolved_path;\n+\t}\n+\n+\tcode = get_default_remote_submodule(path, &default_remote);\n+\tif (code) {\n+\t\tfree(resolved_path);\n+\t\treturn code;\n+\t}\n+\n+\tprintf(\"%s\\n\", default_remote);\n+\tfree(default_remote);\n+\tfree(resolved_path);\n+\treturn 0;\n+}\n+\n /* the result should be freed by the caller. */\n static char *get_submodule_displaypath(const char *path, const char *prefix,\n \t\t\t\t       const char *super_prefix)\n@@ -3608,6 +3645,7 @@ int cmd_submodule__helper(int argc,\n \t\tOPT_SUBCOMMAND(\"set-url\", &fn, module_set_url),\n \t\tOPT_SUBCOMMAND(\"set-branch\", &fn, module_set_branch),\n \t\tOPT_SUBCOMMAND(\"create-branch\", &fn, module_create_branch),\n+\t\tOPT_SUBCOMMAND(\"get-default-remote\", &fn, module_get_default_remote),\n \t\tOPT_END()\n \t};\n \targc = parse_options(argc, argv, prefix, options, usage, 0);\ndiff --git a/submodule.c b/submodule.c\nindex 40a5c6fb9d..044fcad6c9 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1706,6 +1706,8 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \tif (spf->oid_fetch_tasks_nr) {\n \t\tstruct fetch_task *task =\n \t\t\tspf->oid_fetch_tasks[spf->oid_fetch_tasks_nr - 1];\n+\t\tstruct child_process cp_remote = CHILD_PROCESS_INIT;\n+\t\tstruct strbuf remote_name = STRBUF_INIT;\n \t\tspf->oid_fetch_tasks_nr--;\n \n \t\tchild_process_init(cp);\n@@ -1719,8 +1721,19 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \t\tstrvec_pushf(&cp->args, \"--submodule-prefix=%s%s/\",\n \t\t\t     spf->prefix, task->sub->path);\n \n-\t\t/* NEEDSWORK: have get_default_remote from submodule--helper */\n-\t\tstrvec_push(&cp->args, \"origin\");\n+\t\tcp_remote.git_cmd = 1;\n+\t\tstrvec_pushl(&cp_remote.args, \"submodule--helper\",\n+\t\t\t     \"get-default-remote\", task->sub->path, NULL);\n+\n+\t\tif (!capture_command(&cp_remote, &remote_name, 0)) {\n+\t\t\tstrbuf_trim_trailing_newline(&remote_name);\n+\t\t\tstrvec_push(&cp->args, remote_name.buf);\n+\t\t} else {\n+\t\t\t// Fallback to \"origin\" if the helper fails\n+\t\t\tstrvec_push(&cp->args, \"origin\");\n+\t\t}\n+\t\tstrbuf_release(&remote_name);\n+\n \t\toid_array_for_each_unique(task->commits,\n \t\t\t\t\t  append_oid_to_argv, &cp->args);\n \ndiff --git a/t/meson.build b/t/meson.build\nindex 459c52a489..ef6cdab165 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -887,6 +887,7 @@ integration_tests = [\n   't7422-submodule-output.sh',\n   't7423-submodule-symlinks.sh',\n   't7424-submodule-mixed-ref-formats.sh',\n+  't7425-submodule-get-default-remote.sh',\n   't7450-bad-git-dotfiles.sh',\n   't7500-commit-template-squash-signoff.sh',\n   't7501-commit-basic-functionality.sh',\ndiff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh\nindex 5e566205ba..a5a273b392 100755\n--- a/t/t5526-fetch-submodules.sh\n+++ b/t/t5526-fetch-submodules.sh\n@@ -929,6 +929,58 @@ test_expect_success 'fetch new submodule commit intermittently referenced by sup\n \t)\n '\n \n+test_expect_success 'fetch --recurse-submodules works with custom remote names' '\n+\t# depends on the previous test for setup\n+\n+\t# Rename the remote in sub1 from \"origin\" to \"custom_remote\"\n+\tgit -C downstream/sub1 remote rename origin custom_remote &&\n+\n+\t# Create new commits in the original submodules\n+\tC=$(git -C submodule commit-tree -m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom1 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree -m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom2 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit -m \"updated submodules outside of refs/heads for custom remote\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom3 $E &&\n+\t(\n+\t\tcd downstream &&\n+\t\tgit fetch --recurse-submodules origin refs/changes/custom3:refs/heads/my_other_branch &&\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n+test_expect_success 'fetch new submodule commit on-demand in FETCH_HEAD from custom remote' '\n+\t# depends on the previous test for setup\n+\n+\tC=$(git -C submodule commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom4 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom5 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit -m \"updated submodules outside of refs/heads\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom6 $E &&\n+\t(\n+\t\tcd downstream &&\n+\t\tgit fetch --recurse-submodules origin refs/changes/custom6 &&\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n add_commit_push () {\n \tdir=\"$1\" &&\n \tmsg=\"$2\" &&\ndiff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\nindex 45f384dd32..868dd6d130 100755\n--- a/t/t5572-pull-submodule.sh\n+++ b/t/t5572-pull-submodule.sh\n@@ -257,7 +257,26 @@ test_expect_success 'fetch submodule remote of different name from superproject'\n \tgit -C a-submodule reset --hard HEAD^^ &&\n \n \tgit -C child pull --no-recurse-submodules &&\n-\tgit -C child submodule update\n+\tgit -C child submodule update &&\n+\ttest_path_is_file child/a-submodule/moreecho.t\n+'\n+\n+test_expect_success 'fetch submodule remote of different non-origin name from superproject' '\n+\tgit -C child/a-submodule remote rename origin o2 &&\n+\n+\t# Create commit that's unreachable from current master branch\n+\tgit -C a-submodule checkout -b newmain2 master^ &&\n+\ttest_commit -C a-submodule echo_o2 &&\n+\ttest_commit -C a-submodule moreecho_o2 &&\n+\tsubc=$(git -C a-submodule rev-parse --short HEAD) &&\n+\n+\tgit -C parent/a-submodule fetch &&\n+\tgit -C parent/a-submodule checkout \"$subc\" &&\n+\tgit -C parent commit -m \"update submodule o2\" a-submodule &&\n+\tgit -C a-submodule reset --hard HEAD^^ &&\n+\n+\tgit -C child pull --recurse-submodules &&\n+\ttest_path_is_file child/a-submodule/moreecho_o2.t\n '\n \n test_done\ndiff --git a/t/t7425-submodule-get-default-remote.sh b/t/t7425-submodule-get-default-remote.sh\nnew file mode 100755\nindex 0000000000..b842af9a2d\n--- /dev/null\n+++ b/t/t7425-submodule-get-default-remote.sh\n@@ -0,0 +1,186 @@\n+#!/bin/sh\n+\n+test_description='git submodule--helper get-default-remote'\n+\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tgit config --global protocol.file.allow always\n+'\n+\n+test_expect_success 'setup repositories' '\n+\t# Create a repository to be used as submodule\n+\tgit init sub &&\n+\ttest_commit --no-tag -C sub \"initial commit in sub\" file.txt \"sub content\" &&\n+\n+\t# Create main repository\n+\tgit init super &&\n+\t(\n+\t\tcd super &&\n+\t\tmkdir subdir &&\n+\t\ttest_commit --no-tag -C subdir \"initial commit in super\" main.txt \"super content\" &&\n+\t\tgit submodule add ../sub subpath &&\n+\t\tgit commit -m \"add submodule 'sub' at subpath\"\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns origin for initialized submodule' '\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule update --init &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works from subdirectory' '\n+\t(\n+\t\tcd super/subdir &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote ../subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-existent path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote nonexistent 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-submodule path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subdir 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails without path argument' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with too many arguments' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subpath subdir 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'setup submodule with non-origin default remote name' '\n+\t# Create another submodule path with a different remote name\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ../sub upstream-subpath &&\n+\t\tgit commit -m \"add second submodule in upstream-subpath\" &&\n+\t\tgit submodule update --init upstream-subpath &&\n+\n+\t\t# Change the remote name in the submodule\n+\t\tcd upstream-subpath &&\n+\t\tgit remote rename origin upstream\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns non-origin remote name' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes' '\n+\t(\n+\t\tcd super/subpath &&\n+\t\tgit remote add other-upstream ../../sub &&\n+\t\tgit remote add myfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes and none are origin' '\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit remote add yet-another-upstream ../../sub &&\n+\t\tgit remote add yourfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'setup nested submodule with non-origin remote' '\n+\tgit init innersub &&\n+\ttest_commit --no-tag -C innersub \"initial commit in innersub\" inner.txt \"innersub content\" &&\n+\n+\t(\n+\t\tcd sub &&\n+\t\tgit submodule add ../innersub innersubpath &&\n+\t\tgit commit -m \"add nested submodule at innersubpath\"\n+\t) &&\n+\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit pull upstream &&\n+\t\tgit submodule update --init --recursive . &&\n+\t\t(\n+\t\t\tcd innersubpath &&\n+\t\t\tgit remote rename origin another_upstream\n+\t\t)\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with nested submodule' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"another_upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath/innersubpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with submodule that has no remotes' '\n+\t# Create a submodule directory manually without remotes\n+\t(\n+\t\tcd super &&\n+\t\tgit init no-remote-sub &&\n+\t\ttest_commit --no-tag -C no-remote-sub \"local commit\" local.txt \"local content\"\n+\t) &&\n+\n+\t# Add it as a submodule\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ./no-remote-sub &&\n+\t\tgit commit -m \"add local submodule 'no-remote-sub'\"\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\t# Should fall back to \"origin\" remote name when no remotes exist\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote no-remote-sub >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_done\n-- \n2.51.2\n\n"},{"id":"533694","messageId":"CA+P7+xoqOnU+H_jRq64AsT-Jk058ZTk5M8gpEjREaq799oRHWQ@mail.gmail.com","threadId":"64785","inReplyTo":"20260112213612.2761854-1-nasser.grainawi@oss.qualcomm.com","subject":"Re: [PATCH] Fetch missing submodule objects from default remote","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2026-01-13T02:17:40Z","receivedAt":"2026-01-13T02:17:51Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Mon, Jan 12, 2026 at 1:36 PM Nasser Grainawi\n<nasser.grainawi@oss.qualcomm.com> wrote:\n>\n> When be76c2128234d94b47f7087152ee55d08bb65d88 added support for fetching\n> a missing submodule object by id, it hardcoded the remote name as\n> \"origin\" and deferred anything more complicated for a later patch.\n> Implement the NEEDSWORK item to remove the hardcoded assumption by\n> adding and using a submodule helper subcmd 'get-default-remote'. Fixing\n> this lets 'git fetch --recurse-submodules' succeed when the fetched\n> commit(s) in the superproject trigger a submodule fetch, and that\n> submodule's default remote name is not \"origin\".\n>\n> Add non-\"origin\" remote tests to t5526-fetch-submodules.sh and\n> t5572-pull-submodule.sh demonstrating this works as expected and add\n> dedicated tests for get-default-remote.\n>\n> Signed-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n> ---\n> The original commit was a bit over 7 years ago, so I guess it worked\n> fine for most users. I've now run into cases where it doesn't work while\n> using 'repo' tool manifests that use a non-\"origin\" remote name and\n> contain projects with submodules.\n>\n> I kept this as a single commit because most of the code delta is in the new\n> test for the new submodule helper command, but it could easily be split into\n> two commits if that's preferred.\n>\n>  builtin/submodule--helper.c             |  38 +++++\n>  submodule.c                             |  17 ++-\n>  t/meson.build                           |   1 +\n>  t/t5526-fetch-submodules.sh             |  52 +++++++\n>  t/t5572-pull-submodule.sh               |  21 ++-\n>  t/t7425-submodule-get-default-remote.sh | 186 ++++++++++++++++++++++++\n>  6 files changed, 312 insertions(+), 3 deletions(-)\n>  create mode 100755 t/t7425-submodule-get-default-remote.sh\n>\n\nI've had this exact same issue due to setting a default name of\nupstream instead of origin due to how I like to name things for\nfork-based workflows. The change looks good and the tests are\nappreciated.\n\nI recall trying to fix related issues with the following series, but I\nthink I lost track of its development and never saw it through to\nmerging:\n\nhttps://lore.kernel.org/git/20250623-jk-submodule-helper-use-url-v4-0-133ef3d89569@gmail.com/\n\nReading through that series, it doesn't fix this particular hard\ncoding either, so this is an improvement regardless.\n\nReviewed-by: Jacob Keller <jacob.keller@gmail.com>\n"},{"id":"533776","messageId":"7FB6BFD0-4C54-4924-851E-D4B5C574D7FC@gmail.com","threadId":"64785","inReplyTo":"20260112213612.2761854-1-nasser.grainawi@oss.qualcomm.com","subject":"Re: [PATCH] Fetch missing submodule objects from default remote","fromName":"Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-01-13T21:51:37Z","receivedAt":"2026-01-13T21:51:49Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"\n\n> Le 12 janv. 2026 à 16:36, Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> a écrit :\n> \n> ﻿When be76c2128234d94b47f7087152ee55d08bb65d88 added support for fetching\n> a missing submodule object by id, it\n\nConvention is to refer to published commits using the “reference” format supported by git log and git show :)"},{"id":"533777","messageId":"CAFcKa=_P8kUXvsuneQsoi=Bwbmc8U1kBv68fx4yq81vKBLkFEQ@mail.gmail.com","threadId":"64785","inReplyTo":"7FB6BFD0-4C54-4924-851E-D4B5C574D7FC@gmail.com","subject":"Re: [PATCH] Fetch missing submodule objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-01-13T22:41:43Z","receivedAt":"2026-01-13T22:41:55Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"On Tue, Jan 13, 2026 at 2:51 PM Ben Knoble <ben.knoble@gmail.com> wrote:\n>\n>\n>\n> > Le 12 janv. 2026 à 16:36, Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> a écrit :\n> >\n> > ﻿When be76c2128234d94b47f7087152ee55d08bb65d88 added support for fetching\n> > a missing submodule object by id, it\n>\n> Convention is to refer to published commits using the “reference” format supported by git log and git show :)\n\nOh, thanks for pointing that out! I missed it in the SubmittingPatches\ndoc. If I end up sending a v2 I'll include the update, but I assume\nthat alone isn't worth sending a new patch for?\n\nIf it helps to have it here, the corrected first paragraph should be:\n\nWhen be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\nadded support for fetching a missing submodule object by id, it\nhardcoded the remote name as \"origin\" and deferred anything more\ncomplicated for a later patch. Implement the NEEDSWORK item to remove\nthe hardcoded assumption by adding and using a submodule helper subcmd\n'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\nsucceed when the fetched commit(s) in the superproject trigger a\nsubmodule fetch, and that submodule's default remote name is not\n\"origin\".\n"},{"id":"533787","messageId":"CALnO6CAVup7AeOyUu4Qt9onD77c+GWsWJ1o5NVDtymGhwuE7XQ@mail.gmail.com","threadId":"64785","inReplyTo":"CAFcKa=_P8kUXvsuneQsoi=Bwbmc8U1kBv68fx4yq81vKBLkFEQ@mail.gmail.com","subject":"Re: [PATCH] Fetch missing submodule objects from default remote","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-01-14T02:19:14Z","receivedAt":"2026-01-14T02:19:26Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"On Tue, Jan 13, 2026 at 5:41 PM Nasser Grainawi\n<nasser.grainawi@oss.qualcomm.com> wrote:\n> On Tue, Jan 13, 2026 at 2:51 PM Ben Knoble <ben.knoble@gmail.com> wrote:\n> > > Le 12 janv. 2026 à 16:36, Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> a écrit :\n> > >\n> > > ﻿When be76c2128234d94b47f7087152ee55d08bb65d88 added support for fetching\n> > > a missing submodule object by id, it\n> >\n> > Convention is to refer to published commits using the “reference” format supported by git log and git show :)\n>\n> Oh, thanks for pointing that out! I missed it in the SubmittingPatches\n> doc. If I end up sending a v2 I'll include the update, but I assume\n> that alone isn't worth sending a new patch for?\n>\n> If it helps to have it here, the corrected first paragraph should be:\n>\n> When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\n> added support for fetching a missing submodule object by id, it\n> hardcoded the remote name as \"origin\" and deferred anything more\n> complicated for a later patch. Implement the NEEDSWORK item to remove\n> the hardcoded assumption by adding and using a submodule helper subcmd\n> 'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\n> succeed when the fetched commit(s) in the superproject trigger a\n> submodule fetch, and that submodule's default remote name is not\n> \"origin\".\n\nThanks. That should be sufficient for Junio to correct it when\napplying, but I would amend the change locally in case we get further\niterations ;)\n\n-- \nD. Ben Knoble\n"},{"id":"533822","messageId":"xmqqqzrsguh5.fsf@gitster.g","threadId":"64785","inReplyTo":"7FB6BFD0-4C54-4924-851E-D4B5C574D7FC@gmail.com","subject":"Re: [PATCH] Fetch missing submodule objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-14T14:05:42Z","receivedAt":"2026-01-14T14:05:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ben Knoble <ben.knoble@gmail.com> writes:\n\n>> Le 12 janv. 2026 à 16:36, Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> a écrit :\n>> \n>> ﻿When be76c2128234d94b47f7087152ee55d08bb65d88 added support for fetching\n>> a missing submodule object by id, it\n>\n> Convention is to refer to published commits using the\n> “reference” format supported by git log and git show :)\n\n;-)\n\nOther two conventions violated that you may want to point out are\n(1) the commit title lacks the <area>: prefix and upcases the first\nword, (2) we do not use // to introduce a comment line.\n\nThanks.\n"},{"id":"533823","messageId":"xmqqms2ggu8p.fsf@gitster.g","threadId":"64785","inReplyTo":"CALnO6CAVup7AeOyUu4Qt9onD77c+GWsWJ1o5NVDtymGhwuE7XQ@mail.gmail.com","subject":"Re: [PATCH] Fetch missing submodule objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-14T14:10:46Z","receivedAt":"2026-01-14T14:10:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"D. Ben Knoble\" <ben.knoble@gmail.com> writes:\n\n> Thanks. That should be sufficient for Junio to correct it when\n> applying, ...\n\nI'd prefer not to see such recommendation to use the maintainer as a\njanitor, though, as the number of contributors well outweigh the\nnumber of maintainer(s).\n\n\n"},{"id":"533866","messageId":"CAFcKa=8RNxQz--RwLiopuQb9_Fy-jx64ya9vcA1ygcPyYswJ_w@mail.gmail.com","threadId":"64785","inReplyTo":"xmqqqzrsguh5.fsf@gitster.g","subject":"Re: [PATCH] Fetch missing submodule objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-01-14T19:23:08Z","receivedAt":"2026-01-14T19:23:21Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"On Wed, Jan 14, 2026 at 7:05 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Other two conventions violated that you may want to point out are\n> (1) the commit title lacks the <area>: prefix and upcases the first\n> word, (2) we do not use // to introduce a comment line.\n\nThanks. I'll fix those and send a v2 with the commit reference fixed too.\n\nAlso, I couldn't find mention of (2) in the CodingGuidelines doc. Did\nI miss it or would you like a patch to add it?\n"},{"id":"533868","messageId":"20260114194815.1049888-1-nasser.grainawi@oss.qualcomm.com","threadId":"64785","inReplyTo":"20260112213612.2761854-1-nasser.grainawi@oss.qualcomm.com","subject":"[PATCH v2] submodule: fetch missing objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-01-14T19:48:15Z","receivedAt":"2026-01-14T19:48:47Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\nadded support for fetching a missing submodule object by id, it\nhardcoded the remote name as \"origin\" and deferred anything more\ncomplicated for a later patch. Implement the NEEDSWORK item to remove\nthe hardcoded assumption by adding and using a submodule helper subcmd\n'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\nsucceed when the fetched commit(s) in the superproject trigger a\nsubmodule fetch, and that submodule's default remote name is not\n\"origin\".\n\nAdd non-\"origin\" remote tests to t5526-fetch-submodules.sh and\nt5572-pull-submodule.sh demonstrating this works as expected and add\ndedicated tests for get-default-remote.\n\nSigned-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n---\nRange-diff against v1:\n1:  36ce158268 ! 1:  99c4792cff Fetch missing submodule objects from default remote\n    @@ Metadata\n     Author: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n     \n      ## Commit message ##\n    -    Fetch missing submodule objects from default remote\n    +    submodule: fetch missing objects from default remote\n     \n    -    When be76c2128234d94b47f7087152ee55d08bb65d88 added support for fetching\n    -    a missing submodule object by id, it hardcoded the remote name as\n    -    \"origin\" and deferred anything more complicated for a later patch.\n    -    Implement the NEEDSWORK item to remove the hardcoded assumption by\n    -    adding and using a submodule helper subcmd 'get-default-remote'. Fixing\n    -    this lets 'git fetch --recurse-submodules' succeed when the fetched\n    -    commit(s) in the superproject trigger a submodule fetch, and that\n    -    submodule's default remote name is not \"origin\".\n    +    When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\n    +    added support for fetching a missing submodule object by id, it\n    +    hardcoded the remote name as \"origin\" and deferred anything more\n    +    complicated for a later patch. Implement the NEEDSWORK item to remove\n    +    the hardcoded assumption by adding and using a submodule helper subcmd\n    +    'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\n    +    succeed when the fetched commit(s) in the superproject trigger a\n    +    submodule fetch, and that submodule's default remote name is not\n    +    \"origin\".\n     \n         Add non-\"origin\" remote tests to t5526-fetch-submodules.sh and\n         t5572-pull-submodule.sh demonstrating this works as expected and add\n    @@ submodule.c: static int get_next_submodule(struct child_process *cp, struct strb\n     +\t\t\tstrbuf_trim_trailing_newline(&remote_name);\n     +\t\t\tstrvec_push(&cp->args, remote_name.buf);\n     +\t\t} else {\n    -+\t\t\t// Fallback to \"origin\" if the helper fails\n    ++\t\t\t/* Fallback to \"origin\" if the helper fails */\n     +\t\t\tstrvec_push(&cp->args, \"origin\");\n     +\t\t}\n     +\t\tstrbuf_release(&remote_name);\n\n builtin/submodule--helper.c             |  38 +++++\n submodule.c                             |  17 ++-\n t/meson.build                           |   1 +\n t/t5526-fetch-submodules.sh             |  52 +++++++\n t/t5572-pull-submodule.sh               |  21 ++-\n t/t7425-submodule-get-default-remote.sh | 186 ++++++++++++++++++++++++\n 6 files changed, 312 insertions(+), 3 deletions(-)\n create mode 100755 t/t7425-submodule-get-default-remote.sh\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex d537ab087a..b180a24091 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -112,6 +112,43 @@ static int get_default_remote_submodule(const char *module_path, char **default_\n \treturn 0;\n }\n \n+static int module_get_default_remote(int argc, const char **argv, const char *prefix,\n+\t\t\t\t     struct repository *repo UNUSED)\n+{\n+\tconst char *path;\n+\tchar *resolved_path = NULL;\n+\tchar *default_remote = NULL;\n+\tint code;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper get-default-remote <path>\"),\n+\t\tNULL\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\tif (argc != 1)\n+\t\tusage_with_options(usage, options);\n+\n+\tpath = argv[0];\n+\tif (prefix && *prefix && !is_absolute_path(path)) {\n+\t\tresolved_path = xstrfmt(\"%s%s\", prefix, path);\n+\t\tpath = resolved_path;\n+\t}\n+\n+\tcode = get_default_remote_submodule(path, &default_remote);\n+\tif (code) {\n+\t\tfree(resolved_path);\n+\t\treturn code;\n+\t}\n+\n+\tprintf(\"%s\\n\", default_remote);\n+\tfree(default_remote);\n+\tfree(resolved_path);\n+\treturn 0;\n+}\n+\n /* the result should be freed by the caller. */\n static char *get_submodule_displaypath(const char *path, const char *prefix,\n \t\t\t\t       const char *super_prefix)\n@@ -3608,6 +3645,7 @@ int cmd_submodule__helper(int argc,\n \t\tOPT_SUBCOMMAND(\"set-url\", &fn, module_set_url),\n \t\tOPT_SUBCOMMAND(\"set-branch\", &fn, module_set_branch),\n \t\tOPT_SUBCOMMAND(\"create-branch\", &fn, module_create_branch),\n+\t\tOPT_SUBCOMMAND(\"get-default-remote\", &fn, module_get_default_remote),\n \t\tOPT_END()\n \t};\n \targc = parse_options(argc, argv, prefix, options, usage, 0);\ndiff --git a/submodule.c b/submodule.c\nindex 40a5c6fb9d..6599657f34 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1706,6 +1706,8 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \tif (spf->oid_fetch_tasks_nr) {\n \t\tstruct fetch_task *task =\n \t\t\tspf->oid_fetch_tasks[spf->oid_fetch_tasks_nr - 1];\n+\t\tstruct child_process cp_remote = CHILD_PROCESS_INIT;\n+\t\tstruct strbuf remote_name = STRBUF_INIT;\n \t\tspf->oid_fetch_tasks_nr--;\n \n \t\tchild_process_init(cp);\n@@ -1719,8 +1721,19 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \t\tstrvec_pushf(&cp->args, \"--submodule-prefix=%s%s/\",\n \t\t\t     spf->prefix, task->sub->path);\n \n-\t\t/* NEEDSWORK: have get_default_remote from submodule--helper */\n-\t\tstrvec_push(&cp->args, \"origin\");\n+\t\tcp_remote.git_cmd = 1;\n+\t\tstrvec_pushl(&cp_remote.args, \"submodule--helper\",\n+\t\t\t     \"get-default-remote\", task->sub->path, NULL);\n+\n+\t\tif (!capture_command(&cp_remote, &remote_name, 0)) {\n+\t\t\tstrbuf_trim_trailing_newline(&remote_name);\n+\t\t\tstrvec_push(&cp->args, remote_name.buf);\n+\t\t} else {\n+\t\t\t/* Fallback to \"origin\" if the helper fails */\n+\t\t\tstrvec_push(&cp->args, \"origin\");\n+\t\t}\n+\t\tstrbuf_release(&remote_name);\n+\n \t\toid_array_for_each_unique(task->commits,\n \t\t\t\t\t  append_oid_to_argv, &cp->args);\n \ndiff --git a/t/meson.build b/t/meson.build\nindex 459c52a489..ef6cdab165 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -887,6 +887,7 @@ integration_tests = [\n   't7422-submodule-output.sh',\n   't7423-submodule-symlinks.sh',\n   't7424-submodule-mixed-ref-formats.sh',\n+  't7425-submodule-get-default-remote.sh',\n   't7450-bad-git-dotfiles.sh',\n   't7500-commit-template-squash-signoff.sh',\n   't7501-commit-basic-functionality.sh',\ndiff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh\nindex 5e566205ba..a5a273b392 100755\n--- a/t/t5526-fetch-submodules.sh\n+++ b/t/t5526-fetch-submodules.sh\n@@ -929,6 +929,58 @@ test_expect_success 'fetch new submodule commit intermittently referenced by sup\n \t)\n '\n \n+test_expect_success 'fetch --recurse-submodules works with custom remote names' '\n+\t# depends on the previous test for setup\n+\n+\t# Rename the remote in sub1 from \"origin\" to \"custom_remote\"\n+\tgit -C downstream/sub1 remote rename origin custom_remote &&\n+\n+\t# Create new commits in the original submodules\n+\tC=$(git -C submodule commit-tree -m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom1 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree -m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom2 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit -m \"updated submodules outside of refs/heads for custom remote\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom3 $E &&\n+\t(\n+\t\tcd downstream &&\n+\t\tgit fetch --recurse-submodules origin refs/changes/custom3:refs/heads/my_other_branch &&\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n+test_expect_success 'fetch new submodule commit on-demand in FETCH_HEAD from custom remote' '\n+\t# depends on the previous test for setup\n+\n+\tC=$(git -C submodule commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom4 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom5 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit -m \"updated submodules outside of refs/heads\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom6 $E &&\n+\t(\n+\t\tcd downstream &&\n+\t\tgit fetch --recurse-submodules origin refs/changes/custom6 &&\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n add_commit_push () {\n \tdir=\"$1\" &&\n \tmsg=\"$2\" &&\ndiff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\nindex 45f384dd32..868dd6d130 100755\n--- a/t/t5572-pull-submodule.sh\n+++ b/t/t5572-pull-submodule.sh\n@@ -257,7 +257,26 @@ test_expect_success 'fetch submodule remote of different name from superproject'\n \tgit -C a-submodule reset --hard HEAD^^ &&\n \n \tgit -C child pull --no-recurse-submodules &&\n-\tgit -C child submodule update\n+\tgit -C child submodule update &&\n+\ttest_path_is_file child/a-submodule/moreecho.t\n+'\n+\n+test_expect_success 'fetch submodule remote of different non-origin name from superproject' '\n+\tgit -C child/a-submodule remote rename origin o2 &&\n+\n+\t# Create commit that's unreachable from current master branch\n+\tgit -C a-submodule checkout -b newmain2 master^ &&\n+\ttest_commit -C a-submodule echo_o2 &&\n+\ttest_commit -C a-submodule moreecho_o2 &&\n+\tsubc=$(git -C a-submodule rev-parse --short HEAD) &&\n+\n+\tgit -C parent/a-submodule fetch &&\n+\tgit -C parent/a-submodule checkout \"$subc\" &&\n+\tgit -C parent commit -m \"update submodule o2\" a-submodule &&\n+\tgit -C a-submodule reset --hard HEAD^^ &&\n+\n+\tgit -C child pull --recurse-submodules &&\n+\ttest_path_is_file child/a-submodule/moreecho_o2.t\n '\n \n test_done\ndiff --git a/t/t7425-submodule-get-default-remote.sh b/t/t7425-submodule-get-default-remote.sh\nnew file mode 100755\nindex 0000000000..b842af9a2d\n--- /dev/null\n+++ b/t/t7425-submodule-get-default-remote.sh\n@@ -0,0 +1,186 @@\n+#!/bin/sh\n+\n+test_description='git submodule--helper get-default-remote'\n+\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tgit config --global protocol.file.allow always\n+'\n+\n+test_expect_success 'setup repositories' '\n+\t# Create a repository to be used as submodule\n+\tgit init sub &&\n+\ttest_commit --no-tag -C sub \"initial commit in sub\" file.txt \"sub content\" &&\n+\n+\t# Create main repository\n+\tgit init super &&\n+\t(\n+\t\tcd super &&\n+\t\tmkdir subdir &&\n+\t\ttest_commit --no-tag -C subdir \"initial commit in super\" main.txt \"super content\" &&\n+\t\tgit submodule add ../sub subpath &&\n+\t\tgit commit -m \"add submodule 'sub' at subpath\"\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns origin for initialized submodule' '\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule update --init &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works from subdirectory' '\n+\t(\n+\t\tcd super/subdir &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote ../subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-existent path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote nonexistent 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-submodule path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subdir 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails without path argument' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with too many arguments' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subpath subdir 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'setup submodule with non-origin default remote name' '\n+\t# Create another submodule path with a different remote name\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ../sub upstream-subpath &&\n+\t\tgit commit -m \"add second submodule in upstream-subpath\" &&\n+\t\tgit submodule update --init upstream-subpath &&\n+\n+\t\t# Change the remote name in the submodule\n+\t\tcd upstream-subpath &&\n+\t\tgit remote rename origin upstream\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns non-origin remote name' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes' '\n+\t(\n+\t\tcd super/subpath &&\n+\t\tgit remote add other-upstream ../../sub &&\n+\t\tgit remote add myfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes and none are origin' '\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit remote add yet-another-upstream ../../sub &&\n+\t\tgit remote add yourfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'setup nested submodule with non-origin remote' '\n+\tgit init innersub &&\n+\ttest_commit --no-tag -C innersub \"initial commit in innersub\" inner.txt \"innersub content\" &&\n+\n+\t(\n+\t\tcd sub &&\n+\t\tgit submodule add ../innersub innersubpath &&\n+\t\tgit commit -m \"add nested submodule at innersubpath\"\n+\t) &&\n+\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit pull upstream &&\n+\t\tgit submodule update --init --recursive . &&\n+\t\t(\n+\t\t\tcd innersubpath &&\n+\t\t\tgit remote rename origin another_upstream\n+\t\t)\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with nested submodule' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"another_upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath/innersubpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with submodule that has no remotes' '\n+\t# Create a submodule directory manually without remotes\n+\t(\n+\t\tcd super &&\n+\t\tgit init no-remote-sub &&\n+\t\ttest_commit --no-tag -C no-remote-sub \"local commit\" local.txt \"local content\"\n+\t) &&\n+\n+\t# Add it as a submodule\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ./no-remote-sub &&\n+\t\tgit commit -m \"add local submodule 'no-remote-sub'\"\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\t# Should fall back to \"origin\" remote name when no remotes exist\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote no-remote-sub >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_done\n-- \n2.51.2\n\n"},{"id":"533893","messageId":"C3B42E7B-C98B-4A01-B4E4-C5106CD09C37@gmail.com","threadId":"64785","inReplyTo":"xmqqms2ggu8p.fsf@gitster.g","subject":"Re: [PATCH] Fetch missing submodule objects from default remote","fromName":"Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-01-14T21:22:27Z","receivedAt":"2026-01-14T21:22:39Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"\n> \n> Le 14 janv. 2026 à 09:10, Junio C Hamano <gitster@pobox.com> a écrit :\n> \n> ﻿\"D. Ben Knoble\" <ben.knoble@gmail.com> writes:\n> \n>> Thanks. That should be sufficient for Junio to correct it when\n>> applying, ...\n> \n> I'd prefer not to see such recommendation to use the maintainer as a\n> janitor, though, as the number of contributors well outweigh the\n> number of maintainer(s).\n\nCertainly. Thanks. "},{"id":"534307","messageId":"CAFcKa=9jRS2-6wdhUaV0riFNaM+WB60BMho3pYQs_YXU2EvB6Q@mail.gmail.com","threadId":"64785","inReplyTo":"20260114194815.1049888-1-nasser.grainawi@oss.qualcomm.com","subject":"Re: [PATCH v2] submodule: fetch missing objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-01-21T00:48:23Z","receivedAt":"2026-01-21T00:48:35Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"On Wed, Jan 14, 2026 at 12:48 PM Nasser Grainawi\n<nasser.grainawi@oss.qualcomm.com> wrote:\n>\n> Signed-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n\nI realized I missed adding Jacob's Reviewed-by.\n\nReviewed-by: Jacob Keller <jacob.keller@gmail.com>\n\n> diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\n> index 45f384dd32..868dd6d130 100755\n> --- a/t/t5572-pull-submodule.sh\n> +++ b/t/t5572-pull-submodule.sh\n> @@ -257,7 +257,26 @@ test_expect_success 'fetch submodule remote of different name from superproject'\n>         git -C a-submodule reset --hard HEAD^^ &&\n>\n>         git -C child pull --no-recurse-submodules &&\n> -       git -C child submodule update\n> +       git -C child submodule update &&\n> +       test_path_is_file child/a-submodule/moreecho.t\n> +'\n> +\n> +test_expect_success 'fetch submodule remote of different non-origin name from superproject' '\n> +       git -C child/a-submodule remote rename origin o2 &&\n> +\n> +       # Create commit that's unreachable from current master branch\n\nThe single quote in this comment is breaking the test. Sorry I didn't\nre-run the tests before sending the patch. I'll wait for any other\ncomments and otherwise include this fix in a v3 tomorrow.\n\n> diff --git a/t/t7425-submodule-get-default-remote.sh b/t/t7425-submodule-get-default-remote.sh\n\nFYI, this test name conflicts with the new test added in topic\nar/submodule-gitdir-tweak (they both use t7425). Renaming this test to\n't7426-...' and updating the name in t/meson.build is sufficient to\nhave all tests passing with 'seen'.\n"},{"id":"534460","messageId":"20260122152722.866341-1-nasser.grainawi@oss.qualcomm.com","threadId":"64785","inReplyTo":"20260114194815.1049888-1-nasser.grainawi@oss.qualcomm.com","subject":"[PATCH v3] submodule: fetch missing objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-01-22T15:27:22Z","receivedAt":"2026-01-22T15:28:08Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\nadded support for fetching a missing submodule object by id, it\nhardcoded the remote name as \"origin\" and deferred anything more\ncomplicated for a later patch. Implement the NEEDSWORK item to remove\nthe hardcoded assumption by adding and using a submodule helper subcmd\n'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\nsucceed when the fetched commit(s) in the superproject trigger a\nsubmodule fetch, and that submodule's default remote name is not\n\"origin\".\n\nAdd non-\"origin\" remote tests to t5526-fetch-submodules.sh and\nt5572-pull-submodule.sh demonstrating this works as expected and add\ndedicated tests for get-default-remote.\n\nSigned-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\nReviewed-by: Jacob Keller <jacob.keller@gmail.com>\n---\nRange-diff against v2:\n1:  99c4792cff ! 1:  1dd17e9f75 submodule: fetch missing objects from default remote\n    @@ Commit message\n     \n         Change-Id: I0fec01b161aa13ed4c1c5e53477dad6912d1b5e6\n         Signed-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n    +    Reviewed-by: Jacob Keller <jacob.keller@gmail.com>\n     \n      ## builtin/submodule--helper.c ##\n     @@ builtin/submodule--helper.c: static int get_default_remote_submodule(const char *module_path, char **default_\n    @@ t/t5572-pull-submodule.sh: test_expect_success 'fetch submodule remote of differ\n     +test_expect_success 'fetch submodule remote of different non-origin name from superproject' '\n     +\tgit -C child/a-submodule remote rename origin o2 &&\n     +\n    -+\t# Create commit that's unreachable from current master branch\n    ++\t# Create commit that is unreachable from current master branch\n     +\tgit -C a-submodule checkout -b newmain2 master^ &&\n     +\ttest_commit -C a-submodule echo_o2 &&\n     +\ttest_commit -C a-submodule moreecho_o2 &&\n\n builtin/submodule--helper.c             |  38 +++++\n submodule.c                             |  17 ++-\n t/meson.build                           |   1 +\n t/t5526-fetch-submodules.sh             |  52 +++++++\n t/t5572-pull-submodule.sh               |  21 ++-\n t/t7425-submodule-get-default-remote.sh | 186 ++++++++++++++++++++++++\n 6 files changed, 312 insertions(+), 3 deletions(-)\n create mode 100755 t/t7425-submodule-get-default-remote.sh\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex d537ab087a..b180a24091 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -112,6 +112,43 @@ static int get_default_remote_submodule(const char *module_path, char **default_\n \treturn 0;\n }\n \n+static int module_get_default_remote(int argc, const char **argv, const char *prefix,\n+\t\t\t\t     struct repository *repo UNUSED)\n+{\n+\tconst char *path;\n+\tchar *resolved_path = NULL;\n+\tchar *default_remote = NULL;\n+\tint code;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper get-default-remote <path>\"),\n+\t\tNULL\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\tif (argc != 1)\n+\t\tusage_with_options(usage, options);\n+\n+\tpath = argv[0];\n+\tif (prefix && *prefix && !is_absolute_path(path)) {\n+\t\tresolved_path = xstrfmt(\"%s%s\", prefix, path);\n+\t\tpath = resolved_path;\n+\t}\n+\n+\tcode = get_default_remote_submodule(path, &default_remote);\n+\tif (code) {\n+\t\tfree(resolved_path);\n+\t\treturn code;\n+\t}\n+\n+\tprintf(\"%s\\n\", default_remote);\n+\tfree(default_remote);\n+\tfree(resolved_path);\n+\treturn 0;\n+}\n+\n /* the result should be freed by the caller. */\n static char *get_submodule_displaypath(const char *path, const char *prefix,\n \t\t\t\t       const char *super_prefix)\n@@ -3608,6 +3645,7 @@ int cmd_submodule__helper(int argc,\n \t\tOPT_SUBCOMMAND(\"set-url\", &fn, module_set_url),\n \t\tOPT_SUBCOMMAND(\"set-branch\", &fn, module_set_branch),\n \t\tOPT_SUBCOMMAND(\"create-branch\", &fn, module_create_branch),\n+\t\tOPT_SUBCOMMAND(\"get-default-remote\", &fn, module_get_default_remote),\n \t\tOPT_END()\n \t};\n \targc = parse_options(argc, argv, prefix, options, usage, 0);\ndiff --git a/submodule.c b/submodule.c\nindex 40a5c6fb9d..6599657f34 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1706,6 +1706,8 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \tif (spf->oid_fetch_tasks_nr) {\n \t\tstruct fetch_task *task =\n \t\t\tspf->oid_fetch_tasks[spf->oid_fetch_tasks_nr - 1];\n+\t\tstruct child_process cp_remote = CHILD_PROCESS_INIT;\n+\t\tstruct strbuf remote_name = STRBUF_INIT;\n \t\tspf->oid_fetch_tasks_nr--;\n \n \t\tchild_process_init(cp);\n@@ -1719,8 +1721,19 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \t\tstrvec_pushf(&cp->args, \"--submodule-prefix=%s%s/\",\n \t\t\t     spf->prefix, task->sub->path);\n \n-\t\t/* NEEDSWORK: have get_default_remote from submodule--helper */\n-\t\tstrvec_push(&cp->args, \"origin\");\n+\t\tcp_remote.git_cmd = 1;\n+\t\tstrvec_pushl(&cp_remote.args, \"submodule--helper\",\n+\t\t\t     \"get-default-remote\", task->sub->path, NULL);\n+\n+\t\tif (!capture_command(&cp_remote, &remote_name, 0)) {\n+\t\t\tstrbuf_trim_trailing_newline(&remote_name);\n+\t\t\tstrvec_push(&cp->args, remote_name.buf);\n+\t\t} else {\n+\t\t\t/* Fallback to \"origin\" if the helper fails */\n+\t\t\tstrvec_push(&cp->args, \"origin\");\n+\t\t}\n+\t\tstrbuf_release(&remote_name);\n+\n \t\toid_array_for_each_unique(task->commits,\n \t\t\t\t\t  append_oid_to_argv, &cp->args);\n \ndiff --git a/t/meson.build b/t/meson.build\nindex 459c52a489..ef6cdab165 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -887,6 +887,7 @@ integration_tests = [\n   't7422-submodule-output.sh',\n   't7423-submodule-symlinks.sh',\n   't7424-submodule-mixed-ref-formats.sh',\n+  't7425-submodule-get-default-remote.sh',\n   't7450-bad-git-dotfiles.sh',\n   't7500-commit-template-squash-signoff.sh',\n   't7501-commit-basic-functionality.sh',\ndiff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh\nindex 5e566205ba..a5a273b392 100755\n--- a/t/t5526-fetch-submodules.sh\n+++ b/t/t5526-fetch-submodules.sh\n@@ -929,6 +929,58 @@ test_expect_success 'fetch new submodule commit intermittently referenced by sup\n \t)\n '\n \n+test_expect_success 'fetch --recurse-submodules works with custom remote names' '\n+\t# depends on the previous test for setup\n+\n+\t# Rename the remote in sub1 from \"origin\" to \"custom_remote\"\n+\tgit -C downstream/sub1 remote rename origin custom_remote &&\n+\n+\t# Create new commits in the original submodules\n+\tC=$(git -C submodule commit-tree -m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom1 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree -m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom2 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit -m \"updated submodules outside of refs/heads for custom remote\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom3 $E &&\n+\t(\n+\t\tcd downstream &&\n+\t\tgit fetch --recurse-submodules origin refs/changes/custom3:refs/heads/my_other_branch &&\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n+test_expect_success 'fetch new submodule commit on-demand in FETCH_HEAD from custom remote' '\n+\t# depends on the previous test for setup\n+\n+\tC=$(git -C submodule commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom4 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom5 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit -m \"updated submodules outside of refs/heads\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom6 $E &&\n+\t(\n+\t\tcd downstream &&\n+\t\tgit fetch --recurse-submodules origin refs/changes/custom6 &&\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n add_commit_push () {\n \tdir=\"$1\" &&\n \tmsg=\"$2\" &&\ndiff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\nindex 45f384dd32..faafe31409 100755\n--- a/t/t5572-pull-submodule.sh\n+++ b/t/t5572-pull-submodule.sh\n@@ -257,7 +257,26 @@ test_expect_success 'fetch submodule remote of different name from superproject'\n \tgit -C a-submodule reset --hard HEAD^^ &&\n \n \tgit -C child pull --no-recurse-submodules &&\n-\tgit -C child submodule update\n+\tgit -C child submodule update &&\n+\ttest_path_is_file child/a-submodule/moreecho.t\n+'\n+\n+test_expect_success 'fetch submodule remote of different non-origin name from superproject' '\n+\tgit -C child/a-submodule remote rename origin o2 &&\n+\n+\t# Create commit that is unreachable from current master branch\n+\tgit -C a-submodule checkout -b newmain2 master^ &&\n+\ttest_commit -C a-submodule echo_o2 &&\n+\ttest_commit -C a-submodule moreecho_o2 &&\n+\tsubc=$(git -C a-submodule rev-parse --short HEAD) &&\n+\n+\tgit -C parent/a-submodule fetch &&\n+\tgit -C parent/a-submodule checkout \"$subc\" &&\n+\tgit -C parent commit -m \"update submodule o2\" a-submodule &&\n+\tgit -C a-submodule reset --hard HEAD^^ &&\n+\n+\tgit -C child pull --recurse-submodules &&\n+\ttest_path_is_file child/a-submodule/moreecho_o2.t\n '\n \n test_done\ndiff --git a/t/t7425-submodule-get-default-remote.sh b/t/t7425-submodule-get-default-remote.sh\nnew file mode 100755\nindex 0000000000..b842af9a2d\n--- /dev/null\n+++ b/t/t7425-submodule-get-default-remote.sh\n@@ -0,0 +1,186 @@\n+#!/bin/sh\n+\n+test_description='git submodule--helper get-default-remote'\n+\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tgit config --global protocol.file.allow always\n+'\n+\n+test_expect_success 'setup repositories' '\n+\t# Create a repository to be used as submodule\n+\tgit init sub &&\n+\ttest_commit --no-tag -C sub \"initial commit in sub\" file.txt \"sub content\" &&\n+\n+\t# Create main repository\n+\tgit init super &&\n+\t(\n+\t\tcd super &&\n+\t\tmkdir subdir &&\n+\t\ttest_commit --no-tag -C subdir \"initial commit in super\" main.txt \"super content\" &&\n+\t\tgit submodule add ../sub subpath &&\n+\t\tgit commit -m \"add submodule 'sub' at subpath\"\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns origin for initialized submodule' '\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule update --init &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works from subdirectory' '\n+\t(\n+\t\tcd super/subdir &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote ../subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-existent path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote nonexistent 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-submodule path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subdir 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails without path argument' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with too many arguments' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subpath subdir 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'setup submodule with non-origin default remote name' '\n+\t# Create another submodule path with a different remote name\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ../sub upstream-subpath &&\n+\t\tgit commit -m \"add second submodule in upstream-subpath\" &&\n+\t\tgit submodule update --init upstream-subpath &&\n+\n+\t\t# Change the remote name in the submodule\n+\t\tcd upstream-subpath &&\n+\t\tgit remote rename origin upstream\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns non-origin remote name' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes' '\n+\t(\n+\t\tcd super/subpath &&\n+\t\tgit remote add other-upstream ../../sub &&\n+\t\tgit remote add myfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes and none are origin' '\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit remote add yet-another-upstream ../../sub &&\n+\t\tgit remote add yourfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'setup nested submodule with non-origin remote' '\n+\tgit init innersub &&\n+\ttest_commit --no-tag -C innersub \"initial commit in innersub\" inner.txt \"innersub content\" &&\n+\n+\t(\n+\t\tcd sub &&\n+\t\tgit submodule add ../innersub innersubpath &&\n+\t\tgit commit -m \"add nested submodule at innersubpath\"\n+\t) &&\n+\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit pull upstream &&\n+\t\tgit submodule update --init --recursive . &&\n+\t\t(\n+\t\t\tcd innersubpath &&\n+\t\t\tgit remote rename origin another_upstream\n+\t\t)\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with nested submodule' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"another_upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath/innersubpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with submodule that has no remotes' '\n+\t# Create a submodule directory manually without remotes\n+\t(\n+\t\tcd super &&\n+\t\tgit init no-remote-sub &&\n+\t\ttest_commit --no-tag -C no-remote-sub \"local commit\" local.txt \"local content\"\n+\t) &&\n+\n+\t# Add it as a submodule\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ./no-remote-sub &&\n+\t\tgit commit -m \"add local submodule 'no-remote-sub'\"\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\t# Should fall back to \"origin\" remote name when no remotes exist\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote no-remote-sub >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_done\n-- \n2.52.0\n\n"},{"id":"534480","messageId":"xmqq5x8to53y.fsf@gitster.g","threadId":"64785","inReplyTo":"20260122152722.866341-1-nasser.grainawi@oss.qualcomm.com","subject":"Re: [PATCH v3] submodule: fetch missing objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-22T18:49:05Z","receivedAt":"2026-01-22T18:49:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> writes:\n\n> When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\n> added support for fetching a missing submodule object by id, it\n> hardcoded the remote name as \"origin\" and deferred anything more\n> complicated for a later patch. Implement the NEEDSWORK item to remove\n> the hardcoded assumption by adding and using a submodule helper subcmd\n> 'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\n> succeed when the fetched commit(s) in the superproject trigger a\n> submodule fetch, and that submodule's default remote name is not\n> \"origin\".\n>\n> Add non-\"origin\" remote tests to t5526-fetch-submodules.sh and\n> t5572-pull-submodule.sh demonstrating this works as expected and add\n> dedicated tests for get-default-remote.\n>\n> Signed-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n> Reviewed-by: Jacob Keller <jacob.keller@gmail.com>\n> ---\n\nThanks.  Jacob, this v3 is not exactly the same as v1 that you\nreviewed (and range-diff relative to v2 does not capture what got\nchanged between the version you saw and this version), but I just\nchecked that they are \"essentially identical\" except for the\nproposed log message.  Are you happy with having your Reviewed-by on\nthis version?\n\n>  builtin/submodule--helper.c             |  38 +++++\n>  submodule.c                             |  17 ++-\n>  t/meson.build                           |   1 +\n>  t/t5526-fetch-submodules.sh             |  52 +++++++\n>  t/t5572-pull-submodule.sh               |  21 ++-\n>  t/t7425-submodule-get-default-remote.sh | 186 ++++++++++++++++++++++++\n>  6 files changed, 312 insertions(+), 3 deletions(-)\n>  create mode 100755 t/t7425-submodule-get-default-remote.sh\n>\n> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\n> index d537ab087a..b180a24091 100644\n> --- a/builtin/submodule--helper.c\n> +++ b/builtin/submodule--helper.c\n> @@ -112,6 +112,43 @@ static int get_default_remote_submodule(const char *module_path, char **default_\n>  \treturn 0;\n>  }\n>  \n> +static int module_get_default_remote(int argc, const char **argv, const char *prefix,\n> +\t\t\t\t     struct repository *repo UNUSED)\n> +{\n> +\tconst char *path;\n> +\tchar *resolved_path = NULL;\n> +\tchar *default_remote = NULL;\n> +\tint code;\n> +\tstruct option options[] = {\n> +\t\tOPT_END()\n> +\t};\n> +\tconst char *const usage[] = {\n> +\t\tN_(\"git submodule--helper get-default-remote <path>\"),\n> +\t\tNULL\n> +\t};\n> +\n> +\targc = parse_options(argc, argv, prefix, options, usage, 0);\n> +\tif (argc != 1)\n> +\t\tusage_with_options(usage, options);\n\nHmph, I am not sure what is going on.  What are we getting out of\nparse_options() here?  Would it be the same to see if we got\nanything remaining on the command line by checking argc and call\nusage_with_options() without calling parse_options(), or am I\nmissing something?\n\n> +\tpath = argv[0];\n> +\tif (prefix && *prefix && !is_absolute_path(path)) {\n> +\t\tresolved_path = xstrfmt(\"%s%s\", prefix, path);\n> +\t\tpath = resolved_path;\n> +\t}\n> +\n> +\tcode = get_default_remote_submodule(path, &default_remote);\n> +\tif (code) {\n> +\t\tfree(resolved_path);\n> +\t\treturn code;\n> +\t}\n> +\n> +\tprintf(\"%s\\n\", default_remote);\n\nDo we know that the value of default_remote has no funny bytes in\nit, like newline?  In the end the name has to become part of\nrefs/remotes/<name>/HEAD that has to be a valid refname, so not\ngiving any facility to quote funny bytes and allowing the caller of\nthis helper to assume a LF terminated single line should be fine, so\nI am guessing that the answer is yes, but I offhand do not know how\nwe know that we do not have to worry about such a situation in the\ncode path that begins with get_default_remote_submodule().\n\n> diff --git a/submodule.c b/submodule.c\n> index 40a5c6fb9d..6599657f34 100644\n> --- a/submodule.c\n> +++ b/submodule.c\n> @@ -1706,6 +1706,8 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n>  \tif (spf->oid_fetch_tasks_nr) {\n>  \t\tstruct fetch_task *task =\n>  \t\t\tspf->oid_fetch_tasks[spf->oid_fetch_tasks_nr - 1];\n> +\t\tstruct child_process cp_remote = CHILD_PROCESS_INIT;\n> +\t\tstruct strbuf remote_name = STRBUF_INIT;\n>  \t\tspf->oid_fetch_tasks_nr--;\n>  \n>  \t\tchild_process_init(cp);\n> @@ -1719,8 +1721,19 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n>  \t\tstrvec_pushf(&cp->args, \"--submodule-prefix=%s%s/\",\n>  \t\t\t     spf->prefix, task->sub->path);\n>  \n> -\t\t/* NEEDSWORK: have get_default_remote from submodule--helper */\n> -\t\tstrvec_push(&cp->args, \"origin\");\n> +\t\tcp_remote.git_cmd = 1;\n> +\t\tstrvec_pushl(&cp_remote.args, \"submodule--helper\",\n> +\t\t\t     \"get-default-remote\", task->sub->path, NULL);\n> +\n> +\t\tif (!capture_command(&cp_remote, &remote_name, 0)) {\n> +\t\t\tstrbuf_trim_trailing_newline(&remote_name);\n> +\t\t\tstrvec_push(&cp->args, remote_name.buf);\n> +\t\t} else {\n> +\t\t\t/* Fallback to \"origin\" if the helper fails */\n> +\t\t\tstrvec_push(&cp->args, \"origin\");\n> +\t\t}\n> +\t\tstrbuf_release(&remote_name);\n> +\n>  \t\toid_array_for_each_unique(task->commits,\n>  \t\t\t\t\t  append_oid_to_argv, &cp->args);\n\nOK, this part is quite straight-forward.\n\n> diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh\n> index 5e566205ba..a5a273b392 100755\n> --- a/t/t5526-fetch-submodules.sh\n> +++ b/t/t5526-fetch-submodules.sh\n> @@ -929,6 +929,58 @@ test_expect_success 'fetch new submodule commit intermittently referenced by sup\n>  \t)\n>  '\n>  \n> +test_expect_success 'fetch --recurse-submodules works with custom remote names' '\n> +\t# depends on the previous test for setup\n> +\n> +\t# Rename the remote in sub1 from \"origin\" to \"custom_remote\"\n> +\tgit -C downstream/sub1 remote rename origin custom_remote &&\n> +\n> +\t# Create new commits in the original submodules\n> +\tC=$(git -C submodule commit-tree -m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n> +\tgit -C submodule update-ref refs/changes/custom1 $C &&\n> +\tgit update-index --cacheinfo 160000 $C submodule &&\n> +\ttest_tick &&\n> +\n> +\tD=$(git -C sub1 commit-tree -m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n> +\tgit -C sub1 update-ref refs/changes/custom2 $D &&\n> +\tgit update-index --cacheinfo 160000 $D sub1 &&\n> +\n> +\tgit commit -m \"updated submodules outside of refs/heads for custom remote\" &&\n> +\tE=$(git rev-parse HEAD) &&\n> +\tgit update-ref refs/changes/custom3 $E &&\n> +\t(\n> +\t\tcd downstream &&\n> +\t\tgit fetch --recurse-submodules origin refs/changes/custom3:refs/heads/my_other_branch &&\n> +\t\tgit -C submodule cat-file -t $C &&\n> +\t\tgit -C sub1 cat-file -t $D &&\n> +\t\tgit checkout --recurse-submodules FETCH_HEAD\n> +\t)\n> +'\n\nHmph, these overly long lines are eyesore.  I wonder if we can do\nsomething about them?\n\nI also wonder if we want to make sure we are getting from the remote\nthat is given the custom name in a more direct way (instead of \"we\nsee that our fetch succeeds, and because there is no other remote,\nit must have gotten what is needed from the renamed one\"), or is it\ntoo much paranoia?\n\n> +test_expect_success 'fetch new submodule commit on-demand in FETCH_HEAD from custom remote' '\n> +\t# depends on the previous test for setup\n> +\n> +\tC=$(git -C submodule commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n> +\tgit -C submodule update-ref refs/changes/custom4 $C &&\n> +\tgit update-index --cacheinfo 160000 $C submodule &&\n> +\ttest_tick &&\n> +\n> +\tD=$(git -C sub1 commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n> +\tgit -C sub1 update-ref refs/changes/custom5 $D &&\n> +\tgit update-index --cacheinfo 160000 $D sub1 &&\n> +\n> +\tgit commit -m \"updated submodules outside of refs/heads\" &&\n> +\tE=$(git rev-parse HEAD) &&\n> +\tgit update-ref refs/changes/custom6 $E &&\n> +\t(\n> +\t\tcd downstream &&\n> +\t\tgit fetch --recurse-submodules origin refs/changes/custom6 &&\n> +\t\tgit -C submodule cat-file -t $C &&\n> +\t\tgit -C sub1 cat-file -t $D &&\n> +\t\tgit checkout --recurse-submodules FETCH_HEAD\n> +\t)\n> +'\n\nAre we testing anything new in this test, compared to the previous\none?  Both update the submodule sub1 by adding a new ref under\nrefs/changes/ hierachy and have \"git fetch --recurse-submodules\"\nfollow the changes.\n\n> diff --git a/t/t7425-submodule-get-default-remote.sh b/t/t7425-submodule-get-default-remote.sh\n> new file mode 100755\n> index 0000000000..b842af9a2d\n> --- /dev/null\n> +++ b/t/t7425-submodule-get-default-remote.sh\n> ...\n> +test_expect_success 'get-default-remote returns origin for initialized submodule' '\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit submodule update --init &&\n> +\t\techo \"origin\" >expect &&\n> +\t\tgit submodule--helper get-default-remote subpath >actual &&\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n\nOK, we have dedicated tests like these to ensure that when we say\n\"get default remote\" we get what we expect, which is very good.\n\nThe \"too much paranoia\" question above is primarily about \"are we\nreally saying get-default-remote properly at the place where it\nmatters?\"  IOW, we guarantee that the lower layer works as expected\nwith t7425, but the way we validate that we call the lower layer\ncorrectly felt a bit indirect.\n\nOther than these, looking very good.\n\nThanks.\n\n"},{"id":"534495","messageId":"CA+P7+xoYA6zhwVeCV6d5yW4ZxOKNLam3r-ayEDHpYpFqWyLduw@mail.gmail.com","threadId":"64785","inReplyTo":"xmqq5x8to53y.fsf@gitster.g","subject":"Re: [PATCH v3] submodule: fetch missing objects from default remote","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2026-01-22T20:16:53Z","receivedAt":"2026-01-22T20:17:06Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Jan 22, 2026 at 10:49 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> writes:\n>\n> > When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\n> > added support for fetching a missing submodule object by id, it\n> > hardcoded the remote name as \"origin\" and deferred anything more\n> > complicated for a later patch. Implement the NEEDSWORK item to remove\n> > the hardcoded assumption by adding and using a submodule helper subcmd\n> > 'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\n> > succeed when the fetched commit(s) in the superproject trigger a\n> > submodule fetch, and that submodule's default remote name is not\n> > \"origin\".\n> >\n> > Add non-\"origin\" remote tests to t5526-fetch-submodules.sh and\n> > t5572-pull-submodule.sh demonstrating this works as expected and add\n> > dedicated tests for get-default-remote.\n> >\n> > Signed-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n> > Reviewed-by: Jacob Keller <jacob.keller@gmail.com>\n> > ---\n>\n> Thanks.  Jacob, this v3 is not exactly the same as v1 that you\n> reviewed (and range-diff relative to v2 does not capture what got\n> changed between the version you saw and this version), but I just\n> checked that they are \"essentially identical\" except for the\n> proposed log message.  Are you happy with having your Reviewed-by on\n> this version?\n>\n\nI re-reviewed the patch and everything looks fine to me:\n\nReviewed-by: Jacob Keller <jacob.keller@gmail.com>\n"},{"id":"534499","messageId":"xmqq1pjhmlh0.fsf@gitster.g","threadId":"64785","inReplyTo":"CA+P7+xoYA6zhwVeCV6d5yW4ZxOKNLam3r-ayEDHpYpFqWyLduw@mail.gmail.com","subject":"Re: [PATCH v3] submodule: fetch missing objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-22T20:38:35Z","receivedAt":"2026-01-22T20:38:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Keller <jacob.keller@gmail.com> writes:\n\n>> Thanks.  Jacob, this v3 is not exactly the same as v1 that you\n>> reviewed (and range-diff relative to v2 does not capture what got\n>> changed between the version you saw and this version), but I just\n>> checked that they are \"essentially identical\" except for the\n>> proposed log message.  Are you happy with having your Reviewed-by on\n>> this version?\n>>\n>\n> I re-reviewed the patch and everything looks fine to me:\n>\n> Reviewed-by: Jacob Keller <jacob.keller@gmail.com>\n\nThanks.  Queued.\n"},{"id":"534502","messageId":"xmqqsebxl4wm.fsf@gitster.g","threadId":"64785","inReplyTo":"20260122152722.866341-1-nasser.grainawi@oss.qualcomm.com","subject":"Re: [PATCH v3] submodule: fetch missing objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-22T21:21:45Z","receivedAt":"2026-01-22T21:21:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> writes:\n\n>  t/t7425-submodule-get-default-remote.sh | 186 ++++++++++++++++++++++++\n\nOne thing I forgot to notice is that t7425 is already taken by\nanother topic in flight in 'seen'.  Perhaps move it to t7426 or\nsomething, perhaps?\n\n"},{"id":"534588","messageId":"xmqq4iobhpvg.fsf@gitster.g","threadId":"64785","inReplyTo":"20260122152722.866341-1-nasser.grainawi@oss.qualcomm.com","subject":"Re: [PATCH v3] submodule: fetch missing objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-23T23:26:59Z","receivedAt":"2026-01-23T23:27:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> writes:\n\n> diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\n> index 45f384dd32..faafe31409 100755\n> --- a/t/t5572-pull-submodule.sh\n> +++ b/t/t5572-pull-submodule.sh\n> @@ -257,7 +257,26 @@ test_expect_success 'fetch submodule remote of different name from superproject'\n>  \tgit -C a-submodule reset --hard HEAD^^ &&\n>  \n>  \tgit -C child pull --no-recurse-submodules &&\n> -\tgit -C child submodule update\n> +\tgit -C child submodule update &&\n> +\ttest_path_is_file child/a-submodule/moreecho.t\n> +'\n> +\n> +test_expect_success 'fetch submodule remote of different non-origin name from superproject' '\n> +\tgit -C child/a-submodule remote rename origin o2 &&\n> +\n> +\t# Create commit that is unreachable from current master branch\n> +\tgit -C a-submodule checkout -b newmain2 master^ &&\n\nThis test assumes that the first branch created by default is\n'master', which will break in one of the CI jobs:\n\n  https://github.com/git/git/actions/runs/21304166518/job/61328461844#step:9:1942\n\nIf we are assuming that we are on the default branch when this\n\"Create commit\" step runs, perhaps you can replace your \"master^\"\nwith \"HEAD^\" to achieve the same effect in a way that works\nregardless of what the default branch is called?\n"},{"id":"534590","messageId":"xmqqms23lpn2.fsf@gitster.g","threadId":"64785","inReplyTo":"xmqq4iobhpvg.fsf@gitster.g","subject":"Re: [PATCH v3] submodule: fetch missing objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-24T02:18:25Z","receivedAt":"2026-01-24T02:18:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> writes:\n>\n>> diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\n>> index 45f384dd32..faafe31409 100755\n>> --- a/t/t5572-pull-submodule.sh\n>> +++ b/t/t5572-pull-submodule.sh\n>> @@ -257,7 +257,26 @@ test_expect_success 'fetch submodule remote of different name from superproject'\n>>  \tgit -C a-submodule reset --hard HEAD^^ &&\n>>  \n>>  \tgit -C child pull --no-recurse-submodules &&\n>> -\tgit -C child submodule update\n>> +\tgit -C child submodule update &&\n>> +\ttest_path_is_file child/a-submodule/moreecho.t\n>> +'\n>> +\n>> +test_expect_success 'fetch submodule remote of different non-origin name from superproject' '\n>> +\tgit -C child/a-submodule remote rename origin o2 &&\n>> +\n>> +\t# Create commit that is unreachable from current master branch\n>> +\tgit -C a-submodule checkout -b newmain2 master^ &&\n>\n> This test assumes that the first branch created by default is\n> 'master', which will break in one of the CI jobs:\n>\n>   https://github.com/git/git/actions/runs/21304166518/job/61328461844#step:9:1942\n\nFor now, I've queued two fix-up patches on top of the posted patch\nto avoid CI breakages when the topic is merged to 'seen'.  One is to\nrename t7425-submodule-get-default-remote.sh to t7426-submodule-get-default-remote.sh\n(both filename and the reference to it in t/meson.build), and the\nother one is the following.\n\n----- >8 -----\nSubject: [PATCH] SQUASH??? fixup\n\nThe test as posted breaks when run with\n\n    $ make WITH_BREAKING_CHANGES=YesPlease test\n\nas the added part assumes that the default branch name is \"master\".\n\nThis band-aid is sufficient for the purpose of the maintainer to get\nthe CI passing, but the real solution should probably be done better\nin such a way that the latter step does not have to rely on the\ncreation of \"anchorpoint\" in the previous step.  I'll leave it to\nthe contributor of the topic.\n---\n t/t5572-pull-submodule.sh | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\nindex faafe31409..dfc07d050b 100755\n--- a/t/t5572-pull-submodule.sh\n+++ b/t/t5572-pull-submodule.sh\n@@ -246,7 +246,8 @@ test_expect_success 'fetch submodule remote of different name from superproject'\n \tgit -C child submodule update --init &&\n \n \t# Needs to create unreachable commit from current master branch.\n-\tgit -C a-submodule checkout -b newmain HEAD^ &&\n+\tgit -C a-submodule tag anchorpoint HEAD &&\n+\tgit -C a-submodule checkout -b newmain anchorpoint^ &&\n \ttest_commit -C a-submodule echo &&\n \ttest_commit -C a-submodule moreecho &&\n \tsubc=$(git -C a-submodule rev-parse --short HEAD) &&\n@@ -265,7 +266,7 @@ test_expect_success 'fetch submodule remote of different non-origin name from su\n \tgit -C child/a-submodule remote rename origin o2 &&\n \n \t# Create commit that is unreachable from current master branch\n-\tgit -C a-submodule checkout -b newmain2 master^ &&\n+\tgit -C a-submodule checkout -b newmain2 anchorpoint^ &&\n \ttest_commit -C a-submodule echo_o2 &&\n \ttest_commit -C a-submodule moreecho_o2 &&\n \tsubc=$(git -C a-submodule rev-parse --short HEAD) &&\n-- \n2.53.0-rc1-193-g609e9a7b29\n\n"},{"id":"536563","messageId":"xmqqzf53t3fm.fsf@gitster.g","threadId":"64785","inReplyTo":"xmqqms23lpn2.fsf@gitster.g","subject":"Re: [PATCH v3] submodule: fetch missing objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-20T23:12:29Z","receivedAt":"2026-02-20T23:12:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> ...\n>> This test assumes that the first branch created by default is\n>> 'master', which will break in one of the CI jobs:\n>>\n>>   https://github.com/git/git/actions/runs/21304166518/job/61328461844#step:9:1942\n>\n> For now, I've queued two fix-up patches on top of the posted patch\n> to avoid CI breakages when the topic is merged to 'seen'.  One is to\n> rename t7425-submodule-get-default-remote.sh to t7426-submodule-get-default-remote.sh\n> (both filename and the reference to it in t/meson.build), and the\n> other one is the following.\n>\n> ----- >8 -----\n> Subject: [PATCH] SQUASH??? fixup\n>\n> The test as posted breaks when run with\n>\n>     $ make WITH_BREAKING_CHANGES=YesPlease test\n>\n> as the added part assumes that the default branch name is \"master\".\n>\n> This band-aid is sufficient for the purpose of the maintainer to get\n> the CI passing, but the real solution should probably be done better\n> in such a way that the latter step does not have to rely on the\n> creation of \"anchorpoint\" in the previous step.  I'll leave it to\n> the contributor of the topic.\n> ---\n\nThis was from about a month ago, and we haven't heard from you.\nWill we see a hopefully small and final update [PATCH v4] of this\ntopic sometime soon?\n\nThanks.\n"},{"id":"537156","messageId":"xmqq7bs08p4r.fsf@gitster.g","threadId":"64785","inReplyTo":"CA+P7+xoYA6zhwVeCV6d5yW4ZxOKNLam3r-ayEDHpYpFqWyLduw@mail.gmail.com","subject":"Re: [PATCH v3] submodule: fetch missing objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-25T21:55:16Z","receivedAt":"2026-02-25T21:55:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Keller <jacob.keller@gmail.com> writes:\n\n> On Thu, Jan 22, 2026 at 10:49 AM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> writes:\n>> ...\n>> Thanks.  Jacob, this v3 is not exactly the same as v1 that you\n>> reviewed (and range-diff relative to v2 does not capture what got\n>> changed between the version you saw and this version), but I just\n>> checked that they are \"essentially identical\" except for the\n>> proposed log message.  Are you happy with having your Reviewed-by on\n>> this version?\n>>\n>\n> I re-reviewed the patch and everything looks fine to me:\n>\n> Reviewed-by: Jacob Keller <jacob.keller@gmail.com>\n\nWe actually needed a pair of small fixups on top, but I presume that\neven with them your reviewed-by still stands?\n\nThe following is with these fixes squashed in.\n\n builtin/submodule--helper.c             |  38 +++++++\n submodule.c                             |  17 ++-\n t/meson.build                           |   1 +\n t/t5526-fetch-submodules.sh             |  52 +++++++++\n t/t5572-pull-submodule.sh               |  24 ++++-\n t/t7426-submodule-get-default-remote.sh | 186 ++++++++++++++++++++++++++++++++\n 6 files changed, 314 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex d537ab087a..b180a24091 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -112,6 +112,43 @@ static int get_default_remote_submodule(const char *module_path, char **default_\n \treturn 0;\n }\n \n+static int module_get_default_remote(int argc, const char **argv, const char *prefix,\n+\t\t\t\t     struct repository *repo UNUSED)\n+{\n+\tconst char *path;\n+\tchar *resolved_path = NULL;\n+\tchar *default_remote = NULL;\n+\tint code;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper get-default-remote <path>\"),\n+\t\tNULL\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\tif (argc != 1)\n+\t\tusage_with_options(usage, options);\n+\n+\tpath = argv[0];\n+\tif (prefix && *prefix && !is_absolute_path(path)) {\n+\t\tresolved_path = xstrfmt(\"%s%s\", prefix, path);\n+\t\tpath = resolved_path;\n+\t}\n+\n+\tcode = get_default_remote_submodule(path, &default_remote);\n+\tif (code) {\n+\t\tfree(resolved_path);\n+\t\treturn code;\n+\t}\n+\n+\tprintf(\"%s\\n\", default_remote);\n+\tfree(default_remote);\n+\tfree(resolved_path);\n+\treturn 0;\n+}\n+\n /* the result should be freed by the caller. */\n static char *get_submodule_displaypath(const char *path, const char *prefix,\n \t\t\t\t       const char *super_prefix)\n@@ -3608,6 +3645,7 @@ int cmd_submodule__helper(int argc,\n \t\tOPT_SUBCOMMAND(\"set-url\", &fn, module_set_url),\n \t\tOPT_SUBCOMMAND(\"set-branch\", &fn, module_set_branch),\n \t\tOPT_SUBCOMMAND(\"create-branch\", &fn, module_create_branch),\n+\t\tOPT_SUBCOMMAND(\"get-default-remote\", &fn, module_get_default_remote),\n \t\tOPT_END()\n \t};\n \targc = parse_options(argc, argv, prefix, options, usage, 0);\ndiff --git a/submodule.c b/submodule.c\nindex 40a5c6fb9d..6599657f34 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1706,6 +1706,8 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \tif (spf->oid_fetch_tasks_nr) {\n \t\tstruct fetch_task *task =\n \t\t\tspf->oid_fetch_tasks[spf->oid_fetch_tasks_nr - 1];\n+\t\tstruct child_process cp_remote = CHILD_PROCESS_INIT;\n+\t\tstruct strbuf remote_name = STRBUF_INIT;\n \t\tspf->oid_fetch_tasks_nr--;\n \n \t\tchild_process_init(cp);\n@@ -1719,8 +1721,19 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \t\tstrvec_pushf(&cp->args, \"--submodule-prefix=%s%s/\",\n \t\t\t     spf->prefix, task->sub->path);\n \n-\t\t/* NEEDSWORK: have get_default_remote from submodule--helper */\n-\t\tstrvec_push(&cp->args, \"origin\");\n+\t\tcp_remote.git_cmd = 1;\n+\t\tstrvec_pushl(&cp_remote.args, \"submodule--helper\",\n+\t\t\t     \"get-default-remote\", task->sub->path, NULL);\n+\n+\t\tif (!capture_command(&cp_remote, &remote_name, 0)) {\n+\t\t\tstrbuf_trim_trailing_newline(&remote_name);\n+\t\t\tstrvec_push(&cp->args, remote_name.buf);\n+\t\t} else {\n+\t\t\t/* Fallback to \"origin\" if the helper fails */\n+\t\t\tstrvec_push(&cp->args, \"origin\");\n+\t\t}\n+\t\tstrbuf_release(&remote_name);\n+\n \t\toid_array_for_each_unique(task->commits,\n \t\t\t\t\t  append_oid_to_argv, &cp->args);\n \ndiff --git a/t/meson.build b/t/meson.build\nindex 459c52a489..6ff54cf276 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -887,6 +887,7 @@ integration_tests = [\n   't7422-submodule-output.sh',\n   't7423-submodule-symlinks.sh',\n   't7424-submodule-mixed-ref-formats.sh',\n+  't7426-submodule-get-default-remote.sh',\n   't7450-bad-git-dotfiles.sh',\n   't7500-commit-template-squash-signoff.sh',\n   't7501-commit-basic-functionality.sh',\ndiff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh\nindex 5e566205ba..a5a273b392 100755\n--- a/t/t5526-fetch-submodules.sh\n+++ b/t/t5526-fetch-submodules.sh\n@@ -929,6 +929,58 @@ test_expect_success 'fetch new submodule commit intermittently referenced by sup\n \t)\n '\n \n+test_expect_success 'fetch --recurse-submodules works with custom remote names' '\n+\t# depends on the previous test for setup\n+\n+\t# Rename the remote in sub1 from \"origin\" to \"custom_remote\"\n+\tgit -C downstream/sub1 remote rename origin custom_remote &&\n+\n+\t# Create new commits in the original submodules\n+\tC=$(git -C submodule commit-tree -m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom1 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree -m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom2 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit -m \"updated submodules outside of refs/heads for custom remote\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom3 $E &&\n+\t(\n+\t\tcd downstream &&\n+\t\tgit fetch --recurse-submodules origin refs/changes/custom3:refs/heads/my_other_branch &&\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n+test_expect_success 'fetch new submodule commit on-demand in FETCH_HEAD from custom remote' '\n+\t# depends on the previous test for setup\n+\n+\tC=$(git -C submodule commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom4 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom5 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit -m \"updated submodules outside of refs/heads\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom6 $E &&\n+\t(\n+\t\tcd downstream &&\n+\t\tgit fetch --recurse-submodules origin refs/changes/custom6 &&\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n add_commit_push () {\n \tdir=\"$1\" &&\n \tmsg=\"$2\" &&\ndiff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\nindex 45f384dd32..dfc07d050b 100755\n--- a/t/t5572-pull-submodule.sh\n+++ b/t/t5572-pull-submodule.sh\n@@ -246,7 +246,8 @@ test_expect_success 'fetch submodule remote of different name from superproject'\n \tgit -C child submodule update --init &&\n \n \t# Needs to create unreachable commit from current master branch.\n-\tgit -C a-submodule checkout -b newmain HEAD^ &&\n+\tgit -C a-submodule tag anchorpoint HEAD &&\n+\tgit -C a-submodule checkout -b newmain anchorpoint^ &&\n \ttest_commit -C a-submodule echo &&\n \ttest_commit -C a-submodule moreecho &&\n \tsubc=$(git -C a-submodule rev-parse --short HEAD) &&\n@@ -257,7 +258,26 @@ test_expect_success 'fetch submodule remote of different name from superproject'\n \tgit -C a-submodule reset --hard HEAD^^ &&\n \n \tgit -C child pull --no-recurse-submodules &&\n-\tgit -C child submodule update\n+\tgit -C child submodule update &&\n+\ttest_path_is_file child/a-submodule/moreecho.t\n+'\n+\n+test_expect_success 'fetch submodule remote of different non-origin name from superproject' '\n+\tgit -C child/a-submodule remote rename origin o2 &&\n+\n+\t# Create commit that is unreachable from current master branch\n+\tgit -C a-submodule checkout -b newmain2 anchorpoint^ &&\n+\ttest_commit -C a-submodule echo_o2 &&\n+\ttest_commit -C a-submodule moreecho_o2 &&\n+\tsubc=$(git -C a-submodule rev-parse --short HEAD) &&\n+\n+\tgit -C parent/a-submodule fetch &&\n+\tgit -C parent/a-submodule checkout \"$subc\" &&\n+\tgit -C parent commit -m \"update submodule o2\" a-submodule &&\n+\tgit -C a-submodule reset --hard HEAD^^ &&\n+\n+\tgit -C child pull --recurse-submodules &&\n+\ttest_path_is_file child/a-submodule/moreecho_o2.t\n '\n \n test_done\ndiff --git a/t/t7426-submodule-get-default-remote.sh b/t/t7426-submodule-get-default-remote.sh\nnew file mode 100755\nindex 0000000000..b842af9a2d\n--- /dev/null\n+++ b/t/t7426-submodule-get-default-remote.sh\n@@ -0,0 +1,186 @@\n+#!/bin/sh\n+\n+test_description='git submodule--helper get-default-remote'\n+\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tgit config --global protocol.file.allow always\n+'\n+\n+test_expect_success 'setup repositories' '\n+\t# Create a repository to be used as submodule\n+\tgit init sub &&\n+\ttest_commit --no-tag -C sub \"initial commit in sub\" file.txt \"sub content\" &&\n+\n+\t# Create main repository\n+\tgit init super &&\n+\t(\n+\t\tcd super &&\n+\t\tmkdir subdir &&\n+\t\ttest_commit --no-tag -C subdir \"initial commit in super\" main.txt \"super content\" &&\n+\t\tgit submodule add ../sub subpath &&\n+\t\tgit commit -m \"add submodule 'sub' at subpath\"\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns origin for initialized submodule' '\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule update --init &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works from subdirectory' '\n+\t(\n+\t\tcd super/subdir &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote ../subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-existent path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote nonexistent 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-submodule path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subdir 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails without path argument' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with too many arguments' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subpath subdir 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'setup submodule with non-origin default remote name' '\n+\t# Create another submodule path with a different remote name\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ../sub upstream-subpath &&\n+\t\tgit commit -m \"add second submodule in upstream-subpath\" &&\n+\t\tgit submodule update --init upstream-subpath &&\n+\n+\t\t# Change the remote name in the submodule\n+\t\tcd upstream-subpath &&\n+\t\tgit remote rename origin upstream\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns non-origin remote name' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes' '\n+\t(\n+\t\tcd super/subpath &&\n+\t\tgit remote add other-upstream ../../sub &&\n+\t\tgit remote add myfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes and none are origin' '\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit remote add yet-another-upstream ../../sub &&\n+\t\tgit remote add yourfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'setup nested submodule with non-origin remote' '\n+\tgit init innersub &&\n+\ttest_commit --no-tag -C innersub \"initial commit in innersub\" inner.txt \"innersub content\" &&\n+\n+\t(\n+\t\tcd sub &&\n+\t\tgit submodule add ../innersub innersubpath &&\n+\t\tgit commit -m \"add nested submodule at innersubpath\"\n+\t) &&\n+\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit pull upstream &&\n+\t\tgit submodule update --init --recursive . &&\n+\t\t(\n+\t\t\tcd innersubpath &&\n+\t\t\tgit remote rename origin another_upstream\n+\t\t)\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with nested submodule' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"another_upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath/innersubpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with submodule that has no remotes' '\n+\t# Create a submodule directory manually without remotes\n+\t(\n+\t\tcd super &&\n+\t\tgit init no-remote-sub &&\n+\t\ttest_commit --no-tag -C no-remote-sub \"local commit\" local.txt \"local content\"\n+\t) &&\n+\n+\t# Add it as a submodule\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ./no-remote-sub &&\n+\t\tgit commit -m \"add local submodule 'no-remote-sub'\"\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\t# Should fall back to \"origin\" remote name when no remotes exist\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote no-remote-sub >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_done\n"},{"id":"537308","messageId":"CAFcKa=-hQUnYKRRZzjmiUBbLtnq-oMe6TbiXwro1WrjQxX+MMA@mail.gmail.com","threadId":"64785","inReplyTo":"xmqqzf53t3fm.fsf@gitster.g","subject":"Re: [PATCH v3] submodule: fetch missing objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-02-27T17:20:38Z","receivedAt":"2026-02-27T17:20:52Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"On Fri, Feb 20, 2026 at 4:12 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n> >> ...\n> >> This test assumes that the first branch created by default is\n> >> 'master', which will break in one of the CI jobs:\n> >>\n> >>   https://github.com/git/git/actions/runs/21304166518/job/61328461844#step:9:1942\n> >\n> > For now, I've queued two fix-up patches on top of the posted patch\n> > to avoid CI breakages when the topic is merged to 'seen'.  One is to\n> > rename t7425-submodule-get-default-remote.sh to t7426-submodule-get-default-remote.sh\n> > (both filename and the reference to it in t/meson.build), and the\n> > other one is the following.\n> >\n> > ----- >8 -----\n> > Subject: [PATCH] SQUASH??? fixup\n> >\n> > The test as posted breaks when run with\n> >\n> >     $ make WITH_BREAKING_CHANGES=YesPlease test\n> >\n> > as the added part assumes that the default branch name is \"master\".\n> >\n> > This band-aid is sufficient for the purpose of the maintainer to get\n> > the CI passing, but the real solution should probably be done better\n> > in such a way that the latter step does not have to rely on the\n> > creation of \"anchorpoint\" in the previous step.  I'll leave it to\n> > the contributor of the topic.\n> > ---\n>\n> This was from about a month ago, and we haven't heard from you.\n> Will we see a hopefully small and final update [PATCH v4] of this\n> topic sometime soon?\n>\n\nYes, sorry about the delay. I was aiming for thorough and landed at\ntardy. I'll get my replies to your inline comments sent shortly and\nhopefully the v4 soon after that.\n"},{"id":"537315","messageId":"CAFcKa=9PLNDQcvM1bFq=8_nbP-Ha1qDVHSSwde=apiXTcAC+DQ@mail.gmail.com","threadId":"64785","inReplyTo":"xmqq5x8to53y.fsf@gitster.g","subject":"Re: [PATCH v3] submodule: fetch missing objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-02-27T18:29:40Z","receivedAt":"2026-02-27T18:29:52Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"On Thu, Jan 22, 2026 at 11:49 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> writes:\n> >\n> > diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\n> > index d537ab087a..b180a24091 100644\n> > --- a/builtin/submodule--helper.c\n> > +++ b/builtin/submodule--helper.c\n> > @@ -112,6 +112,43 @@ static int get_default_remote_submodule(const char *module_path, char **default_\n> >       return 0;\n> >  }\n> >\n> > +static int module_get_default_remote(int argc, const char **argv, const char *prefix,\n> > +                                  struct repository *repo UNUSED)\n> > +{\n> > +     const char *path;\n> > +     char *resolved_path = NULL;\n> > +     char *default_remote = NULL;\n> > +     int code;\n> > +     struct option options[] = {\n> > +             OPT_END()\n> > +     };\n> > +     const char *const usage[] = {\n> > +             N_(\"git submodule--helper get-default-remote <path>\"),\n> > +             NULL\n> > +     };\n> > +\n> > +     argc = parse_options(argc, argv, prefix, options, usage, 0);\n> > +     if (argc != 1)\n> > +             usage_with_options(usage, options);\n>\n> Hmph, I am not sure what is going on.  What are we getting out of\n> parse_options() here?  Would it be the same to see if we got\n> anything remaining on the command line by checking argc and call\n> usage_with_options() without calling parse_options(), or am I\n> missing something?\n\nI had found a few other places following this same pattern (for example:\ngc.c maintenance_stop() and notes.c list()) and I thought it was because\nparse_options() handles common options like '-h' and has standardized\nmessages for errors like unknown options.\n\n> > +     code = get_default_remote_submodule(path, &default_remote);\n> > +     if (code) {\n> > +             free(resolved_path);\n> > +             return code;\n> > +     }\n> > +\n> > +     printf(\"%s\\n\", default_remote);\n>\n> Do we know that the value of default_remote has no funny bytes in\n> it, like newline?  In the end the name has to become part of\n> refs/remotes/<name>/HEAD that has to be a valid refname, so not\n> giving any facility to quote funny bytes and allowing the caller of\n> this helper to assume a LF terminated single line should be fine, so\n> I am guessing that the answer is yes, but I offhand do not know how\n> we know that we do not have to worry about such a situation in the\n> code path that begins with get_default_remote_submodule().\n\nI don't think we know that, or at least I can't find proof of it. All I\ncan find is that remote.c issues a warning for remote names starting\nwith '/'. That seems to miss cases tested in t0602-reffiles-fsck.sh.\nHowever, I'm not sure if this helper should be responsible for\nvalidating the remote name as that seems like something remote.c should\nbe doing when parsing configs.\n\n> Hmph, these overly long lines are eyesore.  I wonder if we can do\n> something about them?\n\nI'll send a fixed version.\n\n> I also wonder if we want to make sure we are getting from the remote\n> that is given the custom name in a more direct way (instead of \"we\n> see that our fetch succeeds, and because there is no other remote,\n> it must have gotten what is needed from the renamed one\"), or is it\n> too much paranoia?\n\nI can capture the fetch command output and compare it to some expected\noutput where we have the remote paths, but that still doesn't show the\nremote name. But it looks like I can inspect the GIT_TRACE output and\ncompare the `git submodule--helper get-default-remote` and subsequent\n`git fetch` commands to expected output. I've added both methods to this\nnew test and the existing test it was modeled on so that it's obvious\nthere's a difference between them.\n\n>\n> > +test_expect_success 'fetch new submodule commit on-demand in FETCH_HEAD from custom remote' '\n> > +     # depends on the previous test for setup\n> > +\n> > +     C=$(git -C submodule commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n> > +     git -C submodule update-ref refs/changes/custom4 $C &&\n> > +     git update-index --cacheinfo 160000 $C submodule &&\n> > +     test_tick &&\n> > +\n> > +     D=$(git -C sub1 commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n> > +     git -C sub1 update-ref refs/changes/custom5 $D &&\n> > +     git update-index --cacheinfo 160000 $D sub1 &&\n> > +\n> > +     git commit -m \"updated submodules outside of refs/heads\" &&\n> > +     E=$(git rev-parse HEAD) &&\n> > +     git update-ref refs/changes/custom6 $E &&\n> > +     (\n> > +             cd downstream &&\n> > +             git fetch --recurse-submodules origin refs/changes/custom6 &&\n> > +             git -C submodule cat-file -t $C &&\n> > +             git -C sub1 cat-file -t $D &&\n> > +             git checkout --recurse-submodules FETCH_HEAD\n> > +     )\n> > +'\n>\n> Are we testing anything new in this test, compared to the previous\n> one?  Both update the submodule sub1 by adding a new ref under\n> refs/changes/ hierachy and have \"git fetch --recurse-submodules\"\n> follow the changes.\n\nI think the only difference is not creating a new superproject ref under\nrefs/heads/ when we fetch. I mirrored it after the test above 'fetch new\nsubmodule commit on-demand in FETCH_HEAD', but for the intent of testing\nthis remote name feature, I don't think we need both. I don't mind\ndropping it if you think it's unnecessary.\n"},{"id":"537422","messageId":"20260301025327.3845292-1-nasser.grainawi@oss.qualcomm.com","threadId":"64785","inReplyTo":"20260122152722.866341-1-nasser.grainawi@oss.qualcomm.com","subject":"[PATCH v4] submodule: fetch missing objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-03-01T02:53:27Z","receivedAt":"2026-03-01T02:54:04Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\nadded support for fetching a missing submodule object by id, it\nhardcoded the remote name as \"origin\" and deferred anything more\ncomplicated for a later patch. Implement the NEEDSWORK item to remove\nthe hardcoded assumption by adding and using a submodule helper subcmd\n'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\nsucceed when the fetched commit(s) in the superproject trigger a\nsubmodule fetch, and that submodule's default remote name is not\n\"origin\".\n\nAdd non-\"origin\" remote tests to t5526-fetch-submodules.sh and\nt5572-pull-submodule.sh demonstrating this works as expected and add\ndedicated tests for get-default-remote.\n\nSigned-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n---\nI removed Jacob Keller's Reviewed-By since there are more significant\nedits to the tests in v4 that they haven't reviewed.\n\nv4 includes fixes for the issues Junio patched in seen and I confirmed\nit merges cleanly to seen (with v3 reverted) as well as next and passes\ntests.\n\nRange-diff against v3:\n1:  1dd17e9f75 ! 1:  9c5a5df9a2 submodule: fetch missing objects from default remote\n    @@ submodule.c: static int get_next_submodule(struct child_process *cp, struct strb\n     \n      ## t/meson.build ##\n     @@ t/meson.build: integration_tests = [\n    -   't7422-submodule-output.sh',\n        't7423-submodule-symlinks.sh',\n        't7424-submodule-mixed-ref-formats.sh',\n    -+  't7425-submodule-get-default-remote.sh',\n    +   't7425-submodule-gitdir-path-extension.sh',\n    ++  't7426-submodule-get-default-remote.sh',\n        't7450-bad-git-dotfiles.sh',\n        't7500-commit-template-squash-signoff.sh',\n        't7501-commit-basic-functionality.sh',\n     \n      ## t/t5526-fetch-submodules.sh ##\n    +@@ t/t5526-fetch-submodules.sh: test_expect_success \"fetch new submodule commits on-demand outside standard refs\n    + \tgit update-ref refs/changes/3 $E &&\n    + \t(\n    + \t\tcd downstream &&\n    +-\t\tgit fetch --recurse-submodules origin refs/changes/3:refs/heads/my_branch &&\n    ++\t\tDEEP_START=$(git -C submodule/subdir/deepsubmodule rev-parse --short origin/deep) &&\n    ++\t\tDEEP_END=$(git -C \"$pwd/deepsubmodule\" rev-parse --short deep) &&\n    ++\t\tcat >\"expect_fetch\" <<-EOF &&\n    ++\t\tFrom $pwd/.\n    ++\t\t * [new ref]         refs/changes/3 -> my_branch\n    ++\t\tFetching submodule sub1\n    ++\t\tFetching submodule sub1/subdir/deepsubmodule\n    ++\t\tFetching submodule submodule\n    ++\t\tFetching submodule submodule/subdir/deepsubmodule\n    ++\t\tFrom $pwd/deepsubmodule\n    ++\t\t   $DEEP_START..$DEEP_END  deep       -> origin/deep\n    ++\t\tFrom $pwd/./sub1\n    ++\t\t * branch            $D -> FETCH_HEAD\n    ++\t\tFetching submodule sub1/subdir/deepsubmodule\n    ++\t\tFrom $pwd/submodule\n    ++\t\t * branch            $C -> FETCH_HEAD\n    ++\t\tFetching submodule submodule/subdir/deepsubmodule\n    ++\t\tEOF\n    ++\t\ttest_when_finished \"rm -f $pwd/on-demand_submodule_fetch_trace\" &&\n    ++\t\tGIT_TRACE=\"$pwd/on-demand_submodule_fetch_trace\" \\\n    ++\t\tgit fetch --recurse-submodules origin refs/changes/3:refs/heads/my_branch 2>actual_fetch &&\n    ++\t\ttest_cmp expect_fetch actual_fetch &&\n    + \t\tgit -C submodule cat-file -t $C &&\n    + \t\tgit -C sub1 cat-file -t $D &&\n    ++\t\ttest_grep \"trace: built-in: git submodule--helper get-default-remote sub1\" \\\n    ++\t\t\t\"$pwd/on-demand_submodule_fetch_trace\" &&\n    ++\t\ttest_grep \"trace: built-in: git fetch .* --submodule-prefix=sub1/ origin\" \\\n    ++\t\t\t\"$pwd/on-demand_submodule_fetch_trace\" &&\n    + \t\tgit checkout --recurse-submodules FETCH_HEAD\n    + \t)\n    + '\n     @@ t/t5526-fetch-submodules.sh: test_expect_success 'fetch new submodule commit intermittently referenced by sup\n      \t)\n      '\n      \n    -+test_expect_success 'fetch --recurse-submodules works with custom remote names' '\n    ++test_expect_success 'fetch new submodule commits on-demand outside standard refspec with custom remote name' '\n     +\t# depends on the previous test for setup\n     +\n     +\t# Rename the remote in sub1 from \"origin\" to \"custom_remote\"\n     +\tgit -C downstream/sub1 remote rename origin custom_remote &&\n     +\n     +\t# Create new commits in the original submodules\n    -+\tC=$(git -C submodule commit-tree -m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n    ++\tC=$(git -C submodule commit-tree \\\n    ++\t\t-m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n     +\tgit -C submodule update-ref refs/changes/custom1 $C &&\n     +\tgit update-index --cacheinfo 160000 $C submodule &&\n     +\ttest_tick &&\n     +\n    -+\tD=$(git -C sub1 commit-tree -m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n    ++\tD=$(git -C sub1 commit-tree \\\n    ++\t\t-m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n     +\tgit -C sub1 update-ref refs/changes/custom2 $D &&\n     +\tgit update-index --cacheinfo 160000 $D sub1 &&\n     +\n    -+\tgit commit -m \"updated submodules outside of refs/heads for custom remote\" &&\n    ++\tgit commit \\\n    ++\t\t-m \"updated submodules outside of refs/heads for custom remote\" &&\n     +\tE=$(git rev-parse HEAD) &&\n     +\tgit update-ref refs/changes/custom3 $E &&\n     +\t(\n     +\t\tcd downstream &&\n    -+\t\tgit fetch --recurse-submodules origin refs/changes/custom3:refs/heads/my_other_branch &&\n    ++\t\tDEEP_START=$(git -C submodule/subdir/deepsubmodule rev-parse --short \\\n    ++\t\t\torigin/deep) &&\n    ++\t\tDEEP_END=$(git -C \"$pwd/deepsubmodule\" rev-parse --short deep) &&\n    ++\t\tcat >\"expect_fetch_custom\" <<-EOF &&\n    ++\t\tFrom $pwd/.\n    ++\t\t * [new ref]         refs/changes/custom3 -> my_other_branch\n    ++\t\tFetching submodule sub1\n    ++\t\tFetching submodule sub1/subdir/deepsubmodule\n    ++\t\tFetching submodule submodule\n    ++\t\tFetching submodule submodule/subdir/deepsubmodule\n    ++\t\tFrom $pwd/./sub1\n    ++\t\t * branch            $D -> FETCH_HEAD\n    ++\t\tFetching submodule sub1/subdir/deepsubmodule\n    ++\t\tFrom $pwd/submodule\n    ++\t\t * branch            $C -> FETCH_HEAD\n    ++\t\tFetching submodule submodule/subdir/deepsubmodule\n    ++\t\tEOF\n    ++\t\ttest_when_finished \"rm -f $pwd/custom_on-demand_submodule_fetch_trace\" &&\n    ++\t\tGIT_TRACE=\"$pwd/custom_on-demand_submodule_fetch_trace\" \\\n    ++\t\tgit fetch --recurse-submodules origin \\\n    ++\t\t\trefs/changes/custom3:refs/heads/my_other_branch \\\n    ++\t\t\t2>actual_fetch_custom &&\n    ++\t\t# the without .gitmodules test above causes warnings\n    ++\t\tgrep -v \"^warning: \" actual_fetch_custom >actual_fetch_warnings_removed &&\n    ++\t\ttest_cmp expect_fetch_custom actual_fetch_warnings_removed &&\n    ++\n     +\t\tgit -C submodule cat-file -t $C &&\n     +\t\tgit -C sub1 cat-file -t $D &&\n    ++\t\ttest_grep \"trace: built-in: git submodule--helper get-default-remote sub1\" \\\n    ++\t\t\t\"$pwd/custom_on-demand_submodule_fetch_trace\" &&\n    ++\t\ttest_grep \"trace: built-in: git fetch .* --submodule-prefix=sub1/ custom_remote $D\" \\\n    ++\t\t\t\"$pwd/custom_on-demand_submodule_fetch_trace\" &&\n     +\t\tgit checkout --recurse-submodules FETCH_HEAD\n     +\t)\n     +'\n    @@ t/t5572-pull-submodule.sh: test_expect_success 'fetch submodule remote of differ\n     +\ttest_path_is_file child/a-submodule/moreecho.t\n     +'\n     +\n    -+test_expect_success 'fetch submodule remote of different non-origin name from superproject' '\n    ++test_expect_success 'fetch non-origin submodule remote named different from superproject' '\n     +\tgit -C child/a-submodule remote rename origin o2 &&\n     +\n     +\t# Create commit that is unreachable from current master branch\n    -+\tgit -C a-submodule checkout -b newmain2 master^ &&\n    ++\t# newmain is already reset in the previous test\n     +\ttest_commit -C a-submodule echo_o2 &&\n     +\ttest_commit -C a-submodule moreecho_o2 &&\n     +\tsubc=$(git -C a-submodule rev-parse --short HEAD) &&\n    @@ t/t5572-pull-submodule.sh: test_expect_success 'fetch submodule remote of differ\n      \n      test_done\n     \n    - ## t/t7425-submodule-get-default-remote.sh (new) ##\n    + ## t/t7426-submodule-get-default-remote.sh (new) ##\n     @@\n     +#!/bin/sh\n     +\n\n builtin/submodule--helper.c             |  38 +++++\n submodule.c                             |  17 ++-\n t/meson.build                           |   1 +\n t/t5526-fetch-submodules.sh             | 111 +++++++++++++-\n t/t5572-pull-submodule.sh               |  21 ++-\n t/t7426-submodule-get-default-remote.sh | 186 ++++++++++++++++++++++++\n 6 files changed, 370 insertions(+), 4 deletions(-)\n create mode 100755 t/t7426-submodule-get-default-remote.sh\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex b621d14275..0a4676f3ba 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -113,6 +113,43 @@ static int get_default_remote_submodule(const char *module_path, char **default_\n \treturn 0;\n }\n \n+static int module_get_default_remote(int argc, const char **argv, const char *prefix,\n+\t\t\t\t     struct repository *repo UNUSED)\n+{\n+\tconst char *path;\n+\tchar *resolved_path = NULL;\n+\tchar *default_remote = NULL;\n+\tint code;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper get-default-remote <path>\"),\n+\t\tNULL\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\tif (argc != 1)\n+\t\tusage_with_options(usage, options);\n+\n+\tpath = argv[0];\n+\tif (prefix && *prefix && !is_absolute_path(path)) {\n+\t\tresolved_path = xstrfmt(\"%s%s\", prefix, path);\n+\t\tpath = resolved_path;\n+\t}\n+\n+\tcode = get_default_remote_submodule(path, &default_remote);\n+\tif (code) {\n+\t\tfree(resolved_path);\n+\t\treturn code;\n+\t}\n+\n+\tprintf(\"%s\\n\", default_remote);\n+\tfree(default_remote);\n+\tfree(resolved_path);\n+\treturn 0;\n+}\n+\n /* the result should be freed by the caller. */\n static char *get_submodule_displaypath(const char *path, const char *prefix,\n \t\t\t\t       const char *super_prefix)\n@@ -3788,6 +3825,7 @@ int cmd_submodule__helper(int argc,\n \t\tOPT_SUBCOMMAND(\"set-url\", &fn, module_set_url),\n \t\tOPT_SUBCOMMAND(\"set-branch\", &fn, module_set_branch),\n \t\tOPT_SUBCOMMAND(\"create-branch\", &fn, module_create_branch),\n+\t\tOPT_SUBCOMMAND(\"get-default-remote\", &fn, module_get_default_remote),\n \t\tOPT_END()\n \t};\n \targc = parse_options(argc, argv, prefix, options, usage, 0);\ndiff --git a/submodule.c b/submodule.c\nindex 508938e4da..906febfa0e 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1708,6 +1708,8 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \tif (spf->oid_fetch_tasks_nr) {\n \t\tstruct fetch_task *task =\n \t\t\tspf->oid_fetch_tasks[spf->oid_fetch_tasks_nr - 1];\n+\t\tstruct child_process cp_remote = CHILD_PROCESS_INIT;\n+\t\tstruct strbuf remote_name = STRBUF_INIT;\n \t\tspf->oid_fetch_tasks_nr--;\n \n \t\tchild_process_init(cp);\n@@ -1721,8 +1723,19 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \t\tstrvec_pushf(&cp->args, \"--submodule-prefix=%s%s/\",\n \t\t\t     spf->prefix, task->sub->path);\n \n-\t\t/* NEEDSWORK: have get_default_remote from submodule--helper */\n-\t\tstrvec_push(&cp->args, \"origin\");\n+\t\tcp_remote.git_cmd = 1;\n+\t\tstrvec_pushl(&cp_remote.args, \"submodule--helper\",\n+\t\t\t     \"get-default-remote\", task->sub->path, NULL);\n+\n+\t\tif (!capture_command(&cp_remote, &remote_name, 0)) {\n+\t\t\tstrbuf_trim_trailing_newline(&remote_name);\n+\t\t\tstrvec_push(&cp->args, remote_name.buf);\n+\t\t} else {\n+\t\t\t/* Fallback to \"origin\" if the helper fails */\n+\t\t\tstrvec_push(&cp->args, \"origin\");\n+\t\t}\n+\t\tstrbuf_release(&remote_name);\n+\n \t\toid_array_for_each_unique(task->commits,\n \t\t\t\t\t  append_oid_to_argv, &cp->args);\n \ndiff --git a/t/meson.build b/t/meson.build\nindex e5174ee575..bf241a7a1e 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -894,6 +894,7 @@ integration_tests = [\n   't7423-submodule-symlinks.sh',\n   't7424-submodule-mixed-ref-formats.sh',\n   't7425-submodule-gitdir-path-extension.sh',\n+  't7426-submodule-get-default-remote.sh',\n   't7450-bad-git-dotfiles.sh',\n   't7500-commit-template-squash-signoff.sh',\n   't7501-commit-basic-functionality.sh',\ndiff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh\nindex 5e566205ba..19c0f05144 100755\n--- a/t/t5526-fetch-submodules.sh\n+++ b/t/t5526-fetch-submodules.sh\n@@ -836,9 +836,34 @@ test_expect_success \"fetch new submodule commits on-demand outside standard refs\n \tgit update-ref refs/changes/3 $E &&\n \t(\n \t\tcd downstream &&\n-\t\tgit fetch --recurse-submodules origin refs/changes/3:refs/heads/my_branch &&\n+\t\tDEEP_START=$(git -C submodule/subdir/deepsubmodule rev-parse --short origin/deep) &&\n+\t\tDEEP_END=$(git -C \"$pwd/deepsubmodule\" rev-parse --short deep) &&\n+\t\tcat >\"expect_fetch\" <<-EOF &&\n+\t\tFrom $pwd/.\n+\t\t * [new ref]         refs/changes/3 -> my_branch\n+\t\tFetching submodule sub1\n+\t\tFetching submodule sub1/subdir/deepsubmodule\n+\t\tFetching submodule submodule\n+\t\tFetching submodule submodule/subdir/deepsubmodule\n+\t\tFrom $pwd/deepsubmodule\n+\t\t   $DEEP_START..$DEEP_END  deep       -> origin/deep\n+\t\tFrom $pwd/./sub1\n+\t\t * branch            $D -> FETCH_HEAD\n+\t\tFetching submodule sub1/subdir/deepsubmodule\n+\t\tFrom $pwd/submodule\n+\t\t * branch            $C -> FETCH_HEAD\n+\t\tFetching submodule submodule/subdir/deepsubmodule\n+\t\tEOF\n+\t\ttest_when_finished \"rm -f $pwd/on-demand_submodule_fetch_trace\" &&\n+\t\tGIT_TRACE=\"$pwd/on-demand_submodule_fetch_trace\" \\\n+\t\tgit fetch --recurse-submodules origin refs/changes/3:refs/heads/my_branch 2>actual_fetch &&\n+\t\ttest_cmp expect_fetch actual_fetch &&\n \t\tgit -C submodule cat-file -t $C &&\n \t\tgit -C sub1 cat-file -t $D &&\n+\t\ttest_grep \"trace: built-in: git submodule--helper get-default-remote sub1\" \\\n+\t\t\t\"$pwd/on-demand_submodule_fetch_trace\" &&\n+\t\ttest_grep \"trace: built-in: git fetch .* --submodule-prefix=sub1/ origin\" \\\n+\t\t\t\"$pwd/on-demand_submodule_fetch_trace\" &&\n \t\tgit checkout --recurse-submodules FETCH_HEAD\n \t)\n '\n@@ -929,6 +954,90 @@ test_expect_success 'fetch new submodule commit intermittently referenced by sup\n \t)\n '\n \n+test_expect_success 'fetch new submodule commits on-demand outside standard refspec with custom remote name' '\n+\t# depends on the previous test for setup\n+\n+\t# Rename the remote in sub1 from \"origin\" to \"custom_remote\"\n+\tgit -C downstream/sub1 remote rename origin custom_remote &&\n+\n+\t# Create new commits in the original submodules\n+\tC=$(git -C submodule commit-tree \\\n+\t\t-m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom1 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree \\\n+\t\t-m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom2 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit \\\n+\t\t-m \"updated submodules outside of refs/heads for custom remote\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom3 $E &&\n+\t(\n+\t\tcd downstream &&\n+\t\tDEEP_START=$(git -C submodule/subdir/deepsubmodule rev-parse --short \\\n+\t\t\torigin/deep) &&\n+\t\tDEEP_END=$(git -C \"$pwd/deepsubmodule\" rev-parse --short deep) &&\n+\t\tcat >\"expect_fetch_custom\" <<-EOF &&\n+\t\tFrom $pwd/.\n+\t\t * [new ref]         refs/changes/custom3 -> my_other_branch\n+\t\tFetching submodule sub1\n+\t\tFetching submodule sub1/subdir/deepsubmodule\n+\t\tFetching submodule submodule\n+\t\tFetching submodule submodule/subdir/deepsubmodule\n+\t\tFrom $pwd/./sub1\n+\t\t * branch            $D -> FETCH_HEAD\n+\t\tFetching submodule sub1/subdir/deepsubmodule\n+\t\tFrom $pwd/submodule\n+\t\t * branch            $C -> FETCH_HEAD\n+\t\tFetching submodule submodule/subdir/deepsubmodule\n+\t\tEOF\n+\t\ttest_when_finished \"rm -f $pwd/custom_on-demand_submodule_fetch_trace\" &&\n+\t\tGIT_TRACE=\"$pwd/custom_on-demand_submodule_fetch_trace\" \\\n+\t\tgit fetch --recurse-submodules origin \\\n+\t\t\trefs/changes/custom3:refs/heads/my_other_branch \\\n+\t\t\t2>actual_fetch_custom &&\n+\t\t# the without .gitmodules test above causes warnings\n+\t\tgrep -v \"^warning: \" actual_fetch_custom >actual_fetch_warnings_removed &&\n+\t\ttest_cmp expect_fetch_custom actual_fetch_warnings_removed &&\n+\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\ttest_grep \"trace: built-in: git submodule--helper get-default-remote sub1\" \\\n+\t\t\t\"$pwd/custom_on-demand_submodule_fetch_trace\" &&\n+\t\ttest_grep \"trace: built-in: git fetch .* --submodule-prefix=sub1/ custom_remote $D\" \\\n+\t\t\t\"$pwd/custom_on-demand_submodule_fetch_trace\" &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n+test_expect_success 'fetch new submodule commit on-demand in FETCH_HEAD from custom remote' '\n+\t# depends on the previous test for setup\n+\n+\tC=$(git -C submodule commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom4 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom5 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit -m \"updated submodules outside of refs/heads\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom6 $E &&\n+\t(\n+\t\tcd downstream &&\n+\t\tgit fetch --recurse-submodules origin refs/changes/custom6 &&\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n add_commit_push () {\n \tdir=\"$1\" &&\n \tmsg=\"$2\" &&\ndiff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\nindex 45f384dd32..42d14328b6 100755\n--- a/t/t5572-pull-submodule.sh\n+++ b/t/t5572-pull-submodule.sh\n@@ -257,7 +257,26 @@ test_expect_success 'fetch submodule remote of different name from superproject'\n \tgit -C a-submodule reset --hard HEAD^^ &&\n \n \tgit -C child pull --no-recurse-submodules &&\n-\tgit -C child submodule update\n+\tgit -C child submodule update &&\n+\ttest_path_is_file child/a-submodule/moreecho.t\n+'\n+\n+test_expect_success 'fetch non-origin submodule remote named different from superproject' '\n+\tgit -C child/a-submodule remote rename origin o2 &&\n+\n+\t# Create commit that is unreachable from current master branch\n+\t# newmain is already reset in the previous test\n+\ttest_commit -C a-submodule echo_o2 &&\n+\ttest_commit -C a-submodule moreecho_o2 &&\n+\tsubc=$(git -C a-submodule rev-parse --short HEAD) &&\n+\n+\tgit -C parent/a-submodule fetch &&\n+\tgit -C parent/a-submodule checkout \"$subc\" &&\n+\tgit -C parent commit -m \"update submodule o2\" a-submodule &&\n+\tgit -C a-submodule reset --hard HEAD^^ &&\n+\n+\tgit -C child pull --recurse-submodules &&\n+\ttest_path_is_file child/a-submodule/moreecho_o2.t\n '\n \n test_done\ndiff --git a/t/t7426-submodule-get-default-remote.sh b/t/t7426-submodule-get-default-remote.sh\nnew file mode 100755\nindex 0000000000..b842af9a2d\n--- /dev/null\n+++ b/t/t7426-submodule-get-default-remote.sh\n@@ -0,0 +1,186 @@\n+#!/bin/sh\n+\n+test_description='git submodule--helper get-default-remote'\n+\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tgit config --global protocol.file.allow always\n+'\n+\n+test_expect_success 'setup repositories' '\n+\t# Create a repository to be used as submodule\n+\tgit init sub &&\n+\ttest_commit --no-tag -C sub \"initial commit in sub\" file.txt \"sub content\" &&\n+\n+\t# Create main repository\n+\tgit init super &&\n+\t(\n+\t\tcd super &&\n+\t\tmkdir subdir &&\n+\t\ttest_commit --no-tag -C subdir \"initial commit in super\" main.txt \"super content\" &&\n+\t\tgit submodule add ../sub subpath &&\n+\t\tgit commit -m \"add submodule 'sub' at subpath\"\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns origin for initialized submodule' '\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule update --init &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works from subdirectory' '\n+\t(\n+\t\tcd super/subdir &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote ../subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-existent path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote nonexistent 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-submodule path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subdir 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails without path argument' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with too many arguments' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subpath subdir 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'setup submodule with non-origin default remote name' '\n+\t# Create another submodule path with a different remote name\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ../sub upstream-subpath &&\n+\t\tgit commit -m \"add second submodule in upstream-subpath\" &&\n+\t\tgit submodule update --init upstream-subpath &&\n+\n+\t\t# Change the remote name in the submodule\n+\t\tcd upstream-subpath &&\n+\t\tgit remote rename origin upstream\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns non-origin remote name' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes' '\n+\t(\n+\t\tcd super/subpath &&\n+\t\tgit remote add other-upstream ../../sub &&\n+\t\tgit remote add myfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes and none are origin' '\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit remote add yet-another-upstream ../../sub &&\n+\t\tgit remote add yourfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'setup nested submodule with non-origin remote' '\n+\tgit init innersub &&\n+\ttest_commit --no-tag -C innersub \"initial commit in innersub\" inner.txt \"innersub content\" &&\n+\n+\t(\n+\t\tcd sub &&\n+\t\tgit submodule add ../innersub innersubpath &&\n+\t\tgit commit -m \"add nested submodule at innersubpath\"\n+\t) &&\n+\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit pull upstream &&\n+\t\tgit submodule update --init --recursive . &&\n+\t\t(\n+\t\t\tcd innersubpath &&\n+\t\t\tgit remote rename origin another_upstream\n+\t\t)\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with nested submodule' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"another_upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath/innersubpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with submodule that has no remotes' '\n+\t# Create a submodule directory manually without remotes\n+\t(\n+\t\tcd super &&\n+\t\tgit init no-remote-sub &&\n+\t\ttest_commit --no-tag -C no-remote-sub \"local commit\" local.txt \"local content\"\n+\t) &&\n+\n+\t# Add it as a submodule\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ./no-remote-sub &&\n+\t\tgit commit -m \"add local submodule 'no-remote-sub'\"\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\t# Should fall back to \"origin\" remote name when no remotes exist\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote no-remote-sub >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_done\n-- \n2.52.0\n\n"},{"id":"537617","messageId":"CA+P7+xqtupp=E8+MOOEJ-nB84QnTXL7FD549LwEmfQiwoUNHzQ@mail.gmail.com","threadId":"64785","inReplyTo":"xmqq7bs08p4r.fsf@gitster.g","subject":"Re: [PATCH v3] submodule: fetch missing objects from default remote","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2026-03-02T22:06:52Z","receivedAt":"2026-03-02T22:07:04Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Wed, Feb 25, 2026 at 1:55 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Jacob Keller <jacob.keller@gmail.com> writes:\n>\n> > On Thu, Jan 22, 2026 at 10:49 AM Junio C Hamano <gitster@pobox.com> wrote:\n> >>\n> >> Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> writes:\n> >> ...\n> >> Thanks.  Jacob, this v3 is not exactly the same as v1 that you\n> >> reviewed (and range-diff relative to v2 does not capture what got\n> >> changed between the version you saw and this version), but I just\n> >> checked that they are \"essentially identical\" except for the\n> >> proposed log message.  Are you happy with having your Reviewed-by on\n> >> this version?\n> >>\n> >\n> > I re-reviewed the patch and everything looks fine to me:\n> >\n> > Reviewed-by: Jacob Keller <jacob.keller@gmail.com>\n>\n> We actually needed a pair of small fixups on top, but I presume that\n> even with them your reviewed-by still stands?\n>\n\nI'll re-review the latest version.\n"},{"id":"537619","messageId":"CA+P7+xqC10D9a3zp7JO_yoK9Vjpg+xua7yH=fGXjApUYisTf4w@mail.gmail.com","threadId":"64785","inReplyTo":"20260301025327.3845292-1-nasser.grainawi@oss.qualcomm.com","subject":"Re: [PATCH v4] submodule: fetch missing objects from default remote","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2026-03-02T22:09:18Z","receivedAt":"2026-03-02T22:09:29Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Sat, Feb 28, 2026 at 6:53 PM Nasser Grainawi\n<nasser.grainawi@oss.qualcomm.com> wrote:\n>\n> When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\n> added support for fetching a missing submodule object by id, it\n> hardcoded the remote name as \"origin\" and deferred anything more\n> complicated for a later patch. Implement the NEEDSWORK item to remove\n> the hardcoded assumption by adding and using a submodule helper subcmd\n> 'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\n> succeed when the fetched commit(s) in the superproject trigger a\n> submodule fetch, and that submodule's default remote name is not\n> \"origin\".\n>\n> Add non-\"origin\" remote tests to t5526-fetch-submodules.sh and\n> t5572-pull-submodule.sh demonstrating this works as expected and add\n> dedicated tests for get-default-remote.\n>\n> Signed-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n> ---\n> I removed Jacob Keller's Reviewed-By since there are more significant\n> edits to the tests in v4 that they haven't reviewed.\n>\n> v4 includes fixes for the issues Junio patched in seen and I confirmed\n> it merges cleanly to seen (with v3 reverted) as well as next and passes\n> tests.\n>\n\nv4 looks good, thanks!\n\nReviewed-by: Jacob Keller <jacob.keller@gmail.com>\n"},{"id":"537620","messageId":"xmqqms0pyj8o.fsf@gitster.g","threadId":"64785","inReplyTo":"CA+P7+xqC10D9a3zp7JO_yoK9Vjpg+xua7yH=fGXjApUYisTf4w@mail.gmail.com","subject":"Re: [PATCH v4] submodule: fetch missing objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-02T22:11:35Z","receivedAt":"2026-03-02T22:11:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Keller <jacob.keller@gmail.com> writes:\n\n>> v4 includes fixes for the issues Junio patched in seen and I confirmed\n>> it merges cleanly to seen (with v3 reverted) as well as next and passes\n>> tests.\n>>\n>\n> v4 looks good, thanks!\n>\n> Reviewed-by: Jacob Keller <jacob.keller@gmail.com>\n\nThanks.  Will wiggle your Reviewed-by in while queuing the patch.\n\n"},{"id":"537640","messageId":"xmqqfr6hfyqq.fsf@gitster.g","threadId":"64785","inReplyTo":"20260301025327.3845292-1-nasser.grainawi@oss.qualcomm.com","subject":"Re: [PATCH v4] submodule: fetch missing objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-03T02:11:41Z","receivedAt":"2026-03-03T02:11:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> writes:\n\n> When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\n> added support for fetching a missing submodule object by id, it\n> hardcoded the remote name as \"origin\" and deferred anything more\n> complicated for a later patch. Implement the NEEDSWORK item to remove\n> the hardcoded assumption by adding and using a submodule helper subcmd\n> 'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\n> succeed when the fetched commit(s) in the superproject trigger a\n> submodule fetch, and that submodule's default remote name is not\n> \"origin\".\n>\n> Add non-\"origin\" remote tests to t5526-fetch-submodules.sh and\n> t5572-pull-submodule.sh demonstrating this works as expected and add\n> dedicated tests for get-default-remote.\n>\n> Signed-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n> ---\n> I removed Jacob Keller's Reviewed-By since there are more significant\n> edits to the tests in v4 that they haven't reviewed.\n>\n> v4 includes fixes for the issues Junio patched in seen and I confirmed\n> it merges cleanly to seen (with v3 reverted) as well as next and passes\n> tests.\n\nThe tests in this patch seems to be broken.  I didn't notice it\nbefore merging it to 'seen', so tonight's integration CI is expected\nto fail at the tip of 'seen'.\n\n\n\n*** prove (shell & unit tests) ***\n[18:10:13] t5526-fetch-submodules.sh .. 39/? \n\terror: bug in the test script: test_when_finished does nothing in a subshell\n[18:10:13] t5526-fetch-submodules.sh .. 42/?\n\terror: bug in the test script: test_when_finished does nothing in a subshell\n[18:10:13] t5526-fetch-submodules.sh .. Dubious, test returned 1 (wstat 256, 0x100)\nFailed 2/56 subtests \n[18:10:28]\n\nTest Summary Report\n-------------------\nt5526-fetch-submodules.sh (Wstat: 256 (exited 1) Tests: 56 Failed: 2)\n  Failed tests:  40, 44\n  Non-zero exit status: 1\n\n\n"},{"id":"537720","messageId":"CAFcKa=-ei+93P_Rp+2eKWW+43aSyg_hxHb9K6X5KmXEcS1VGvg@mail.gmail.com","threadId":"64785","inReplyTo":"xmqqfr6hfyqq.fsf@gitster.g","subject":"Re: [PATCH v4] submodule: fetch missing objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-03-03T19:00:17Z","receivedAt":"2026-03-03T19:00:30Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"On Mon, Mar 2, 2026 at 7:11 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> writes:\n>\n> > When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\n> > added support for fetching a missing submodule object by id, it\n> > hardcoded the remote name as \"origin\" and deferred anything more\n> > complicated for a later patch. Implement the NEEDSWORK item to remove\n> > the hardcoded assumption by adding and using a submodule helper subcmd\n> > 'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\n> > succeed when the fetched commit(s) in the superproject trigger a\n> > submodule fetch, and that submodule's default remote name is not\n> > \"origin\".\n> >\n> > Add non-\"origin\" remote tests to t5526-fetch-submodules.sh and\n> > t5572-pull-submodule.sh demonstrating this works as expected and add\n> > dedicated tests for get-default-remote.\n> >\n> > Signed-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n> > ---\n> > I removed Jacob Keller's Reviewed-By since there are more significant\n> > edits to the tests in v4 that they haven't reviewed.\n> >\n> > v4 includes fixes for the issues Junio patched in seen and I confirmed\n> > it merges cleanly to seen (with v3 reverted) as well as next and passes\n> > tests.\n>\n> The tests in this patch seems to be broken.  I didn't notice it\n> before merging it to 'seen', so tonight's integration CI is expected\n> to fail at the tip of 'seen'.\n>\n>\n>\n> *** prove (shell & unit tests) ***\n> [18:10:13] t5526-fetch-submodules.sh .. 39/?\n>         error: bug in the test script: test_when_finished does nothing in a subshell\n\nAnd this is how I learned about TEST_SHELL_PATH. My Ubuntu machine has\nsh linked to dash, so these errors weren't showing up for me.\n\nAlso, I just noticed I have $pwd in a couple places and need that to be\n$(pwd), so I will send both those fixes.\n"},{"id":"537722","messageId":"CAFcKa=-EH7YD0bjeUyC6EVijTStuFZYOzQHbAnoXRUtY694Khw@mail.gmail.com","threadId":"64785","inReplyTo":"CAFcKa=-ei+93P_Rp+2eKWW+43aSyg_hxHb9K6X5KmXEcS1VGvg@mail.gmail.com","subject":"Re: [PATCH v4] submodule: fetch missing objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-03-03T19:26:39Z","receivedAt":"2026-03-03T19:26:53Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"On Tue, Mar 3, 2026 at 12:00 PM Nasser Grainawi\n<nasser.grainawi@oss.qualcomm.com> wrote:\n>\n> Also, I just noticed I have $pwd in a couple places and need that to be\n> $(pwd), so I will send both those fixes.\n\nSorry, ignore this. L11 in this script has `pwd=$(pwd)`, so the $pwd\nusage was intentional and not problematic.\n"},{"id":"537726","messageId":"xmqq5x7cd6ly.fsf@gitster.g","threadId":"64785","inReplyTo":"CAFcKa=-EH7YD0bjeUyC6EVijTStuFZYOzQHbAnoXRUtY694Khw@mail.gmail.com","subject":"Re: [PATCH v4] submodule: fetch missing objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-03T20:02:17Z","receivedAt":"2026-03-03T20:02:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> writes:\n\n> On Tue, Mar 3, 2026 at 12:00 PM Nasser Grainawi\n> <nasser.grainawi@oss.qualcomm.com> wrote:\n>>\n>> Also, I just noticed I have $pwd in a couple places and need that to be\n>> $(pwd), so I will send both those fixes.\n>\n> Sorry, ignore this. L11 in this script has `pwd=$(pwd)`, so the $pwd\n> usage was intentional and not problematic.\n\nOh, then it would make it easier to fix, actually, since $pwd is\nsome fixed directory established long before the control flow went\ninto the subshell so the fix for test_when_finished that are used to\nremove things under $pwd/ (not $(pwd)/) would just be the matter of\nmoving them out of the subshell, setting the finished handlers up\nbefore we go into the subshells.\n\n"},{"id":"537727","messageId":"20260303200906.4118348-1-nasser.grainawi@oss.qualcomm.com","threadId":"64785","inReplyTo":"20260301025327.3845292-1-nasser.grainawi@oss.qualcomm.com","subject":"[PATCH v5] submodule: fetch missing objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-03-03T20:09:06Z","receivedAt":"2026-03-03T20:09:28Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\nadded support for fetching a missing submodule object by id, it\nhardcoded the remote name as \"origin\" and deferred anything more\ncomplicated for a later patch. Implement the NEEDSWORK item to remove\nthe hardcoded assumption by adding and using a submodule helper subcmd\n'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\nsucceed when the fetched commit(s) in the superproject trigger a\nsubmodule fetch, and that submodule's default remote name is not\n\"origin\".\n\nAdd non-\"origin\" remote tests to t5526-fetch-submodules.sh and\nt5572-pull-submodule.sh demonstrating this works as expected and add\ndedicated tests for get-default-remote.\n\nSigned-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\nReviewed-by: Jacob Keller <jacob.keller@gmail.com>\n---\nFixes for test_when_finished usage within a subshell.\n\nRange-diff against v4:\n1:  9c5a5df9a2 ! 1:  673ad0372a submodule: fetch missing objects from default remote\n    @@ t/meson.build: integration_tests = [\n     \n      ## t/t5526-fetch-submodules.sh ##\n     @@ t/t5526-fetch-submodules.sh: test_expect_success \"fetch new submodule commits on-demand outside standard refs\n    + \tgit commit -m \"updated submodules outside of refs/heads\" &&\n    + \tE=$(git rev-parse HEAD) &&\n      \tgit update-ref refs/changes/3 $E &&\n    ++\tFETCH_TRACE=\"$(pwd)/trace.out\" &&\n    ++\ttest_when_finished \"rm -f \\\"$FETCH_TRACE\\\"\" &&\n      \t(\n      \t\tcd downstream &&\n     -\t\tgit fetch --recurse-submodules origin refs/changes/3:refs/heads/my_branch &&\n    @@ t/t5526-fetch-submodules.sh: test_expect_success \"fetch new submodule commits on\n     +\t\t * branch            $C -> FETCH_HEAD\n     +\t\tFetching submodule submodule/subdir/deepsubmodule\n     +\t\tEOF\n    -+\t\ttest_when_finished \"rm -f $pwd/on-demand_submodule_fetch_trace\" &&\n    -+\t\tGIT_TRACE=\"$pwd/on-demand_submodule_fetch_trace\" \\\n    -+\t\tgit fetch --recurse-submodules origin refs/changes/3:refs/heads/my_branch 2>actual_fetch &&\n    ++\t\tGIT_TRACE=\"$FETCH_TRACE\" git fetch --recurse-submodules origin \\\n    ++\t\t\trefs/changes/3:refs/heads/my_branch 2>actual_fetch &&\n     +\t\ttest_cmp expect_fetch actual_fetch &&\n      \t\tgit -C submodule cat-file -t $C &&\n      \t\tgit -C sub1 cat-file -t $D &&\n     +\t\ttest_grep \"trace: built-in: git submodule--helper get-default-remote sub1\" \\\n    -+\t\t\t\"$pwd/on-demand_submodule_fetch_trace\" &&\n    ++\t\t\t\"$FETCH_TRACE\" &&\n     +\t\ttest_grep \"trace: built-in: git fetch .* --submodule-prefix=sub1/ origin\" \\\n    -+\t\t\t\"$pwd/on-demand_submodule_fetch_trace\" &&\n    ++\t\t\t\"$FETCH_TRACE\" &&\n      \t\tgit checkout --recurse-submodules FETCH_HEAD\n      \t)\n      '\n    @@ t/t5526-fetch-submodules.sh: test_expect_success 'fetch new submodule commit int\n     +\t\t-m \"updated submodules outside of refs/heads for custom remote\" &&\n     +\tE=$(git rev-parse HEAD) &&\n     +\tgit update-ref refs/changes/custom3 $E &&\n    ++\tFETCH_TRACE=\"$(pwd)/trace.out\" &&\n    ++\ttest_when_finished \"rm -f \\\"$FETCH_TRACE\\\"\" &&\n     +\t(\n     +\t\tcd downstream &&\n     +\t\tDEEP_START=$(git -C submodule/subdir/deepsubmodule rev-parse --short \\\n    @@ t/t5526-fetch-submodules.sh: test_expect_success 'fetch new submodule commit int\n     +\t\t * branch            $C -> FETCH_HEAD\n     +\t\tFetching submodule submodule/subdir/deepsubmodule\n     +\t\tEOF\n    -+\t\ttest_when_finished \"rm -f $pwd/custom_on-demand_submodule_fetch_trace\" &&\n    -+\t\tGIT_TRACE=\"$pwd/custom_on-demand_submodule_fetch_trace\" \\\n    -+\t\tgit fetch --recurse-submodules origin \\\n    ++\t\tGIT_TRACE=\"$FETCH_TRACE\" git fetch --recurse-submodules origin \\\n     +\t\t\trefs/changes/custom3:refs/heads/my_other_branch \\\n     +\t\t\t2>actual_fetch_custom &&\n     +\t\t# the without .gitmodules test above causes warnings\n    @@ t/t5526-fetch-submodules.sh: test_expect_success 'fetch new submodule commit int\n     +\t\tgit -C submodule cat-file -t $C &&\n     +\t\tgit -C sub1 cat-file -t $D &&\n     +\t\ttest_grep \"trace: built-in: git submodule--helper get-default-remote sub1\" \\\n    -+\t\t\t\"$pwd/custom_on-demand_submodule_fetch_trace\" &&\n    ++\t\t\t\"$FETCH_TRACE\" &&\n     +\t\ttest_grep \"trace: built-in: git fetch .* --submodule-prefix=sub1/ custom_remote $D\" \\\n    -+\t\t\t\"$pwd/custom_on-demand_submodule_fetch_trace\" &&\n    ++\t\t\t\"$FETCH_TRACE\" &&\n     +\t\tgit checkout --recurse-submodules FETCH_HEAD\n     +\t)\n     +'\n\n builtin/submodule--helper.c             |  38 +++++\n submodule.c                             |  17 ++-\n t/meson.build                           |   1 +\n t/t5526-fetch-submodules.sh             | 112 +++++++++++++-\n t/t5572-pull-submodule.sh               |  21 ++-\n t/t7426-submodule-get-default-remote.sh | 186 ++++++++++++++++++++++++\n 6 files changed, 371 insertions(+), 4 deletions(-)\n create mode 100755 t/t7426-submodule-get-default-remote.sh\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex b621d14275..0a4676f3ba 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -113,6 +113,43 @@ static int get_default_remote_submodule(const char *module_path, char **default_\n \treturn 0;\n }\n \n+static int module_get_default_remote(int argc, const char **argv, const char *prefix,\n+\t\t\t\t     struct repository *repo UNUSED)\n+{\n+\tconst char *path;\n+\tchar *resolved_path = NULL;\n+\tchar *default_remote = NULL;\n+\tint code;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper get-default-remote <path>\"),\n+\t\tNULL\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\tif (argc != 1)\n+\t\tusage_with_options(usage, options);\n+\n+\tpath = argv[0];\n+\tif (prefix && *prefix && !is_absolute_path(path)) {\n+\t\tresolved_path = xstrfmt(\"%s%s\", prefix, path);\n+\t\tpath = resolved_path;\n+\t}\n+\n+\tcode = get_default_remote_submodule(path, &default_remote);\n+\tif (code) {\n+\t\tfree(resolved_path);\n+\t\treturn code;\n+\t}\n+\n+\tprintf(\"%s\\n\", default_remote);\n+\tfree(default_remote);\n+\tfree(resolved_path);\n+\treturn 0;\n+}\n+\n /* the result should be freed by the caller. */\n static char *get_submodule_displaypath(const char *path, const char *prefix,\n \t\t\t\t       const char *super_prefix)\n@@ -3788,6 +3825,7 @@ int cmd_submodule__helper(int argc,\n \t\tOPT_SUBCOMMAND(\"set-url\", &fn, module_set_url),\n \t\tOPT_SUBCOMMAND(\"set-branch\", &fn, module_set_branch),\n \t\tOPT_SUBCOMMAND(\"create-branch\", &fn, module_create_branch),\n+\t\tOPT_SUBCOMMAND(\"get-default-remote\", &fn, module_get_default_remote),\n \t\tOPT_END()\n \t};\n \targc = parse_options(argc, argv, prefix, options, usage, 0);\ndiff --git a/submodule.c b/submodule.c\nindex 508938e4da..906febfa0e 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1708,6 +1708,8 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \tif (spf->oid_fetch_tasks_nr) {\n \t\tstruct fetch_task *task =\n \t\t\tspf->oid_fetch_tasks[spf->oid_fetch_tasks_nr - 1];\n+\t\tstruct child_process cp_remote = CHILD_PROCESS_INIT;\n+\t\tstruct strbuf remote_name = STRBUF_INIT;\n \t\tspf->oid_fetch_tasks_nr--;\n \n \t\tchild_process_init(cp);\n@@ -1721,8 +1723,19 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \t\tstrvec_pushf(&cp->args, \"--submodule-prefix=%s%s/\",\n \t\t\t     spf->prefix, task->sub->path);\n \n-\t\t/* NEEDSWORK: have get_default_remote from submodule--helper */\n-\t\tstrvec_push(&cp->args, \"origin\");\n+\t\tcp_remote.git_cmd = 1;\n+\t\tstrvec_pushl(&cp_remote.args, \"submodule--helper\",\n+\t\t\t     \"get-default-remote\", task->sub->path, NULL);\n+\n+\t\tif (!capture_command(&cp_remote, &remote_name, 0)) {\n+\t\t\tstrbuf_trim_trailing_newline(&remote_name);\n+\t\t\tstrvec_push(&cp->args, remote_name.buf);\n+\t\t} else {\n+\t\t\t/* Fallback to \"origin\" if the helper fails */\n+\t\t\tstrvec_push(&cp->args, \"origin\");\n+\t\t}\n+\t\tstrbuf_release(&remote_name);\n+\n \t\toid_array_for_each_unique(task->commits,\n \t\t\t\t\t  append_oid_to_argv, &cp->args);\n \ndiff --git a/t/meson.build b/t/meson.build\nindex 6d91470ebc..cfa3de5962 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -894,6 +894,7 @@ integration_tests = [\n   't7423-submodule-symlinks.sh',\n   't7424-submodule-mixed-ref-formats.sh',\n   't7425-submodule-gitdir-path-extension.sh',\n+  't7426-submodule-get-default-remote.sh',\n   't7450-bad-git-dotfiles.sh',\n   't7500-commit-template-squash-signoff.sh',\n   't7501-commit-basic-functionality.sh',\ndiff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh\nindex 5e566205ba..cc8aec5b1c 100755\n--- a/t/t5526-fetch-submodules.sh\n+++ b/t/t5526-fetch-submodules.sh\n@@ -834,11 +834,37 @@ test_expect_success \"fetch new submodule commits on-demand outside standard refs\n \tgit commit -m \"updated submodules outside of refs/heads\" &&\n \tE=$(git rev-parse HEAD) &&\n \tgit update-ref refs/changes/3 $E &&\n+\tFETCH_TRACE=\"$(pwd)/trace.out\" &&\n+\ttest_when_finished \"rm -f \\\"$FETCH_TRACE\\\"\" &&\n \t(\n \t\tcd downstream &&\n-\t\tgit fetch --recurse-submodules origin refs/changes/3:refs/heads/my_branch &&\n+\t\tDEEP_START=$(git -C submodule/subdir/deepsubmodule rev-parse --short origin/deep) &&\n+\t\tDEEP_END=$(git -C \"$pwd/deepsubmodule\" rev-parse --short deep) &&\n+\t\tcat >\"expect_fetch\" <<-EOF &&\n+\t\tFrom $pwd/.\n+\t\t * [new ref]         refs/changes/3 -> my_branch\n+\t\tFetching submodule sub1\n+\t\tFetching submodule sub1/subdir/deepsubmodule\n+\t\tFetching submodule submodule\n+\t\tFetching submodule submodule/subdir/deepsubmodule\n+\t\tFrom $pwd/deepsubmodule\n+\t\t   $DEEP_START..$DEEP_END  deep       -> origin/deep\n+\t\tFrom $pwd/./sub1\n+\t\t * branch            $D -> FETCH_HEAD\n+\t\tFetching submodule sub1/subdir/deepsubmodule\n+\t\tFrom $pwd/submodule\n+\t\t * branch            $C -> FETCH_HEAD\n+\t\tFetching submodule submodule/subdir/deepsubmodule\n+\t\tEOF\n+\t\tGIT_TRACE=\"$FETCH_TRACE\" git fetch --recurse-submodules origin \\\n+\t\t\trefs/changes/3:refs/heads/my_branch 2>actual_fetch &&\n+\t\ttest_cmp expect_fetch actual_fetch &&\n \t\tgit -C submodule cat-file -t $C &&\n \t\tgit -C sub1 cat-file -t $D &&\n+\t\ttest_grep \"trace: built-in: git submodule--helper get-default-remote sub1\" \\\n+\t\t\t\"$FETCH_TRACE\" &&\n+\t\ttest_grep \"trace: built-in: git fetch .* --submodule-prefix=sub1/ origin\" \\\n+\t\t\t\"$FETCH_TRACE\" &&\n \t\tgit checkout --recurse-submodules FETCH_HEAD\n \t)\n '\n@@ -929,6 +955,90 @@ test_expect_success 'fetch new submodule commit intermittently referenced by sup\n \t)\n '\n \n+test_expect_success 'fetch new submodule commits on-demand outside standard refspec with custom remote name' '\n+\t# depends on the previous test for setup\n+\n+\t# Rename the remote in sub1 from \"origin\" to \"custom_remote\"\n+\tgit -C downstream/sub1 remote rename origin custom_remote &&\n+\n+\t# Create new commits in the original submodules\n+\tC=$(git -C submodule commit-tree \\\n+\t\t-m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom1 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree \\\n+\t\t-m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom2 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit \\\n+\t\t-m \"updated submodules outside of refs/heads for custom remote\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom3 $E &&\n+\tFETCH_TRACE=\"$(pwd)/trace.out\" &&\n+\ttest_when_finished \"rm -f \\\"$FETCH_TRACE\\\"\" &&\n+\t(\n+\t\tcd downstream &&\n+\t\tDEEP_START=$(git -C submodule/subdir/deepsubmodule rev-parse --short \\\n+\t\t\torigin/deep) &&\n+\t\tDEEP_END=$(git -C \"$pwd/deepsubmodule\" rev-parse --short deep) &&\n+\t\tcat >\"expect_fetch_custom\" <<-EOF &&\n+\t\tFrom $pwd/.\n+\t\t * [new ref]         refs/changes/custom3 -> my_other_branch\n+\t\tFetching submodule sub1\n+\t\tFetching submodule sub1/subdir/deepsubmodule\n+\t\tFetching submodule submodule\n+\t\tFetching submodule submodule/subdir/deepsubmodule\n+\t\tFrom $pwd/./sub1\n+\t\t * branch            $D -> FETCH_HEAD\n+\t\tFetching submodule sub1/subdir/deepsubmodule\n+\t\tFrom $pwd/submodule\n+\t\t * branch            $C -> FETCH_HEAD\n+\t\tFetching submodule submodule/subdir/deepsubmodule\n+\t\tEOF\n+\t\tGIT_TRACE=\"$FETCH_TRACE\" git fetch --recurse-submodules origin \\\n+\t\t\trefs/changes/custom3:refs/heads/my_other_branch \\\n+\t\t\t2>actual_fetch_custom &&\n+\t\t# the without .gitmodules test above causes warnings\n+\t\tgrep -v \"^warning: \" actual_fetch_custom >actual_fetch_warnings_removed &&\n+\t\ttest_cmp expect_fetch_custom actual_fetch_warnings_removed &&\n+\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\ttest_grep \"trace: built-in: git submodule--helper get-default-remote sub1\" \\\n+\t\t\t\"$FETCH_TRACE\" &&\n+\t\ttest_grep \"trace: built-in: git fetch .* --submodule-prefix=sub1/ custom_remote $D\" \\\n+\t\t\t\"$FETCH_TRACE\" &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n+test_expect_success 'fetch new submodule commit on-demand in FETCH_HEAD from custom remote' '\n+\t# depends on the previous test for setup\n+\n+\tC=$(git -C submodule commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom4 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom5 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit -m \"updated submodules outside of refs/heads\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom6 $E &&\n+\t(\n+\t\tcd downstream &&\n+\t\tgit fetch --recurse-submodules origin refs/changes/custom6 &&\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n add_commit_push () {\n \tdir=\"$1\" &&\n \tmsg=\"$2\" &&\ndiff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\nindex 45f384dd32..42d14328b6 100755\n--- a/t/t5572-pull-submodule.sh\n+++ b/t/t5572-pull-submodule.sh\n@@ -257,7 +257,26 @@ test_expect_success 'fetch submodule remote of different name from superproject'\n \tgit -C a-submodule reset --hard HEAD^^ &&\n \n \tgit -C child pull --no-recurse-submodules &&\n-\tgit -C child submodule update\n+\tgit -C child submodule update &&\n+\ttest_path_is_file child/a-submodule/moreecho.t\n+'\n+\n+test_expect_success 'fetch non-origin submodule remote named different from superproject' '\n+\tgit -C child/a-submodule remote rename origin o2 &&\n+\n+\t# Create commit that is unreachable from current master branch\n+\t# newmain is already reset in the previous test\n+\ttest_commit -C a-submodule echo_o2 &&\n+\ttest_commit -C a-submodule moreecho_o2 &&\n+\tsubc=$(git -C a-submodule rev-parse --short HEAD) &&\n+\n+\tgit -C parent/a-submodule fetch &&\n+\tgit -C parent/a-submodule checkout \"$subc\" &&\n+\tgit -C parent commit -m \"update submodule o2\" a-submodule &&\n+\tgit -C a-submodule reset --hard HEAD^^ &&\n+\n+\tgit -C child pull --recurse-submodules &&\n+\ttest_path_is_file child/a-submodule/moreecho_o2.t\n '\n \n test_done\ndiff --git a/t/t7426-submodule-get-default-remote.sh b/t/t7426-submodule-get-default-remote.sh\nnew file mode 100755\nindex 0000000000..b842af9a2d\n--- /dev/null\n+++ b/t/t7426-submodule-get-default-remote.sh\n@@ -0,0 +1,186 @@\n+#!/bin/sh\n+\n+test_description='git submodule--helper get-default-remote'\n+\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tgit config --global protocol.file.allow always\n+'\n+\n+test_expect_success 'setup repositories' '\n+\t# Create a repository to be used as submodule\n+\tgit init sub &&\n+\ttest_commit --no-tag -C sub \"initial commit in sub\" file.txt \"sub content\" &&\n+\n+\t# Create main repository\n+\tgit init super &&\n+\t(\n+\t\tcd super &&\n+\t\tmkdir subdir &&\n+\t\ttest_commit --no-tag -C subdir \"initial commit in super\" main.txt \"super content\" &&\n+\t\tgit submodule add ../sub subpath &&\n+\t\tgit commit -m \"add submodule 'sub' at subpath\"\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns origin for initialized submodule' '\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule update --init &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works from subdirectory' '\n+\t(\n+\t\tcd super/subdir &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote ../subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-existent path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote nonexistent 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-submodule path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subdir 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails without path argument' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with too many arguments' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subpath subdir 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'setup submodule with non-origin default remote name' '\n+\t# Create another submodule path with a different remote name\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ../sub upstream-subpath &&\n+\t\tgit commit -m \"add second submodule in upstream-subpath\" &&\n+\t\tgit submodule update --init upstream-subpath &&\n+\n+\t\t# Change the remote name in the submodule\n+\t\tcd upstream-subpath &&\n+\t\tgit remote rename origin upstream\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns non-origin remote name' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes' '\n+\t(\n+\t\tcd super/subpath &&\n+\t\tgit remote add other-upstream ../../sub &&\n+\t\tgit remote add myfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes and none are origin' '\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit remote add yet-another-upstream ../../sub &&\n+\t\tgit remote add yourfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'setup nested submodule with non-origin remote' '\n+\tgit init innersub &&\n+\ttest_commit --no-tag -C innersub \"initial commit in innersub\" inner.txt \"innersub content\" &&\n+\n+\t(\n+\t\tcd sub &&\n+\t\tgit submodule add ../innersub innersubpath &&\n+\t\tgit commit -m \"add nested submodule at innersubpath\"\n+\t) &&\n+\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit pull upstream &&\n+\t\tgit submodule update --init --recursive . &&\n+\t\t(\n+\t\t\tcd innersubpath &&\n+\t\t\tgit remote rename origin another_upstream\n+\t\t)\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with nested submodule' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"another_upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath/innersubpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with submodule that has no remotes' '\n+\t# Create a submodule directory manually without remotes\n+\t(\n+\t\tcd super &&\n+\t\tgit init no-remote-sub &&\n+\t\ttest_commit --no-tag -C no-remote-sub \"local commit\" local.txt \"local content\"\n+\t) &&\n+\n+\t# Add it as a submodule\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ./no-remote-sub &&\n+\t\tgit commit -m \"add local submodule 'no-remote-sub'\"\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\t# Should fall back to \"origin\" remote name when no remotes exist\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote no-remote-sub >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_done\n-- \n2.52.0\n\n"},{"id":"537735","messageId":"2e62dc94-b821-4815-8dd2-f806580d2027@ramsayjones.plus.com","threadId":"64785","inReplyTo":"20260303200906.4118348-1-nasser.grainawi@oss.qualcomm.com","subject":"Re: [PATCH v5] submodule: fetch missing objects from default remote","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2026-03-03T20:47:02Z","receivedAt":"2026-03-03T20:50:12Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 03/03/2026 8:09 pm, Nasser Grainawi wrote:\n> When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\n> added support for fetching a missing submodule object by id, it\n> hardcoded the remote name as \"origin\" and deferred anything more\n> complicated for a later patch. Implement the NEEDSWORK item to remove\n> the hardcoded assumption by adding and using a submodule helper subcmd\n> 'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\n> succeed when the fetched commit(s) in the superproject trigger a\n> submodule fetch, and that submodule's default remote name is not\n> \"origin\".\n> \n> Add non-\"origin\" remote tests to t5526-fetch-submodules.sh and\n> t5572-pull-submodule.sh demonstrating this works as expected and add\n> dedicated tests for get-default-remote.\n> \n> Signed-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n> Reviewed-by: Jacob Keller <jacob.keller@gmail.com>\n> ---\n> Fixes for test_when_finished usage within a subshell.\n> \n\nThe 't5526-fetch-submodules.sh' test failed for me tonight. Having seen an\nearlier email about the test_when_finished failures I wasn't too surprised\nbut, once I looked at the failure, it was obviously not the cause of this\nfailure. Indeed, when I ran the test by hand, it passed ... :)\n\nIt seems the 'seen' branch (@764d09c9ce) has this v5 version of the patch\nand (in this test anyway) has some flakiness:\n\n  $ cd t\n  $ ./t5526-fetch-submodules.sh --stress >out 2>&1\n  $ cat out\n  FAIL  4.1\n  FAIL  3.1\n  FAIL  5.1\n  OK    0.1 \n  OK    1.1\n  OK    6.1 \n  OK    7.1\n  OK    2.1 \n  Log(s) of failed test run(s):\n  Contents of '/home/ramsay/git/t/test-results/t5526-fetch-submodules.stress-3.out':\n  Initialized empty Git repository in /home/ramsay/git/t/trash directory.t5526-fetch-submodules.stress-3/.git/\n  expecting success of 5526.1 'setup': \n\n  ...\n\n  expecting success of 5526.44 'fetch new submodule commits on-demand outside standard refspec with custom remote name':\n\n  ...\n\n  + diff -u expect_fetch_custom actual_fetch_warnings_removed\n  --- expect_fetch_custom 2026-03-03 20:35:13.949600802 +0000\n  +++ actual_fetch_warnings_removed       2026-03-03 20:35:14.150601532 +0000\n  @@ -4,9 +4,9 @@\n   Fetching submodule sub1/subdir/deepsubmodule\n   Fetching submodule submodule\n   Fetching submodule submodule/subdir/deepsubmodule\n  -From /home/ramsay/git/t/trash directory.t5526-fetch-submodules.stress-5/./sub1\n  - * branch            43c17d99ab9d4fcabf7107e36660b27113b54663 -> FETCH_HEAD\n  -Fetching submodule sub1/subdir/deepsubmodule\n   From /home/ramsay/git/t/trash directory.t5526-fetch-submodules.stress-5/submodule\n    * branch            e38933e027ee8a2000f603124aa899302a09a51f -> FETCH_HEAD\n   Fetching submodule submodule/subdir/deepsubmodule\n  +From /home/ramsay/git/t/trash directory.t5526-fetch-submodules.stress-5/./sub1\n  + * branch            43c17d99ab9d4fcabf7107e36660b27113b54663 -> FETCH_HEAD\n  +Fetching submodule sub1/subdir/deepsubmodule\n  error: last command exited with $?=1\n  not ok 44 - fetch new submodule commits on-demand outside standard refspec with custom remote name\n\n  ...\n\n  $ \n\nFrom which I guess that the order of the output is somewhat unpredictable.\n\nAlso, other test files in that patch didn't fail for me with 'make test', but it\ncould be possible that they are also flaky. I didn't look.\n\nThanks.\n\nATB,\nRamsay Jones\n\n\n\n"},{"id":"537737","messageId":"xmqqms0obo5s.fsf@gitster.g","threadId":"64785","inReplyTo":"2e62dc94-b821-4815-8dd2-f806580d2027@ramsayjones.plus.com","subject":"Re: [PATCH v5] submodule: fetch missing objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-03T21:26:07Z","receivedAt":"2026-03-03T21:26:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ramsay Jones <ramsay@ramsayjones.plus.com> writes:\n\n>   + diff -u expect_fetch_custom actual_fetch_warnings_removed\n>   --- expect_fetch_custom 2026-03-03 20:35:13.949600802 +0000\n>   +++ actual_fetch_warnings_removed       2026-03-03 20:35:14.150601532 +0000\n>   @@ -4,9 +4,9 @@\n>    Fetching submodule sub1/subdir/deepsubmodule\n>    Fetching submodule submodule\n>    Fetching submodule submodule/subdir/deepsubmodule\n>   -From /home/ramsay/git/t/trash directory.t5526-fetch-submodules.stress-5/./sub1\n>   - * branch            43c17d99ab9d4fcabf7107e36660b27113b54663 -> FETCH_HEAD\n>   -Fetching submodule sub1/subdir/deepsubmodule\n>    From /home/ramsay/git/t/trash directory.t5526-fetch-submodules.stress-5/submodule\n>     * branch            e38933e027ee8a2000f603124aa899302a09a51f -> FETCH_HEAD\n>    Fetching submodule submodule/subdir/deepsubmodule\n>   +From /home/ramsay/git/t/trash directory.t5526-fetch-submodules.stress-5/./sub1\n>   + * branch            43c17d99ab9d4fcabf7107e36660b27113b54663 -> FETCH_HEAD\n>   +Fetching submodule sub1/subdir/deepsubmodule\n>   error: last command exited with $?=1\n>   not ok 44 - fetch new submodule commits on-demand outside standard refspec with custom remote name\n>\n>   ...\n>\n>   $ \n>\n> From which I guess that the order of the output is somewhat unpredictable.\n>\n> Also, other test files in that patch didn't fail for me with 'make test', but it\n> could be possible that they are also flaky. I didn't look.\n\nAh, looks like the command tries to fetch from multiple places in\nparallel and it is up to the luck which one reports its result\nfirst?  We probably do not want such a \"human readable progress\noutput should look exactly like this\" test.\n\nThanks for reporting.\n"},{"id":"537739","messageId":"CAFcKa=_Ovs8bZmx8K4VFYKgV-_sDTWMsaCmQCC8J0LAEGTg9vA@mail.gmail.com","threadId":"64785","inReplyTo":"xmqqms0obo5s.fsf@gitster.g","subject":"Re: [PATCH v5] submodule: fetch missing objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-03-03T23:29:13Z","receivedAt":"2026-03-03T23:29:25Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"On Tue, Mar 3, 2026 at 2:26 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Ramsay Jones <ramsay@ramsayjones.plus.com> writes:\n>\n> >   + diff -u expect_fetch_custom actual_fetch_warnings_removed\n> >   --- expect_fetch_custom 2026-03-03 20:35:13.949600802 +0000\n> >   +++ actual_fetch_warnings_removed       2026-03-03 20:35:14.150601532 +0000\n> >   @@ -4,9 +4,9 @@\n> >    Fetching submodule sub1/subdir/deepsubmodule\n> >    Fetching submodule submodule\n> >    Fetching submodule submodule/subdir/deepsubmodule\n> >   -From /home/ramsay/git/t/trash directory.t5526-fetch-submodules.stress-5/./sub1\n> >   - * branch            43c17d99ab9d4fcabf7107e36660b27113b54663 -> FETCH_HEAD\n> >   -Fetching submodule sub1/subdir/deepsubmodule\n> >    From /home/ramsay/git/t/trash directory.t5526-fetch-submodules.stress-5/submodule\n> >     * branch            e38933e027ee8a2000f603124aa899302a09a51f -> FETCH_HEAD\n> >    Fetching submodule submodule/subdir/deepsubmodule\n> >   +From /home/ramsay/git/t/trash directory.t5526-fetch-submodules.stress-5/./sub1\n> >   + * branch            43c17d99ab9d4fcabf7107e36660b27113b54663 -> FETCH_HEAD\n> >   +Fetching submodule sub1/subdir/deepsubmodule\n> >   error: last command exited with $?=1\n> >   not ok 44 - fetch new submodule commits on-demand outside standard refspec with custom remote name\n> >\n> >   ...\n> >\n> >   $\n> >\n> > From which I guess that the order of the output is somewhat unpredictable.\n> >\n> > Also, other test files in that patch didn't fail for me with 'make test', but it\n> > could be possible that they are also flaky. I didn't look.\n>\n> Ah, looks like the command tries to fetch from multiple places in\n> parallel and it is up to the luck which one reports its result\n> first?  We probably do not want such a \"human readable progress\n> output should look exactly like this\" test.\n>\n> Thanks for reporting.\n\nYes, thank you. I'll drop that part of the test and just keep the\nGIT_TRACE check.\n"},{"id":"537740","messageId":"20260303234044.105157-1-nasser.grainawi@oss.qualcomm.com","threadId":"64785","inReplyTo":"20260303200906.4118348-1-nasser.grainawi@oss.qualcomm.com","subject":"[PATCH v6] submodule: fetch missing objects from default remote","fromName":"Nasser Grainawi","fromEmail":"nasser.grainawi@oss.qualcomm.com","sentAt":"2026-03-03T23:40:44Z","receivedAt":"2026-03-03T23:40:58Z","isPatch":true,"sender":{"key":"nasser.grainawi@oss.qualcomm.com","avatar":"https://avatars.githubusercontent.com/u/757421?v=4"},"body":"When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\nadded support for fetching a missing submodule object by id, it\nhardcoded the remote name as \"origin\" and deferred anything more\ncomplicated for a later patch. Implement the NEEDSWORK item to remove\nthe hardcoded assumption by adding and using a submodule helper subcmd\n'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\nsucceed when the fetched commit(s) in the superproject trigger a\nsubmodule fetch, and that submodule's default remote name is not\n\"origin\".\n\nAdd non-\"origin\" remote tests to t5526-fetch-submodules.sh and\nt5572-pull-submodule.sh demonstrating this works as expected and add\ndedicated tests for get-default-remote.\n\nSigned-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\nReviewed-by: Jacob Keller <jacob.keller@gmail.com>\n---\nRemoved the fetch progress output comparison from the tests as the\nordering is unpredictable.\n\nRange-diff against v5:\n1:  673ad0372a ! 1:  ff3034f05f submodule: fetch missing objects from default remote\n    @@ t/t5526-fetch-submodules.sh: test_expect_success \"fetch new submodule commits on\n      \t(\n      \t\tcd downstream &&\n     -\t\tgit fetch --recurse-submodules origin refs/changes/3:refs/heads/my_branch &&\n    -+\t\tDEEP_START=$(git -C submodule/subdir/deepsubmodule rev-parse --short origin/deep) &&\n    -+\t\tDEEP_END=$(git -C \"$pwd/deepsubmodule\" rev-parse --short deep) &&\n    -+\t\tcat >\"expect_fetch\" <<-EOF &&\n    -+\t\tFrom $pwd/.\n    -+\t\t * [new ref]         refs/changes/3 -> my_branch\n    -+\t\tFetching submodule sub1\n    -+\t\tFetching submodule sub1/subdir/deepsubmodule\n    -+\t\tFetching submodule submodule\n    -+\t\tFetching submodule submodule/subdir/deepsubmodule\n    -+\t\tFrom $pwd/deepsubmodule\n    -+\t\t   $DEEP_START..$DEEP_END  deep       -> origin/deep\n    -+\t\tFrom $pwd/./sub1\n    -+\t\t * branch            $D -> FETCH_HEAD\n    -+\t\tFetching submodule sub1/subdir/deepsubmodule\n    -+\t\tFrom $pwd/submodule\n    -+\t\t * branch            $C -> FETCH_HEAD\n    -+\t\tFetching submodule submodule/subdir/deepsubmodule\n    -+\t\tEOF\n     +\t\tGIT_TRACE=\"$FETCH_TRACE\" git fetch --recurse-submodules origin \\\n    -+\t\t\trefs/changes/3:refs/heads/my_branch 2>actual_fetch &&\n    -+\t\ttest_cmp expect_fetch actual_fetch &&\n    ++\t\t\trefs/changes/3:refs/heads/my_branch &&\n      \t\tgit -C submodule cat-file -t $C &&\n      \t\tgit -C sub1 cat-file -t $D &&\n     +\t\ttest_grep \"trace: built-in: git submodule--helper get-default-remote sub1\" \\\n    @@ t/t5526-fetch-submodules.sh: test_expect_success 'fetch new submodule commit int\n     +\ttest_when_finished \"rm -f \\\"$FETCH_TRACE\\\"\" &&\n     +\t(\n     +\t\tcd downstream &&\n    -+\t\tDEEP_START=$(git -C submodule/subdir/deepsubmodule rev-parse --short \\\n    -+\t\t\torigin/deep) &&\n    -+\t\tDEEP_END=$(git -C \"$pwd/deepsubmodule\" rev-parse --short deep) &&\n    -+\t\tcat >\"expect_fetch_custom\" <<-EOF &&\n    -+\t\tFrom $pwd/.\n    -+\t\t * [new ref]         refs/changes/custom3 -> my_other_branch\n    -+\t\tFetching submodule sub1\n    -+\t\tFetching submodule sub1/subdir/deepsubmodule\n    -+\t\tFetching submodule submodule\n    -+\t\tFetching submodule submodule/subdir/deepsubmodule\n    -+\t\tFrom $pwd/./sub1\n    -+\t\t * branch            $D -> FETCH_HEAD\n    -+\t\tFetching submodule sub1/subdir/deepsubmodule\n    -+\t\tFrom $pwd/submodule\n    -+\t\t * branch            $C -> FETCH_HEAD\n    -+\t\tFetching submodule submodule/subdir/deepsubmodule\n    -+\t\tEOF\n     +\t\tGIT_TRACE=\"$FETCH_TRACE\" git fetch --recurse-submodules origin \\\n    -+\t\t\trefs/changes/custom3:refs/heads/my_other_branch \\\n    -+\t\t\t2>actual_fetch_custom &&\n    -+\t\t# the without .gitmodules test above causes warnings\n    -+\t\tgrep -v \"^warning: \" actual_fetch_custom >actual_fetch_warnings_removed &&\n    -+\t\ttest_cmp expect_fetch_custom actual_fetch_warnings_removed &&\n    -+\n    ++\t\t\trefs/changes/custom3:refs/heads/my_other_branch &&\n     +\t\tgit -C submodule cat-file -t $C &&\n     +\t\tgit -C sub1 cat-file -t $D &&\n     +\t\ttest_grep \"trace: built-in: git submodule--helper get-default-remote sub1\" \\\n\n builtin/submodule--helper.c             |  38 +++++\n submodule.c                             |  17 ++-\n t/meson.build                           |   1 +\n t/t5526-fetch-submodules.sh             |  71 ++++++++-\n t/t5572-pull-submodule.sh               |  21 ++-\n t/t7426-submodule-get-default-remote.sh | 186 ++++++++++++++++++++++++\n 6 files changed, 330 insertions(+), 4 deletions(-)\n create mode 100755 t/t7426-submodule-get-default-remote.sh\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex b621d14275..0a4676f3ba 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -113,6 +113,43 @@ static int get_default_remote_submodule(const char *module_path, char **default_\n \treturn 0;\n }\n \n+static int module_get_default_remote(int argc, const char **argv, const char *prefix,\n+\t\t\t\t     struct repository *repo UNUSED)\n+{\n+\tconst char *path;\n+\tchar *resolved_path = NULL;\n+\tchar *default_remote = NULL;\n+\tint code;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper get-default-remote <path>\"),\n+\t\tNULL\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\tif (argc != 1)\n+\t\tusage_with_options(usage, options);\n+\n+\tpath = argv[0];\n+\tif (prefix && *prefix && !is_absolute_path(path)) {\n+\t\tresolved_path = xstrfmt(\"%s%s\", prefix, path);\n+\t\tpath = resolved_path;\n+\t}\n+\n+\tcode = get_default_remote_submodule(path, &default_remote);\n+\tif (code) {\n+\t\tfree(resolved_path);\n+\t\treturn code;\n+\t}\n+\n+\tprintf(\"%s\\n\", default_remote);\n+\tfree(default_remote);\n+\tfree(resolved_path);\n+\treturn 0;\n+}\n+\n /* the result should be freed by the caller. */\n static char *get_submodule_displaypath(const char *path, const char *prefix,\n \t\t\t\t       const char *super_prefix)\n@@ -3788,6 +3825,7 @@ int cmd_submodule__helper(int argc,\n \t\tOPT_SUBCOMMAND(\"set-url\", &fn, module_set_url),\n \t\tOPT_SUBCOMMAND(\"set-branch\", &fn, module_set_branch),\n \t\tOPT_SUBCOMMAND(\"create-branch\", &fn, module_create_branch),\n+\t\tOPT_SUBCOMMAND(\"get-default-remote\", &fn, module_get_default_remote),\n \t\tOPT_END()\n \t};\n \targc = parse_options(argc, argv, prefix, options, usage, 0);\ndiff --git a/submodule.c b/submodule.c\nindex 508938e4da..906febfa0e 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1708,6 +1708,8 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \tif (spf->oid_fetch_tasks_nr) {\n \t\tstruct fetch_task *task =\n \t\t\tspf->oid_fetch_tasks[spf->oid_fetch_tasks_nr - 1];\n+\t\tstruct child_process cp_remote = CHILD_PROCESS_INIT;\n+\t\tstruct strbuf remote_name = STRBUF_INIT;\n \t\tspf->oid_fetch_tasks_nr--;\n \n \t\tchild_process_init(cp);\n@@ -1721,8 +1723,19 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err,\n \t\tstrvec_pushf(&cp->args, \"--submodule-prefix=%s%s/\",\n \t\t\t     spf->prefix, task->sub->path);\n \n-\t\t/* NEEDSWORK: have get_default_remote from submodule--helper */\n-\t\tstrvec_push(&cp->args, \"origin\");\n+\t\tcp_remote.git_cmd = 1;\n+\t\tstrvec_pushl(&cp_remote.args, \"submodule--helper\",\n+\t\t\t     \"get-default-remote\", task->sub->path, NULL);\n+\n+\t\tif (!capture_command(&cp_remote, &remote_name, 0)) {\n+\t\t\tstrbuf_trim_trailing_newline(&remote_name);\n+\t\t\tstrvec_push(&cp->args, remote_name.buf);\n+\t\t} else {\n+\t\t\t/* Fallback to \"origin\" if the helper fails */\n+\t\t\tstrvec_push(&cp->args, \"origin\");\n+\t\t}\n+\t\tstrbuf_release(&remote_name);\n+\n \t\toid_array_for_each_unique(task->commits,\n \t\t\t\t\t  append_oid_to_argv, &cp->args);\n \ndiff --git a/t/meson.build b/t/meson.build\nindex 6d91470ebc..cfa3de5962 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -894,6 +894,7 @@ integration_tests = [\n   't7423-submodule-symlinks.sh',\n   't7424-submodule-mixed-ref-formats.sh',\n   't7425-submodule-gitdir-path-extension.sh',\n+  't7426-submodule-get-default-remote.sh',\n   't7450-bad-git-dotfiles.sh',\n   't7500-commit-template-squash-signoff.sh',\n   't7501-commit-basic-functionality.sh',\ndiff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh\nindex 5e566205ba..1242ee9185 100755\n--- a/t/t5526-fetch-submodules.sh\n+++ b/t/t5526-fetch-submodules.sh\n@@ -834,11 +834,18 @@ test_expect_success \"fetch new submodule commits on-demand outside standard refs\n \tgit commit -m \"updated submodules outside of refs/heads\" &&\n \tE=$(git rev-parse HEAD) &&\n \tgit update-ref refs/changes/3 $E &&\n+\tFETCH_TRACE=\"$(pwd)/trace.out\" &&\n+\ttest_when_finished \"rm -f \\\"$FETCH_TRACE\\\"\" &&\n \t(\n \t\tcd downstream &&\n-\t\tgit fetch --recurse-submodules origin refs/changes/3:refs/heads/my_branch &&\n+\t\tGIT_TRACE=\"$FETCH_TRACE\" git fetch --recurse-submodules origin \\\n+\t\t\trefs/changes/3:refs/heads/my_branch &&\n \t\tgit -C submodule cat-file -t $C &&\n \t\tgit -C sub1 cat-file -t $D &&\n+\t\ttest_grep \"trace: built-in: git submodule--helper get-default-remote sub1\" \\\n+\t\t\t\"$FETCH_TRACE\" &&\n+\t\ttest_grep \"trace: built-in: git fetch .* --submodule-prefix=sub1/ origin\" \\\n+\t\t\t\"$FETCH_TRACE\" &&\n \t\tgit checkout --recurse-submodules FETCH_HEAD\n \t)\n '\n@@ -929,6 +936,68 @@ test_expect_success 'fetch new submodule commit intermittently referenced by sup\n \t)\n '\n \n+test_expect_success 'fetch new submodule commits on-demand outside standard refspec with custom remote name' '\n+\t# depends on the previous test for setup\n+\n+\t# Rename the remote in sub1 from \"origin\" to \"custom_remote\"\n+\tgit -C downstream/sub1 remote rename origin custom_remote &&\n+\n+\t# Create new commits in the original submodules\n+\tC=$(git -C submodule commit-tree \\\n+\t\t-m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom1 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree \\\n+\t\t-m \"change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom2 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit \\\n+\t\t-m \"updated submodules outside of refs/heads for custom remote\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom3 $E &&\n+\tFETCH_TRACE=\"$(pwd)/trace.out\" &&\n+\ttest_when_finished \"rm -f \\\"$FETCH_TRACE\\\"\" &&\n+\t(\n+\t\tcd downstream &&\n+\t\tGIT_TRACE=\"$FETCH_TRACE\" git fetch --recurse-submodules origin \\\n+\t\t\trefs/changes/custom3:refs/heads/my_other_branch &&\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\ttest_grep \"trace: built-in: git submodule--helper get-default-remote sub1\" \\\n+\t\t\t\"$FETCH_TRACE\" &&\n+\t\ttest_grep \"trace: built-in: git fetch .* --submodule-prefix=sub1/ custom_remote $D\" \\\n+\t\t\t\"$FETCH_TRACE\" &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n+test_expect_success 'fetch new submodule commit on-demand in FETCH_HEAD from custom remote' '\n+\t# depends on the previous test for setup\n+\n+\tC=$(git -C submodule commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C submodule update-ref refs/changes/custom4 $C &&\n+\tgit update-index --cacheinfo 160000 $C submodule &&\n+\ttest_tick &&\n+\n+\tD=$(git -C sub1 commit-tree -m \"another change outside refs/heads for custom remote\" HEAD^{tree}) &&\n+\tgit -C sub1 update-ref refs/changes/custom5 $D &&\n+\tgit update-index --cacheinfo 160000 $D sub1 &&\n+\n+\tgit commit -m \"updated submodules outside of refs/heads\" &&\n+\tE=$(git rev-parse HEAD) &&\n+\tgit update-ref refs/changes/custom6 $E &&\n+\t(\n+\t\tcd downstream &&\n+\t\tgit fetch --recurse-submodules origin refs/changes/custom6 &&\n+\t\tgit -C submodule cat-file -t $C &&\n+\t\tgit -C sub1 cat-file -t $D &&\n+\t\tgit checkout --recurse-submodules FETCH_HEAD\n+\t)\n+'\n+\n add_commit_push () {\n \tdir=\"$1\" &&\n \tmsg=\"$2\" &&\ndiff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\nindex 45f384dd32..42d14328b6 100755\n--- a/t/t5572-pull-submodule.sh\n+++ b/t/t5572-pull-submodule.sh\n@@ -257,7 +257,26 @@ test_expect_success 'fetch submodule remote of different name from superproject'\n \tgit -C a-submodule reset --hard HEAD^^ &&\n \n \tgit -C child pull --no-recurse-submodules &&\n-\tgit -C child submodule update\n+\tgit -C child submodule update &&\n+\ttest_path_is_file child/a-submodule/moreecho.t\n+'\n+\n+test_expect_success 'fetch non-origin submodule remote named different from superproject' '\n+\tgit -C child/a-submodule remote rename origin o2 &&\n+\n+\t# Create commit that is unreachable from current master branch\n+\t# newmain is already reset in the previous test\n+\ttest_commit -C a-submodule echo_o2 &&\n+\ttest_commit -C a-submodule moreecho_o2 &&\n+\tsubc=$(git -C a-submodule rev-parse --short HEAD) &&\n+\n+\tgit -C parent/a-submodule fetch &&\n+\tgit -C parent/a-submodule checkout \"$subc\" &&\n+\tgit -C parent commit -m \"update submodule o2\" a-submodule &&\n+\tgit -C a-submodule reset --hard HEAD^^ &&\n+\n+\tgit -C child pull --recurse-submodules &&\n+\ttest_path_is_file child/a-submodule/moreecho_o2.t\n '\n \n test_done\ndiff --git a/t/t7426-submodule-get-default-remote.sh b/t/t7426-submodule-get-default-remote.sh\nnew file mode 100755\nindex 0000000000..b842af9a2d\n--- /dev/null\n+++ b/t/t7426-submodule-get-default-remote.sh\n@@ -0,0 +1,186 @@\n+#!/bin/sh\n+\n+test_description='git submodule--helper get-default-remote'\n+\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tgit config --global protocol.file.allow always\n+'\n+\n+test_expect_success 'setup repositories' '\n+\t# Create a repository to be used as submodule\n+\tgit init sub &&\n+\ttest_commit --no-tag -C sub \"initial commit in sub\" file.txt \"sub content\" &&\n+\n+\t# Create main repository\n+\tgit init super &&\n+\t(\n+\t\tcd super &&\n+\t\tmkdir subdir &&\n+\t\ttest_commit --no-tag -C subdir \"initial commit in super\" main.txt \"super content\" &&\n+\t\tgit submodule add ../sub subpath &&\n+\t\tgit commit -m \"add submodule 'sub' at subpath\"\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns origin for initialized submodule' '\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule update --init &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works from subdirectory' '\n+\t(\n+\t\tcd super/subdir &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote ../subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-existent path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote nonexistent 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with non-submodule path' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subdir 2>err &&\n+\t\ttest_grep \"could not get a repository handle\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails without path argument' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote fails with too many arguments' '\n+\t(\n+\t\tcd super &&\n+\t\ttest_must_fail git submodule--helper get-default-remote subpath subdir 2>err &&\n+\t\ttest_grep \"usage:\" err\n+\t)\n+'\n+\n+test_expect_success 'setup submodule with non-origin default remote name' '\n+\t# Create another submodule path with a different remote name\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ../sub upstream-subpath &&\n+\t\tgit commit -m \"add second submodule in upstream-subpath\" &&\n+\t\tgit submodule update --init upstream-subpath &&\n+\n+\t\t# Change the remote name in the submodule\n+\t\tcd upstream-subpath &&\n+\t\tgit remote rename origin upstream\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote returns non-origin remote name' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes' '\n+\t(\n+\t\tcd super/subpath &&\n+\t\tgit remote add other-upstream ../../sub &&\n+\t\tgit remote add myfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote handles submodule with multiple remotes and none are origin' '\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit remote add yet-another-upstream ../../sub &&\n+\t\tgit remote add yourfork ../../sub\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\techo \"upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'setup nested submodule with non-origin remote' '\n+\tgit init innersub &&\n+\ttest_commit --no-tag -C innersub \"initial commit in innersub\" inner.txt \"innersub content\" &&\n+\n+\t(\n+\t\tcd sub &&\n+\t\tgit submodule add ../innersub innersubpath &&\n+\t\tgit commit -m \"add nested submodule at innersubpath\"\n+\t) &&\n+\n+\t(\n+\t\tcd super/upstream-subpath &&\n+\t\tgit pull upstream &&\n+\t\tgit submodule update --init --recursive . &&\n+\t\t(\n+\t\t\tcd innersubpath &&\n+\t\t\tgit remote rename origin another_upstream\n+\t\t)\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with nested submodule' '\n+\t(\n+\t\tcd super &&\n+\t\techo \"another_upstream\" >expect &&\n+\t\tgit submodule--helper get-default-remote upstream-subpath/innersubpath >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'get-default-remote works with submodule that has no remotes' '\n+\t# Create a submodule directory manually without remotes\n+\t(\n+\t\tcd super &&\n+\t\tgit init no-remote-sub &&\n+\t\ttest_commit --no-tag -C no-remote-sub \"local commit\" local.txt \"local content\"\n+\t) &&\n+\n+\t# Add it as a submodule\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add ./no-remote-sub &&\n+\t\tgit commit -m \"add local submodule 'no-remote-sub'\"\n+\t) &&\n+\n+\t(\n+\t\tcd super &&\n+\t\t# Should fall back to \"origin\" remote name when no remotes exist\n+\t\techo \"origin\" >expect &&\n+\t\tgit submodule--helper get-default-remote no-remote-sub >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_done\n-- \n2.52.0\n\n"},{"id":"538340","messageId":"xmqqzf4gzi4i.fsf@gitster.g","threadId":"64785","inReplyTo":"20260303234044.105157-1-nasser.grainawi@oss.qualcomm.com","subject":"Re: [PATCH v6] submodule: fetch missing objects from default remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-09T23:40:45Z","receivedAt":"2026-03-09T23:40:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> writes:\n\n> When be76c21282 (fetch: ensure submodule objects fetched, 2018-12-06)\n> added support for fetching a missing submodule object by id, it\n> hardcoded the remote name as \"origin\" and deferred anything more\n> complicated for a later patch. Implement the NEEDSWORK item to remove\n> the hardcoded assumption by adding and using a submodule helper subcmd\n> 'get-default-remote'. Fixing this lets 'git fetch --recurse-submodules'\n> succeed when the fetched commit(s) in the superproject trigger a\n> submodule fetch, and that submodule's default remote name is not\n> \"origin\".\n>\n> Add non-\"origin\" remote tests to t5526-fetch-submodules.sh and\n> t5572-pull-submodule.sh demonstrating this works as expected and add\n> dedicated tests for get-default-remote.\n>\n> Signed-off-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>\n> Reviewed-by: Jacob Keller <jacob.keller@gmail.com>\n> ---\n> Removed the fetch progress output comparison from the tests as the\n> ordering is unpredictable.\n\nThis has gone quiet.  Shall we declare victory and mark the topic\nfor 'next'?\n\nThanks.\n"}]}