git-ents.gitmain
⌘K
foforge
commit cd8c280
fixup! feat: add the `git ents auth` porcelain for managing push signers
Joseph D. Carpinelli · 1 month ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

crates/git-ents/src/main.rs @@ -113,7 +113,7 @@ let signing_key = match key { Some(path) => ensure_key(path)?, None => match config_get("user.signingkey") { - Some(existing) => existing, + Some(existing) => ensure_key(&signing_key_path(&existing))?, None => ensure_key(&default_key_path()?)?, }, }; @@ -146,10 +146,47 @@ .map_err(|error| format!("could not write {}: {error}", public.display()))?; return Ok(public.display().to_string()); } + if !confirm(&format!( + "no SSH key at {}; generate a new ed25519 keypair there?", + private.display() + ))? { + return Err("setup needs a signing key; re-run with `--key` or generate one".to_owned()); + } generate_key(&private)?; Ok(public.display().to_string()) } +/// Resolve the path to ensure for a configured `user.signingkey`. A real key +/// path (or one a `.pub` can be derived from) is used as-is; a bare key id — +/// e.g. an openpgp fingerprint left from another signing format — is not a +/// path, so fall back to the default SSH key location rather than generating a +/// keypair named after it. +fn signing_key_path(configured: &str) -> PathBuf { + let candidate = expand_tilde(configured); + let (private, public) = key_paths(&candidate); + if private.exists() || public.exists() || configured.contains('/') { + candidate + } else { + default_key_path().unwrap_or(candidate) + } +} + +/// Ask `question` on the terminal, returning whether it was accepted. Enter +/// (an empty reply) accepts; a reply starting with `n` declines. +fn confirm(question: &str) -> Result<bool, String> { + use std::io::Write as _; + print!("{question} [Y/n] "); + std::io::stdout() + .flush() + .map_err(|error| format!("could not write prompt: {error}"))?; + let mut reply = String::new(); + std::io::stdin() + .read_line(&mut reply) + .map_err(|error| format!("could not read reply: {error}"))?; + let reply = reply.trim(); + Ok(reply.is_empty() || !reply.starts_with(['n', 'N'])) +} + /// Split a key path into its private and `.pub` halves. fn key_paths(path: &Path) -> (PathBuf, PathBuf) { if path.extension().is_some_and(|extension| extension == "pub") {