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 <gitster@pobox.com>
jch
Volodymyr Vriukalo 2026-08-22 01:01:42 +02:00 committed by Junio C Hamano
parent ef0cac7509
commit 9d721b0606
3 changed files with 26 additions and 4 deletions

View File

@ -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;

View File

@ -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] <name> <start-oid> <start-name>"),
N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start-oid> [<start-name>]"),
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;
}

View File

@ -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" &&
(