use std::io::Write as _;
use anyhow::{Context, Result};
use futures_util::StreamExt;
use prek_consts::env_vars::{EnvVars, EnvVarsRead};
use crate::concurrency::INTERNAL_CONCURRENCY;
use crate::git;
use crate::hook::Hook;
use crate::hooks::HookOutput;
pub(crate) async fn run(hook: &Hook) -> Result<HookOutput> {
let Some(range) = resolve_range().await? else {
return Ok(HookOutput::unchanged(0, Vec::new()));
};
let output = git::git_cmd()?
.current_dir(hook.work_dir())
.args([
"log",
"--no-merges",
"--no-show-signature",
"-z",
"--pretty=format:%H %h",
])
.arg(range)
.output()
.await?;
let commits = String::from_utf8_lossy(&output.stdout);
let mut checks =
futures_util::stream::iter(commits.split('\0').filter(|record| !record.is_empty()))
.map(|record| async move {
let (oid, short_oid) = record
.split_once(' ')
.context("Invalid commit information from Git")?;
let output = git::git_cmd()?
.current_dir(hook.work_dir())
.args(["verify-commit", oid])
.check(false)
.output()
.await
.with_context(|| format!("Failed to verify commit {oid}"))?;
anyhow::Ok((short_oid, output))
})
.buffered(*INTERNAL_CONCURRENCY);
let mut failed = false;
let mut message = output.stderr;
while let Some(result) = checks.next().await {
let (short_oid, output) = result?;
if output.status.success() {
continue;
}
failed = true;
writeln!(message, "{short_oid}: signature verification failed")?;
for line in String::from_utf8_lossy(&output.stderr).trim_end().lines() {
writeln!(message, " {line}")?;
}
}
Ok(HookOutput::unchanged(i32::from(failed), message))
}
async fn resolve_range() -> Result<Option<String>> {
let from_ref = EnvVars.var(EnvVars::PRE_COMMIT_FROM_REF).ok();
let to_ref = EnvVars.var(EnvVars::PRE_COMMIT_TO_REF).ok();
if let Some(to_ref) = to_ref {
return Ok(Some(match from_ref {
Some(from_ref) => format!("{from_ref}..{to_ref}"),
None => to_ref,
}));
}
if !git::rev_exists("HEAD").await? {
return Ok(None);
}
Ok(Some("HEAD^!".to_string()))
}