diff --git a/lib/apps/fabro-cli/src/commands/run/remote_workflow.rs b/lib/apps/fabro-cli/src/commands/run/remote_workflow.rs index 2e1dc417d..b0117f14f 100644 --- a/lib/apps/fabro-cli/src/commands/run/remote_workflow.rs +++ b/lib/apps/fabro-cli/src/commands/run/remote_workflow.rs @@ -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";