chadtmiller.com/knotless
git that lives in your Atmosphere account
1
10
knotless checks --ref takes a branch named bare
.changeset/checks-bare-ref.mdnew+7−0
| @@ -0,0 +1,7 @@ | ||
--- | ||
default: patch | ||
--- | ||
| ||
# `knotless checks --ref` takes a branch named bare | ||
| ||
`knotless checks --ref main` lists the checks of `refs/heads/main`, as `rerun` and `workflows` read a bare name, rather than none; a tag of that name, when there is no such branch. A branch deleted since, as a merged pull's, still names its checks. The MCP `checks` tool's `ref` takes one the same way. | ||
crates/knotless/src/checks.rs+44−2
| @@ -23,8 +23,9 @@ use tokio::time::Instant; | ||
/// Which checks [`list`] gives. | ||
#[derive(Debug, Clone, Copy, Default)] | ||
pub struct Filter<'a> { | ||
/// Only those of pushes to this ref, and runs asked for there, in | ||
/// full, such as `refs/heads/main`: not a pull's or a batch's. | ||
/// Only those of pushes to this ref, and runs asked for there, such | ||
/// as `refs/heads/main`, or `main` as [`full_ref`] reads it: not a | ||
/// pull's or a batch's. | ||
pub refname: Option<&'a str>, | ||
/// Only those of the commits whose hex IDs start with this, as | ||
/// [`commit`] gives it. | ||
| @@ -125,9 +126,30 @@ pub async fn list(ctx: &Ctx, url: &RepoUrl, filter: Filter<'_>, logs: bool) -> R | ||
); | ||
} | ||
let runners: Vec<String> = repo.value.runners.iter().map(|r| r.did.clone()).collect(); | ||
let refname = filter | ||
.refname | ||
.map(|n| full_ref(n, |r| snap.find_ref(r).is_some())); | ||
let filter = Filter { | ||
refname: refname.as_deref(), | ||
..filter | ||
}; | ||
by(ctx, &remote, &repo.value.key, &runners, filter, logs).await | ||
} | ||
| ||
/// The ref whose checks `name` asks for, where `has` says which refs the | ||
/// repository has: itself in full (`refs/…`), else the tag of that name | ||
/// when there is a tag and no branch of it, else the branch, as git takes | ||
/// a bare name: a branch deleted since, as a merged pull's, still names | ||
/// its checks. | ||
pub fn full_ref(name: &str, has: impl Fn(&str) -> bool) -> String { | ||
let (branch, tag) = (format!("refs/heads/{name}"), format!("refs/tags/{name}")); | ||
match name.starts_with("refs/") { | ||
true => name.to_string(), | ||
false if !has(&branch) && has(&tag) => tag, | ||
false => branch, | ||
} | ||
} | ||
| ||
/// The checks `runners` filed of the repository `remote` reads, keyed | ||
/// `key`, as [`list`] gives them, whether the repository names them or not. | ||
pub async fn by( | ||
| @@ -510,6 +532,26 @@ mod tests { | ||
assert_eq!(scan_prefix(o, k, &queue), batches); | ||
} | ||
| ||
#[test] | ||
fn a_bare_name_is_a_branch_or_else_a_tag() { | ||
let has = |r: &str| { | ||
[ | ||
"refs/heads/main", | ||
"refs/tags/v1", | ||
"refs/heads/both", | ||
"refs/tags/both", | ||
] | ||
.contains(&r) | ||
}; | ||
assert_eq!(full_ref("refs/heads/main", has), "refs/heads/main"); | ||
assert_eq!(full_ref("refs/notes/x", has), "refs/notes/x"); | ||
assert_eq!(full_ref("main", has), "refs/heads/main"); | ||
assert_eq!(full_ref("v1", has), "refs/tags/v1"); | ||
assert_eq!(full_ref("both", has), "refs/heads/both"); | ||
// A branch gone since, as a merged pull's, is still a branch. | ||
assert_eq!(full_ref("gone", has), "refs/heads/gone"); | ||
} | ||
| ||
#[test] | ||
fn a_commit_is_its_hex_id_or_the_start_of_it() { | ||
assert_eq!(commit("0838C57").unwrap(), "0838c57"); | ||
crates/knotless/tests/cli.rs+6−0
| @@ -768,6 +768,12 @@ fn runners() { | ||
assert_eq!(v.as_array().unwrap().len(), 1); | ||
assert_eq!(v[0]["runner"], ci.account.did.as_str()); | ||
assert_eq!(v[0]["steps"][0]["durationMs"], 5); | ||
// A branch named bare, as git takes it, whether the repository still | ||
// has it or not. | ||
let (_, bare) = s.cli(&["checks", &url, "--ref", "main", "--json"], b""); | ||
assert_eq!(bare, out); | ||
let (_, out) = s.cli(&["checks", &url, "--ref", "x"], b""); | ||
assert!(out.starts_with("success\tlint\trefs/heads/x"), "{out}"); | ||
let (_, out) = s.cli(&["checks", &url, "--commit", &b], b""); | ||
assert!(out.starts_with("success\tlint"), "{out}"); | ||
// As `git log --oneline` prints it, and in capitals. | ||
crates/knotless-mcp/src/lib.rs+3−3
| @@ -294,9 +294,9 @@ pub struct History { | ||
pub struct Checks { | ||
/// `handle/name`, `did/name` or `knotless://handle/name`. | ||
pub repo: String, | ||
/// Only checks of pushes to this ref, and runs asked for there, in | ||
/// full, such as `refs/heads/main`: a pull's runs are found by `pull`, | ||
/// a batch's by `queue`. | ||
/// Only checks of pushes to this ref, and runs asked for there, such | ||
/// as `refs/heads/main`, or a branch or tag named bare, as `main`: a | ||
/// pull's runs are found by `pull`, a batch's by `queue`. | ||
#[serde(rename = "ref")] | ||
pub refname: Option<String>, | ||
/// Only checks of this commit: its hex object ID, or at least its | ||
docs/reference/cli.md+1−1
| @@ -201,7 +201,7 @@ queue](../design/merge-queue.md). | ||
| `runners <url> [add \| remove <handle>]` | the accounts whose checks of the repository readers show; `add` makes the account a member of a private repository, and invites it as `create` does (`added`, then `warning: …` if it could not be), which `remove` leaves it (`unshare` takes it back). A runner added after secrets were set has none of them, as nothing keeps their values to seal again: `warning: <did> gets the secrets set before only once each is set again: <names>`. `remove` deletes the secrets sealed to it | | ||
| `secret <url> set <name> [--ref <pattern>]... [--runner <handle>]...` | as owner, give the repository's runners the secret `<name>`, read from stdin less one line ending, sealed to the key each publishes so only it reads it, and left at the appview (`KNOTLESS_APPVIEW`), replacing one of the same name; `--ref`, repeatable, limits it to pushes to those refs, as a trigger's `refs` match; `--runner`, repeatable, by handle or DID, gives it to those of the repository's runners alone, and takes back the copy any other of them had. Prints `sealed <name> to <runners>`. Fails if the repository names no runners, a `--runner` is not one of them, or one it seals to publishes no key (a knotless-run from before). `<name>` is a shell variable's, not starting `KNOTLESS_RUN`; the value at most 16 KiB, the refs at most 16. See [Secrets](../design/runner.md#secrets) | | ||
| `secret <url> ls \| rm <name>` | as owner, each secret given, one line per runner, `<name>\t<runner>\t<refs, or *>\t<updated>`, never a value; or take one back from every runner, printing `removed <name>` | | ||
| `checks <url> [--ref <name>] [--commit <sha>] [--pull <at-uri>] [--queue <branch>] [--workflow <name>] [--log] [--images <dir>] [--wait <secs>] [--json]` | what those runners report, newest first; `--ref`, of pushes to that ref and runs asked for there (a pull's are read with `--pull`); `--queue`, of the merge queue's batches; `--log` adds the end of each log; `--images` saves each check's pictures; `--wait` waits for runs to finish | | ||
| `checks <url> [--ref <name>] [--commit <sha>] [--pull <at-uri>] [--queue <branch>] [--workflow <name>] [--log] [--images <dir>] [--wait <secs>] [--json]` | what those runners report, newest first; `--ref`, of pushes to that ref and runs asked for there, in full or a branch or tag named bare, as `main` (a pull's are read with `--pull`); `--queue`, of the merge queue's batches; `--log` adds the end of each log; `--images` saves each check's pictures; `--wait` waits for runs to finish | | ||
| `rerun <url> --ref <name> \| --pull <at-uri> [--workflow <name>]` | as owner, ask the runners to run it again, or only one of its workflows | | ||
| `rerun <url> --ref <name> --workflow <name> [-e <name>=<value>]...` | as owner, ask for one workflow there, with variables its steps see | | ||
| `workflows <url> [<ref>]` | the workflows at a branch or tag (the default branch), and what runs each there | | ||