From 9d721b060686f309b3a719fdb9f261124ca954fd Mon Sep 17 00:00:00 2001 From: Volodymyr Vriukalo <0@zitro.id> Date: Sat, 22 Aug 2026 01:01:42 +0200 Subject: [PATCH] branch: allow recursion with no tracking name Creating a branch across submodules from a commit that no ref points at fails, with the helper's usage text reprinted as an error: submodule 'sub': usage: git submodule--helper create-branch [...] fatal: submodule 'sub': cannot create branch 'branch-a' `submodule_create_branch()` runs the helper in a child process because `install_branch_config_multiple_remotes()` cannot write config into a submodule, and passes the branch name, the start oid and the tracking name as three positionals. `dwim_branch_start()` leaves the tracking name NULL where the start point named no ref, and `strvec_pushl()` stops at the first NULL, so the child receives two positionals. `module_create_branch()` requires exactly three and prints its usage. Make the third positional optional, since a start point that named no ref has no tracking name to give and the recursion has nothing to track in the submodule either. Push it separately in the caller too: relying on `strvec_pushl()` to stop early leaves the argument dropped by accident rather than by intent, and a reader has to know where the terminator falls to see that it can go missing at all. 961b130d20 (branch: add --recurse-submodules option for branch creation, 2022-01-28) introduced both sides. This is the same NULL tracking name as the previous patch, reached one step earlier: the dry-run pass over the submodules runs before the superproject's own `setup_tracking()` call, so with a submodule present this failure hides the abort that patch removes. The new test therefore needs that patch under it. Assisted-by: An LLM. Signed-off-by: Volodymyr Vriukalo <0@zitro.id> Signed-off-by: Junio C Hamano --- branch.c | 10 +++++++++- builtin/submodule--helper.c | 7 ++++--- t/t3207-branch-submodule.sh | 13 +++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/branch.c b/branch.c index e26301197d..b5182f2bb7 100644 --- a/branch.c +++ b/branch.c @@ -760,7 +760,15 @@ static int submodule_create_branch(struct repository *r, break; } - strvec_pushl(&child.args, name, start_oid, tracking_name, NULL); + /* + * The tracking name is absent when the start point named no ref. + * Push it separately: strvec_pushl() stops at the first NULL, so + * passing it inline would drop the argument by accident rather + * than by intent. + */ + strvec_pushl(&child.args, name, start_oid, NULL); + if (tracking_name) + strvec_push(&child.args, tracking_name); if ((ret = start_command(&child))) return ret; diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index e7cd3225fa..1eb73de674 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -3336,7 +3336,7 @@ static int module_create_branch(int argc, const char **argv, const char *prefix, OPT_END() }; const char *const usage[] = { - N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] "), + N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] []"), NULL }; struct repo_config_values *cfg = repo_config_values(the_repository); @@ -3345,13 +3345,14 @@ static int module_create_branch(int argc, const char **argv, const char *prefix, track = cfg->branch_track; argc = parse_options(argc, argv, prefix, options, usage, 0); - if (argc != 3) + if (argc < 2 || argc > 3) usage_with_options(usage, options); if (!quiet && !dry_run) printf_ln(_("creating branch '%s'"), argv[0]); - create_branches_recursively(the_repository, argv[0], argv[1], argv[2], + create_branches_recursively(the_repository, argv[0], argv[1], + argc > 2 ? argv[2] : NULL, force, reflog, quiet, track, dry_run); return 0; } diff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh index 97d057959d..4dd26c6c0f 100755 --- a/t/t3207-branch-submodule.sh +++ b/t/t3207-branch-submodule.sh @@ -115,6 +115,19 @@ test_expect_success 'should move a branch to a start point that names no ref' ' ) ' +test_expect_success 'should recurse into submodules from a start point that names no ref' ' + test_when_finished "reset_test" && + ( + cd super && + oid=$(git rev-parse HEAD) && + git branch --recurse-submodules branch-a "$oid" && + git rev-parse branch-a && + git -C sub rev-parse branch-a && + git -C sub/sub-sub rev-parse branch-a && + git -C second/sub rev-parse branch-a + ) +' + test_expect_success 'should ignore submodule.recurse when not creating branches' ' test_when_finished "reset_test" && (