vv/branch-recurse-no-start-ref
"git branch" with submodule.propagateBranches enabled mishandled a start point that names no ref, such as a raw object id. In a repository with no submodules it aborted with a BUG after having already moved the branch; with submodules it failed earlier, when the helper it invokes rejected a truncated argument list. Both are the same missing tracking name, which has been corrected.
I hit the first of these while scripting a branch rewrite -- the sort that moves a branch to a commit no ref points at yet, which for such a script is the ordinary case rather than the exception:
BUG: refspec.c:442: refspec_find_match: need either src or dst
Aborted (core dumped)The ref had already been updated. So the abort is loud and the damage is quiet, which is the wrong way round: an exit status of 134 on an operation that in fact completed will fool any caller that checks it, and one whose error path rolls back will helpfully undo a successful update.
The exposure is bounded. submodule.propagateBranches is documented as experimental, and reproducing it needs all four of that setting, submodule.recurse, a configured remote, and a start point that is not a ref name; drop any one and the command succeeds. Within those bounds it is not exotic -- a script that moves a branch by object id hits it on the first attempt, the commit it wants having no ref on it yet, which is rather the point of moving a branch. It reproduces identically on 2.54.0, 2.55.0 and master, which is why this is based on maint.
Both patches guard at the call site rather than inside the callee. That follows create_branch(), which already declines the same value with "if (real_ref && track)", and it leaves setup_tracking()'s own convention intact: it opens by BUG()ing on a caller that should not have called it, so absorbing a NULL quietly would contradict that four lines later. Neither patch changes anything for a start point that does name a ref.
This series was written with LLM assistance, recorded as an "Assisted-by: An LLM." trailer on both patches. I used it for the whole change rather than as step-by-step guidance, because at this size the distinction is irrelevant: two guards and two tests, short enough to read in full. I have read the surrounding code and checked every claim in the commit messages against the source myself.
Two things I would welcome direction on.
dwim_and_setup_tracking() carries the same unguarded call. It survives only because its single caller passes BRANCH_TRACK_OVERRIDE, under which dwim_branch_start() dies rather than returning NULL -- safe by an argument its caller happens to pass, not by anything in the function itself. Nothing enforces that, so a future caller could reintroduce the same abort. I left it alone to keep the series to the bug I actually hit, but I am happy to add a third patch.
The first test uses no submodule, because the bug does not need one. I put it in t3207 since create_branches_recursively() is only reached under submodule.propagateBranches, but t3200 is a perfectly defensible home and I will move it on request.
The series merges cleanly into next and seen; both merge results build and pass t3200 and t3207, and the full suite passes on the topic itself.
---
Volodymyr Vriukalo (2):
branch: do not track a start point with no ref
branch: allow recursion with no tracking namebranch.c | 12 ++++++++++-- builtin/submodule--helper.c | 7 ++++--- t/t3207-branch-submodule.sh | 30 ++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) --- base-commit: e9019fcafe0040228b8631c30f97ae1adb61bcdc change-id: 20260822-vv-branch-recurse-no-start-ref-31fac1e34eab
Best regards, -- Volodymyr Vriukalo <0@zitro.id>