use crate::standards_review_input::has_shadow_log;
use crate::standards_review_run::{Contained, ReviewOptions, ReviewOutcome, options_from_environment, review_landed, run_contained};
use crate::standards_review_source::{read_landed_commit, read_message, read_names};
use crate::standards_review_text::commit_subject;
use crate::service_review_key::{KEY_VARIABLES, ReviewKey};
use crate::service_work::Verdict;
use crate::transaction_git::GitContext;
use crate::work_item::WorkItem;
use std::path::{Path, PathBuf};
use std::time::Duration;
pub const CALL_QUIET_WINDOW: Duration = Duration::from_secs(10);
pub fn common_dir_of(git_dir: &Path) -> PathBuf {
let Ok(text) = std::fs::read(git_dir.join("commondir")) else {
return git_dir.to_path_buf();
};
let end: usize = text.iter().rposition(|byte| { return *byte != b'\r' && *byte != b'\n'; }).map_or(0, |last| { return last + 1; });
if end == 0 {
return git_dir.to_path_buf();
}
#[cfg(unix)]
let named: PathBuf = PathBuf::from(<std::ffi::OsStr as std::os::unix::ffi::OsStrExt>::from_bytes(&text[..end]));
#[cfg(not(unix))]
let named: PathBuf = PathBuf::from(String::from_utf8_lossy(&text[..end]).into_owned());
let joined: PathBuf = git_dir.join(named);
return std::fs::canonicalize(&joined).unwrap_or(joined);
}
pub fn options_for(item: &WorkItem, variable: &dyn Fn(&str) -> Option<String>) -> ReviewOptions {
let common: PathBuf = common_dir_of(Path::new(item.git_dir.as_str()));
let mut options: ReviewOptions = options_from_environment(common.as_path(), variable);
options.quiet_window = CALL_QUIET_WINDOW;
return options;
}
pub fn review_variables(key: Option<&ReviewKey>) -> impl Fn(&str) -> Option<String> {
let exposed: Option<String> = key.map(|held| { return held.expose().to_string(); });
return move |name: &str| {
if name == KEY_VARIABLES[0] {
return exposed.clone();
}
if name == KEY_VARIABLES[1] {
return None;
}
return std::env::var(name).ok();
};
}
fn run_review(context: &GitContext, item: &WorkItem, options: &ReviewOptions, now_millis: &dyn Fn() -> u64) -> ReviewOutcome {
let cwd: PathBuf = PathBuf::from(item.repository.as_str());
let names: Vec<String> = match read_names(context, cwd.as_path(), item.oid.as_str()) {
Ok(found) => found,
Err(message) => return ReviewOutcome::Unread(message),
};
let subject: String = match read_message(context, cwd.as_path(), item.oid.as_str()) {
Ok(message) => commit_subject(message.as_str()),
Err(message) => return ReviewOutcome::Unread(message),
};
let read = |files: &[String]| { return read_landed_commit(context, cwd.as_path(), item.oid.as_str(), subject.as_str(), files); };
return review_landed(names.as_slice(), item.oid.as_str(), subject.as_str(), &read, options, now_millis);
}
fn wall_clock_millis() -> u64 {
return std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |elapsed| { return u64::try_from(elapsed.as_millis()).unwrap_or(u64::MAX); });
}
pub fn review_item(context: &GitContext, item: &WorkItem, options: ReviewOptions) -> Verdict {
if !has_shadow_log(Path::new(item.repository.as_str())) {
return Verdict::Done;
}
let owned_context: GitContext = GitContext { common_dir: Some(PathBuf::from(item.git_dir.as_str())), ..context.clone() };
let owned: WorkItem = item.clone();
let outcome: Contained<ReviewOutcome> = run_contained(move || { return run_review(&owned_context, &owned, &options, &wall_clock_millis); }, crate::wait_ceilings::REVIEW_CEILING);
return match outcome {
Contained::Panicked => Verdict::Failed(format!("the review of {} stopped with an internal error", item.oid)),
Contained::CeilingReached(ceiling) => Verdict::Failed(ceiling_reason(item.oid.as_str(), ceiling)),
Contained::Finished(ReviewOutcome::Unread(message) | ReviewOutcome::Unwritable(message)) => Verdict::Failed(message),
Contained::Finished(ReviewOutcome::NoEntry | ReviewOutcome::Skipped | ReviewOutcome::Reviewed(_) | ReviewOutcome::AlreadyLogged) => Verdict::Done,
};
}
pub fn ceiling_reason(oid: &str, ceiling: Duration) -> String {
return format!(
"the standards review of {oid} ran for {} seconds, the longest the service lets one review run, and the service stopped waiting for it; \
review it again with `SHADOW_REVIEW_SHA={oid} mise run shadow-review`",
ceiling.as_secs()
);
}
#[cfg(test)]
#[path = "service_review_tests.rs"]
mod tests;