use crate::transaction_git::{GitContext, GitRequest, run_git};
use std::ffi::OsString;
use std::path::Path;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ChangedPaths {
pub count: usize,
pub first: Option<String>,
pub attributes_changed: bool,
}
pub fn plain(text: &str) -> String {
let mut shown: String = String::new();
for character in text.chars() {
shown.push(if character.is_control() { '?' } else { character });
}
return shown;
}
impl ChangedPaths {
pub fn has_changes(&self) -> bool {
return self.count > 0;
}
}
fn is_nul_byte(byte: &u8) -> bool {
return *byte == 0;
}
fn plain_path_text(path: &&[u8]) -> String {
return plain(String::from_utf8_lossy(path).as_ref());
}
fn ask_git(context: &GitContext, root: &Path, arguments: &[&str]) -> Result<Vec<u8>, String> {
let words: String = arguments.iter().skip(1).copied().take(1).collect::<Vec<&str>>().join(" ");
let request: GitRequest = GitRequest { cwd: root.to_path_buf(), arguments: arguments.iter().copied().map(OsString::from).collect(), without_prefix: true, ..GitRequest::default() };
return match run_git(context, &request) {
Ok(found) if found.code == Some(0) => Ok(found.stdout),
Ok(found) => {
let said: String = String::from_utf8_lossy(found.stderr.as_slice()).trim().to_string();
let code: String = match found.code {
Some(number) => format!("ended with exit code {number}"),
None => String::from("was stopped by a signal"),
};
crate::diagnostic_log::debug("changedPaths", format!("Git could not list the changes (`git {words}` {code}: {said})").as_str());
Err(if said.is_empty() { format!("`git {words}` {code} and said nothing") } else { format!("`git {words}` {code}: {}", plain(said.as_str())) })
}
Err(error) => {
crate::diagnostic_log::debug("changedPaths", format!("Git could not list the changes ({error})").as_str());
Err(format!("`git {words}` could not be started ({error})"))
}
};
}
pub fn changed_paths(context: &GitContext, root: &Path) -> Result<ChangedPaths, String> {
let listing: Vec<u8> = ask_git(context, root, &["--no-optional-locks", "status", "--porcelain=v1", "-z", "--untracked-files=normal", "--no-renames"])?;
let untracked_attributes: Vec<u8> = ask_git(context, root, &["--no-optional-locks", "ls-files", "--others", "--exclude-standard", "-z", "--", ":(glob)**/.gitattributes"])?;
let mut paths: Vec<&[u8]> = Vec::new();
for entry in listing.split(is_nul_byte) {
if entry.is_empty() {
continue;
}
if let Some(path) = entry.get(3..) {
paths.push(path);
}
}
let mut names_attributes: bool = false;
for path in &paths {
if *path == b".gitattributes" || path.ends_with(b"/.gitattributes") {
names_attributes = true;
break;
}
}
let attributes_changed: bool = !untracked_attributes.is_empty() || names_attributes;
let first: Option<String> = paths.first().map(plain_path_text);
return Ok(ChangedPaths { count: paths.len(), first, attributes_changed });
}