Reject unusable remote default branches with a --target-branch hint

Target resolution accepted any remote default HEAD that passed the ref
selector grammar, then failed inside GitRunTarget::validate with a
generic branch-grammar error when the default branch was something like
heads/main or tags/release. Validate the default branch as a working
branch name up front and point the user at --target-branch, since they
passed no branch at all.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Scott Werner 2026-09-11 09:35:41 -06:00
parent e2d95be8b7
commit 7d050ed0f6

View file

@ -195,7 +195,7 @@ impl NativeGit {
let records = self
.records(root, &repository, &["HEAD".into()], cancel)
.await?;
default_head(&records)?
default_target_branch(&records)?
};
let target = GitRunTarget {
repo: repository.to_string(),
@ -402,6 +402,20 @@ fn default_head(records: &str) -> anyhow::Result<(String, String)> {
))
}
/// The remote default branch as a run target. `GitRunTarget` requires a bare
/// working branch name, which is stricter than the ref grammar `default_head`
/// accepts for workflow acquisition; report the mismatch with the flag that
/// resolves it instead of a generic branch-grammar error.
fn default_target_branch(records: &str) -> anyhow::Result<(String, String)> {
let (branch, sha) = default_head(records)?;
if !repository::is_valid_git_branch_name(&branch) {
bail!(
"remote default branch `{branch}` cannot name a run target branch; pass --target-branch to select a working branch"
);
}
Ok((branch, sha))
}
/// The fully qualified refs a validated `--workflow-ref` may name. A bare name
/// may be a branch or a tag; a `refs/heads/` or `refs/tags/` name is exactly
/// one.
@ -953,6 +967,26 @@ mod tests {
);
}
#[test]
fn remote_workflow_default_target_branch_requires_a_working_branch_name() {
let sha = "1234567890123456789012345678901234567890";
for (head, valid) in [
("trunk", true),
("topic/slash", true),
("heads/main", false),
("tags/release", false),
(sha, false),
] {
let records = format!("ref: refs/heads/{head}\tHEAD\n{sha}\tHEAD\n");
assert_eq!(default_head(&records).unwrap().0, head);
let target = default_target_branch(&records);
assert_eq!(target.is_ok(), valid, "{head}");
if !valid {
assert!(target.unwrap_err().to_string().contains("--target-branch"));
}
}
}
#[test]
fn remote_workflow_matches_records_exactly_and_rejects_host_symlinks() {
let sha = "1234567890123456789012345678901234567890";