feat: add `git ents checks` porcelain for `refs/meta/checks`
commit
ec40890feat: add `git ents checks` porcelain for `refs/meta/checks`
Mirrors git ents auth: list, add, and remove read a remote’s check set by
fetching refs/meta/checks, edit it through git_ents::checks, and push it
back as a signed compare-and-swap. add <name> <command> replaces any check
already under that name.
feat: add checks list reading a remote’s set
feat: add checks add <name> <command> recording/replacing a check
feat: add checks remove <name> dropping a check
Assisted-by: Claude:claude-opus-4-8
Reviews
No reviews of this commit yet — record a verdict below.
Start a review
crates/git-ents/src/main.rs
@@ -12,6 +12,7 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use clap::{Parser, Subcommand};
+use git_ents::checks::{self, CHECKS_REF, Check};
use git_ents::signers::{self, AUTH_REF, Signer};
#[derive(Parser)]
@@ -28,6 +29,11 @@
#[command(subcommand)]
action: Action,
},
+ /// Manage the configured checks at `refs/meta/checks`.
+ Checks {
+ #[command(subcommand)]
+ action: ChecksAction,
+ },
}
#[derive(Subcommand)]
@@ -76,10 +82,39 @@
},
}
+#[derive(Subcommand)]
+enum ChecksAction {
+ /// List the checks configured on a remote.
+ List {
+ /// Remote to read `refs/meta/checks` from.
+ #[arg(default_value = "origin")]
+ remote: String,
+ },
+ /// Add (or replace) a check on a remote's set and push the update.
+ Add {
+ /// Name to record the check under (`checks/<name>`).
+ name: String,
+ /// Command the check runs (e.g. `cargo fmt --check`).
+ command: String,
+ /// Remote whose `refs/meta/checks` to update.
+ #[arg(default_value = "origin")]
+ remote: String,
+ },
+ /// Remove a check from a remote's set and push the update.
+ Remove {
+ /// Name (`checks/<name>`) to drop.
+ name: String,
+ /// Remote whose `refs/meta/checks` to update.
+ #[arg(default_value = "origin")]
+ remote: String,
+ },
+}
+
fn main() -> ExitCode {
let cli = Cli::parse();
let result = match cli.command {
Top::Auth { action } => run_auth(action),
+ Top::Checks { action } => run_checks(action),
};
match result {
Ok(()) => ExitCode::SUCCESS,
@@ -103,6 +138,69 @@
}
}
+fn run_checks(action: ChecksAction) -> Result<(), String> {
+ match action {
+ ChecksAction::List { remote } => list_checks(&remote),
+ ChecksAction::Add {
+ name,
+ command,
+ remote,
+ } => add_check(&name, &command, &remote),
+ ChecksAction::Remove { name, remote } => remove_check(&name, &remote),
+ }
+}
+
+/// Print each check configured on `remote` as `<name> <command>`.
+fn list_checks(remote: &str) -> Result<(), String> {
+ let repo = repo()?;
+ sync_checks(remote)?;
+ let checks = checks::load(&repo).map_err(|error| error.to_string())?;
+ if checks.is_empty() {
+ println!("no checks configured on {remote}");
+ return Ok(());
+ }
+ for check in &checks {
+ println!("{} {}", check.name, check.command);
+ }
+ Ok(())
+}
+
+/// Add `name` running `command` to `remote`'s set, replacing any check already
+/// recorded under that name, and push the update.
+fn add_check(name: &str, command: &str, remote: &str) -> Result<(), String> {
+ let repo = repo()?;
+ let expected = sync_checks(remote)?;
+ let mut checks = checks::load(&repo).map_err(|error| error.to_string())?;
+ checks.retain(|check| check.name != name);
+ checks.push(Check {
+ name: name.to_owned(),
+ command: command.to_owned(),
+ });
+ checks::store(&repo, &checks).map_err(|error| error.to_string())?;
+ push_checks(remote, expected.as_deref())?;
+ println!("recorded check {name}");
+ Ok(())
+}
+
+/// Drop the check named `name` from `remote` and push the update.
+fn remove_check(name: &str, remote: &str) -> Result<(), String> {
+ let repo = repo()?;
+ let expected = sync_checks(remote)?;
+ let before = checks::load(&repo).map_err(|error| error.to_string())?;
+ let count = before.len();
+ let after: Vec<Check> = before
+ .into_iter()
+ .filter(|check| check.name != name)
+ .collect();
+ if after.len() == count {
+ return Err(format!("no check named {name} on {remote}"));
+ }
+ checks::store(&repo, &after).map_err(|error| error.to_string())?;
+ push_checks(remote, expected.as_deref())?;
+ println!("removed {name}");
+ Ok(())
+}
+
/// Set this machine up to produce the signed pushes the server requires:
/// ensure a signing key exists, then record the SSH signing config
/// (SSH-format signatures, the key, and "sign when the server asks" so pushes
@@ -368,6 +466,34 @@
git_run(&["push", "--force-if-includes", &lease, remote, AUTH_REF])
}
+/// Mirror `remote`'s `refs/meta/checks` into the local repository so the check
+/// helpers see the current set, returning the remote's current object id (or
+/// `None` when it has no such ref). When the remote has none, clear any stale
+/// local ref so the set reads empty.
+fn sync_checks(remote: &str) -> Result<Option<String>, String> {
+ let listing = git_capture(&["ls-remote", remote, CHECKS_REF])?;
+ let oid = listing.split_whitespace().next().map(str::to_owned);
+ if oid.is_some() {
+ let refspec = format!("+{CHECKS_REF}:{CHECKS_REF}");
+ git_run(&["fetch", "--quiet", remote, &refspec])?;
+ } else {
+ let _deleted = git_capture(&["update-ref", "-d", CHECKS_REF]);
+ }
+ Ok(oid)
+}
+
+/// Push the local `refs/meta/checks` to `remote`, signed per the client's
+/// config. As with the signer set, the update is a compare-and-swap against
+/// `expected` so a set someone changed since the fetch is not clobbered.
+fn push_checks(remote: &str, expected: Option<&str>) -> Result<(), String> {
+ const ZERO: &str = "0000000000000000000000000000000000000000";
+ let lease = format!(
+ "--force-with-lease={CHECKS_REF}:{}",
+ expected.unwrap_or(ZERO)
+ );
+ git_run(&["push", "--force-if-includes", &lease, remote, CHECKS_REF])
+}
+
/// Resolve the OpenSSH public key to operate on, defaulting to the key behind
/// `user.signingkey`.
fn public_key(key: Option<&Path>) -> Result<String, String> {