use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use trusty_mpm::core::config::MpmConfig;
use trusty_mpm::core::project_aliases::main_checkout_root;
use super::shell_lex;
use super::split_shell_segments;
use crate::commands::hook_rewrite::first_command_token;
fn listed(root: &Path, list: &[PathBuf]) -> bool {
let Ok(real) = root.canonicalize() else {
return false;
};
list.iter()
.filter(|entry| entry.is_absolute())
.any(|entry| entry.canonicalize().is_ok_and(|e| e == real))
}
pub(crate) fn is_documents_repo(root: &Path) -> bool {
listed(root, &MpmConfig::load_default().pm_guard.documents_repos)
}
pub(super) fn reset_keep_is_exempt(
command: &str,
verb: &str,
tail: &[String],
target: &Path,
) -> bool {
reset_keep_exempt_with(
(command, verb, tail, target),
|| MpmConfig::load_default().pm_guard.runtime_checkouts,
content_matches,
)
}
fn reset_keep_exempt_with(
(command, verb, tail, target): (&str, &str, &[String], &Path),
list: impl FnOnce() -> Vec<PathBuf>,
probe: impl FnOnce(&Path, &str) -> Option<bool>,
) -> bool {
if verb != "reset" {
return false;
}
let Some(rev) = keep_revision(tail) else {
return false;
};
if !command_is_a_lone_reset(command) {
return false;
}
let Some(root) = main_checkout_root(target) else {
return false;
};
listed(&root, &list()) && probe(&root, rev) == Some(true)
}
fn keep_revision(tail: &[String]) -> Option<&str> {
let mut keep = 0;
let mut rev = None;
for token in tail {
match token.as_str() {
"--keep" => keep += 1,
"-q" | "--quiet" => {}
t if !t.starts_with('-') && rev.is_none() => rev = Some(t),
_ => return None,
}
}
(keep == 1).then_some(rev.unwrap_or("HEAD"))
}
fn command_is_a_lone_reset(command: &str) -> bool {
let mut resets = 0;
for segment in split_shell_segments(command) {
let trimmed = segment.trim();
if trimmed.is_empty() || first_command_token(trimmed).as_deref() == Some("cd") {
continue;
}
if shell_lex::git_subcommand(trimmed).as_deref() != Some("reset") {
return false;
}
resets += 1;
}
resets == 1
}
fn content_matches(root: &Path, rev: &str) -> Option<bool> {
let status = Command::new("git")
.arg("-C")
.arg(root)
.args([
"diff",
"--quiet",
"--no-ext-diff",
"--no-textconv",
rev,
"--",
])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.ok()?;
match status.code() {
Some(0) => Some(true),
Some(1) => Some(false),
_ => None,
}
}
#[cfg(test)]
#[path = "operator_checkouts_tests.rs"]
mod tests;