hive-forge: ci-runs --branch takes the refs the listing prints
The runs-list query's `ref` filter matches only a fully-qualified ref, while the listing prints `prettyref` (`main`, `#3967`). So no value a caller can read off the output was accepted by the filter, and the rejection rendered as "no matching runs" — indistinguishable from a branch that has never been built. Qualify the value before it reaches the query: `refs/...` passes through, `#N`/`N` becomes the pull head ref, anything else becomes a branch. A slash cannot mark an already-qualified ref, since branch names contain them. Not shared with ci-rerun: its `ref` is a workflow_dispatch body field taking a bare branch name, so the same normalisation would break it. Closes #3970
This commit is contained in:
parent
fe7bf81d4a
commit
8a48f958b8
1 changed files with 61 additions and 2 deletions
|
|
@ -21,7 +21,10 @@ pub struct Args {
|
||||||
/// Only runs of this workflow file (e.g. `ci.yml`).
|
/// Only runs of this workflow file (e.g. `ci.yml`).
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
workflow: Option<String>,
|
workflow: Option<String>,
|
||||||
/// Only runs on this branch or ref (e.g. `main`, `damocles/foo`).
|
/// Only runs on this ref. A branch name (`main`, `damocles/foo`) or a
|
||||||
|
/// PR (`#N`) is qualified for you; a `refs/…` value is used as
|
||||||
|
/// given. An all-digit value is read as a PR number — to filter a
|
||||||
|
/// branch literally named that, pass `refs/heads/<name>`.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
branch: Option<String>,
|
branch: Option<String>,
|
||||||
/// How many runs to print (default 20).
|
/// How many runs to print (default 20).
|
||||||
|
|
@ -40,7 +43,7 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
let (owner, name) = client.owner_repo()?;
|
let (owner, name) = client.owner_repo()?;
|
||||||
let query = ListActionRunsQuery {
|
let query = ListActionRunsQuery {
|
||||||
workflow_id: args.workflow.clone(),
|
workflow_id: args.workflow.clone(),
|
||||||
r#ref: args.branch.clone(),
|
r#ref: args.branch.as_deref().map(qualify_ref),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let body = client
|
let body = client
|
||||||
|
|
@ -65,6 +68,30 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Qualify a `--branch` value into the full ref the runs-list filter
|
||||||
|
/// matches on. It only matches a complete ref, while the listing prints
|
||||||
|
/// `prettyref` (`main`, `#N`) — so without this every value a caller
|
||||||
|
/// can read off the output comes back as an empty result set, which is
|
||||||
|
/// indistinguishable from a branch that has never been built.
|
||||||
|
///
|
||||||
|
/// A slash cannot be used to detect an already-qualified ref: branch
|
||||||
|
/// names contain slashes (`damocles/3932-merge-collisions-lint`), so
|
||||||
|
/// `refs/` is the only reliable marker and anything else is a branch.
|
||||||
|
///
|
||||||
|
/// Deliberately not shared with `ci-rerun`, whose `ref` is a
|
||||||
|
/// `workflow_dispatch` body field taking a **bare** branch name. Same
|
||||||
|
/// field name, different domain; qualifying there would break it.
|
||||||
|
fn qualify_ref(value: &str) -> String {
|
||||||
|
if value.starts_with("refs/") {
|
||||||
|
return value.to_owned();
|
||||||
|
}
|
||||||
|
let digits = value.strip_prefix('#').unwrap_or(value);
|
||||||
|
if !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit()) {
|
||||||
|
return format!("refs/pull/{digits}/head");
|
||||||
|
}
|
||||||
|
format!("refs/heads/{value}")
|
||||||
|
}
|
||||||
|
|
||||||
/// One human-readable line per run: number, status, workflow file, ref,
|
/// One human-readable line per run: number, status, workflow file, ref,
|
||||||
/// title. Widths are cosmetic alignment, not a fixed schema — a longer
|
/// title. Widths are cosmetic alignment, not a fixed schema — a longer
|
||||||
/// value just pushes the next column over rather than truncating.
|
/// value just pushes the next column over rather than truncating.
|
||||||
|
|
@ -76,3 +103,35 @@ fn print_row(run: &ActionRun) {
|
||||||
let title = run.title.as_deref().unwrap_or("");
|
let title = run.title.as_deref().unwrap_or("");
|
||||||
println!("{number:<7} {status:<10} {workflow:<20} {run_ref:<28} {title}");
|
println!("{number:<7} {status:<10} {workflow:<20} {run_ref:<28} {title}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::qualify_ref;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn qualifies_a_bare_branch_name() {
|
||||||
|
assert_eq!(qualify_ref("main"), "refs/heads/main");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_slash_does_not_imply_an_already_qualified_ref() {
|
||||||
|
assert_eq!(qualify_ref("damocles/foo"), "refs/heads/damocles/foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reads_a_pr_number_as_its_pull_head_ref() {
|
||||||
|
assert_eq!(qualify_ref("#3967"), "refs/pull/3967/head"); // lint:allow test input
|
||||||
|
assert_eq!(qualify_ref("3967"), "refs/pull/3967/head");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn leaves_a_qualified_ref_alone() {
|
||||||
|
assert_eq!(qualify_ref("refs/heads/main"), "refs/heads/main");
|
||||||
|
assert_eq!(qualify_ref("refs/pull/3967/merge"), "refs/pull/3967/merge");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_lone_hash_is_a_branch_name_not_an_empty_pr() {
|
||||||
|
assert_eq!(qualify_ref("#"), "refs/heads/#");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue