use crate::candidate_object::{CandidateMode, ObjectId};
use std::path::Path;
pub const ADDED_PATH_HEAD_REASON: &str =
"HEAD does not hold it as the ordinary file the fix was computed against";
pub const ADDED_PATH_STAGED_REASON: &str = "it has staged changes";
pub const ADDED_PATH_MISSING_REASON: &str = "its worktree copy is missing";
pub const ADDED_PATH_UNSTAGED_REASON: &str = "its worktree copy has unstaged changes";
pub type Entry = (CandidateMode, ObjectId);
pub fn added_path_message(path: &str, reason: &str) -> String {
return format!(
"A policy fix needs to change {path}, which this fix did not select, but {reason}. \
Include {path} in the fix pathspecs so the fix applies to its worktree copy, \
or restore it to match HEAD (git restore --staged --worktree -- {path}), \
then run the fix again."
);
}
fn is_ordinary(mode: CandidateMode) -> bool {
return mode == CandidateMode::Regular || mode == CandidateMode::Executable;
}
pub fn head_mode(head: Option<&Entry>, index: &Entry) -> Result<CandidateMode, &'static str> {
let Some((mode, object)) = head else {
return Err(ADDED_PATH_HEAD_REASON);
};
if *object != index.1 || !is_ordinary(*mode) {
return Err(ADDED_PATH_HEAD_REASON);
}
if index.0 != *mode {
return Err(ADDED_PATH_STAGED_REASON);
}
return Ok(*mode);
}
fn executable_matches(metadata: &std::fs::Metadata, mode: CandidateMode) -> bool {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
return (metadata.permissions().mode() & 0o100 != 0) == (mode == CandidateMode::Executable);
}
#[cfg(not(unix))]
{
let _ = (metadata, mode);
return true;
}
}
pub fn worktree_refusal(
file: &Path,
mode: CandidateMode,
head_bytes: &[u8],
) -> Result<Option<&'static str>, std::io::Error> {
let metadata: std::fs::Metadata = match std::fs::symlink_metadata(file) {
Ok(found) => found,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
return Ok(Some(ADDED_PATH_MISSING_REASON));
}
Err(error) => return Err(error),
};
if !metadata.is_file() || !executable_matches(&metadata, mode) {
return Ok(Some(ADDED_PATH_UNSTAGED_REASON));
}
if std::fs::read(file)? != head_bytes {
return Ok(Some(ADDED_PATH_UNSTAGED_REASON));
}
return Ok(None);
}
#[cfg(test)]
#[path = "policy_added_path_tests.rs"]
mod tests;