use crate::config_file::load_repository_config;
use crate::config_schema::{CliGitConfig, ConcurrencyConfig};
use crate::discarded_prune::Limits;
use crate::config_snapshot::read_config_snapshot;
use crate::external_hooks::{ConfiguredHook, configured_hooks, describe_hook, explain_external_hooks};
use crate::transaction_git::{GitContext, GitRequest, run_git};
use std::path::{Path, PathBuf};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConfigFindings {
pub hooks: Vec<ConfiguredHook>,
pub retired_hooks_key: bool,
pub retired_index_lock_key: bool,
pub retired_landing_key: bool,
pub discarded: Limits,
}
fn top_level(context: &GitContext, cwd: &Path) -> Option<PathBuf> {
let request: GitRequest = GitRequest::new(cwd, &["rev-parse", "--show-toplevel"]);
let output = run_git(context, &request).ok()?;
if !output.succeeded() {
return None;
}
let text: String = String::from_utf8(output.stdout).ok()?;
let trimmed: &str = text.trim_end_matches('\n');
if trimmed.is_empty() {
return None;
}
return Some(PathBuf::from(trimmed));
}
pub fn config_findings(context: &GitContext, cwd: &Path) -> ConfigFindings {
let hooks: Vec<ConfiguredHook> = match read_config_snapshot(context, cwd) {
Some(snapshot) => configured_hooks(&snapshot),
None => Vec::new(),
};
let loaded: Option<CliGitConfig> = match top_level(context, cwd) {
Some(root) => match load_repository_config(root.as_path()) {
Ok(found) => Some(found.config),
Err(_) => None,
},
None => None,
};
let (retired_hooks_key, retired_index_lock_key, retired_landing_key): (bool, bool, bool) = match loaded.as_ref() {
Some(found) => {
let retired: ConcurrencyConfig = found.concurrency;
(retired.hooks.present, retired.index_lock.present, retired.landing.present)
}
None => (false, false, false),
};
let discarded: Limits = match loaded {
Some(found) => found.discarded,
None => Limits::default(),
};
return ConfigFindings {
hooks,
retired_hooks_key,
retired_index_lock_key,
retired_landing_key,
discarded,
};
}
pub fn render_hook_lines(findings: &ConfigFindings) -> String {
let mut text: String = String::new();
for hook in &findings.hooks {
text.push_str(describe_hook(hook).as_str());
text.push('\n');
}
return text;
}
pub fn render_notes(findings: &ConfigFindings) -> String {
let mut text: String = String::new();
if !findings.hooks.is_empty() {
text.push_str("git monochromatic-git-policy-cli doctor: ");
text.push_str(explain_external_hooks(findings.hooks.as_slice()).as_str());
text.push('\n');
}
if findings.retired_hooks_key {
text.push_str(
"git monochromatic-git-policy-cli doctor: monochromatic-git-policy-cli.config.jsonc has a `hooks` key (`hooks.concurrentCommits`). It is accepted so \
older files still load, and ignored: the wrapper runs no external hook, so there is no hook lock to \
tune. Delete the key when convenient.\n",
);
}
if findings.retired_index_lock_key {
text.push_str(
"git monochromatic-git-policy-cli doctor: monochromatic-git-policy-cli.config.jsonc sets `indexLock.unprovenOwnerTimeoutMs`. The key does nothing: the \
commit service waits for another process's `index.lock` for as long as the lock stays there, whether the process \
is proven to hold it or not, says what it knows about the owner while it waits, and reads no timeout from this \
file. Remove the key from monochromatic-git-policy-cli.config.jsonc.\n",
);
}
if findings.retired_landing_key {
text.push_str(
"git monochromatic-git-policy-cli doctor: monochromatic-git-policy-cli.config.jsonc sets `landing.reserveAfterLostRaces`. The key does nothing: the \
commit service lands commits in the order they arrive and no longer reserves a landing slot for a commit \
that lost races. Remove the key from monochromatic-git-policy-cli.config.jsonc.\n",
);
}
return text;
}
#[cfg(test)]
#[path = "doctor_config_tests.rs"]
mod tests;