use super::*;
pub fn normalize_affected_file(path: &str, repo_root: Option<&str>) -> String {
crate::hooks::decide::normalize_path(&path.replace('\\', "/"), repo_root)
}
pub fn normalize_affected_files_with_root(
files: &[String],
repo_root: Option<&str>,
) -> Vec<String> {
let mut out: Vec<String> = Vec::with_capacity(files.len());
for raw in files {
if raw.is_empty() {
continue;
}
let normalized = normalize_affected_file(raw, repo_root);
if normalized.is_empty() || out.contains(&normalized) {
continue;
}
out.push(normalized);
}
out
}
pub fn normalize_affected_files(files: &[String], repo_root: &Path) -> Vec<String> {
if !files.iter().any(|f| looks_absolute(f)) {
return normalize_affected_files_with_root(files, None);
}
let root = repo_root
.to_str()
.map(|r| resolve_lenient(r).unwrap_or_else(|| r.to_string()));
let resolved: Vec<String> = files
.iter()
.map(|f| match looks_absolute(f) {
true => resolve_lenient(f).unwrap_or_else(|| f.clone()),
false => f.clone(),
})
.collect();
normalize_affected_files_with_root(&resolved, root.as_deref())
}
fn looks_absolute(path: &str) -> bool {
path.starts_with('/') || std::path::Path::new(path).is_absolute()
}
pub(super) fn resolve_lenient(path: &str) -> Option<String> {
let path = std::path::Path::new(path);
if let Ok(c) = std::fs::canonicalize(path) {
return c.to_str().map(str::to_string);
}
let mut tail: Vec<std::ffi::OsString> = Vec::new();
let mut cur = path;
loop {
let parent = cur.parent()?;
tail.push(cur.file_name()?.to_os_string());
if let Ok(cp) = std::fs::canonicalize(parent) {
let mut out = cp;
for comp in tail.iter().rev() {
out.push(comp);
}
return out.to_str().map(str::to_string);
}
cur = parent;
}
}
pub(crate) fn with_normalized_affected_files(record: &Record, files: &[String]) -> Option<Record> {
let current = record.payload.as_ref()?.get("affected_files")?.as_array()?;
let unchanged = current.len() == files.len()
&& current
.iter()
.zip(files)
.all(|(v, f)| v.as_str() == Some(f.as_str()));
if unchanged {
return None;
}
let mut out = record.clone();
let obj = out.payload.as_mut()?.as_object_mut()?;
obj.insert(
"affected_files".into(),
serde_json::Value::Array(
files
.iter()
.cloned()
.map(serde_json::Value::String)
.collect(),
),
);
Some(out)
}
pub(crate) fn tombstoned_copy(record: &Record, reason: TombstoneReason, at: u64) -> Record {
let mut out = record.clone();
out.lifecycle = RecordLifecycle::Tombstoned { reason, at };
out.updated_at = at;
out.version.logical_clock += 1;
out.version.wall_clock = at;
out
}