use std::collections::HashSet;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use crate::project::GitStatus;
pub fn partition_ignored(
root: &Path,
candidates: Vec<PathBuf>,
) -> (Vec<PathBuf>, Vec<PathBuf>, GitStatus) {
if candidates.is_empty() {
return (Vec::new(), Vec::new(), GitStatus::Ignored);
}
let relative: Vec<String> = candidates
.iter()
.map(|p| {
p.strip_prefix(root)
.unwrap_or(p)
.to_string_lossy()
.into_owned()
})
.collect();
match run_check_ignore(root, &relative) {
Ok(ignored) => {
let (keep, dropped): (Vec<_>, Vec<_>) = candidates
.into_iter()
.zip(&relative)
.partition(|(_, rel)| ignored.contains(rel.as_str()));
(
keep.into_iter().map(|(p, _)| p).collect(),
dropped.into_iter().map(|(p, _)| p).collect(),
GitStatus::Ignored,
)
}
Err(status) => (candidates, Vec::new(), status),
}
}
fn run_check_ignore(root: &Path, relative: &[String]) -> Result<HashSet<String>, GitStatus> {
let mut child = Command::new("git")
.arg("-C")
.arg(root)
.args(["check-ignore", "--stdin", "-z"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.map_err(|_| GitStatus::Unavailable)?;
{
let mut stdin = child.stdin.take().ok_or(GitStatus::Unavailable)?;
for path in relative {
if stdin.write_all(path.as_bytes()).is_err() || stdin.write_all(&[0]).is_err() {
break;
}
}
}
let output = child
.wait_with_output()
.map_err(|_| GitStatus::Unavailable)?;
match output.status.code() {
Some(0) | Some(1) => {}
Some(128) => return Err(GitStatus::NoRepo),
_ => return Err(GitStatus::Unavailable),
}
Ok(String::from_utf8_lossy(&output.stdout)
.split('\0')
.filter(|s| !s.is_empty())
.map(normalise)
.collect())
}
fn normalise(path: &str) -> String {
path.trim_end_matches('/').to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_candidates_is_trivially_ignored() {
let (keep, dropped, status) = partition_ignored(Path::new("/tmp"), Vec::new());
assert!(keep.is_empty() && dropped.is_empty());
assert_eq!(status, GitStatus::Ignored);
}
#[test]
fn outside_a_repo_keeps_everything() {
let dir = std::env::temp_dir();
let candidates = vec![dir.join("target")];
let (keep, dropped, status) = partition_ignored(&dir, candidates.clone());
if status != GitStatus::Ignored {
assert_eq!(keep, candidates);
assert!(dropped.is_empty());
}
}
}