use crate::child_environment::{FORWARD_TARGET_VARIABLE, environment_value};
use crate::git_version_cache::{BinaryIdentity, binary_identity, cache_directory, read_cached_text, store_cached_text};
use std::ffi::OsString;
use std::path::{Path, PathBuf};
pub const SYSTEM_CONFIG_KIND: &str = "git-system-config";
pub const SYSTEM_ATTRIBUTES_KIND: &str = "git-system-attributes";
const PATH_LIMIT: usize = 4096;
pub fn parse_path_line(stdout: &[u8]) -> Option<PathBuf> {
let line: &[u8] = stdout.strip_suffix(b"\n")?;
if line.is_empty() || line.len() > PATH_LIMIT || line.contains(&b'\n') || line.contains(&0) {
return None;
}
let path: PathBuf = PathBuf::from(std::str::from_utf8(line).ok()?);
return if path.is_absolute() { Some(path) } else { None };
}
fn ask_git(real_git: &Path) -> Option<Vec<u8>> {
return ask_git_var(real_git, "GIT_CONFIG_SYSTEM");
}
fn ask_git_attributes(real_git: &Path) -> Option<Vec<u8>> {
return ask_git_var(real_git, "GIT_ATTR_SYSTEM");
}
fn ask_git_var(real_git: &Path, variable: &str) -> Option<Vec<u8>> {
let mut command = std::process::Command::new(real_git);
command
.args(["var", variable])
.env_clear()
.env("LC_ALL", "C")
.current_dir("/")
.stdin(std::process::Stdio::null())
.stderr(std::process::Stdio::null());
crate::started_as_git::mark_real_git_start(&mut command, crate::started_as_git::StartMode::Child);
let output = command.output().ok()?;
return if output.status.success() { Some(output.stdout) } else { None };
}
pub fn system_config_of(real_git: &Path, cache: Option<&Path>, ask: &dyn Fn(&Path) -> Option<Vec<u8>>) -> Option<PathBuf> {
return learned_path(real_git, cache, SYSTEM_CONFIG_KIND, ask);
}
pub fn system_attributes_of(real_git: &Path, cache: Option<&Path>, ask: &dyn Fn(&Path) -> Option<Vec<u8>>) -> Option<PathBuf> {
return learned_path(real_git, cache, SYSTEM_ATTRIBUTES_KIND, ask);
}
fn learned_path(real_git: &Path, cache: Option<&Path>, kind: &str, ask: &dyn Fn(&Path) -> Option<Vec<u8>>) -> Option<PathBuf> {
let identity: BinaryIdentity = binary_identity(real_git)?;
if let Some(directory) = cache
&& let Some(path) = read_cached_text(directory, &identity, kind).and_then(|text| { return parse_path_line(&[text.as_slice(), b"\n"].concat()); })
{
return Some(path);
}
let answered: Vec<u8> = ask(real_git)?;
let path: PathBuf = parse_path_line(answered.as_slice())?;
if let Some(directory) = cache
&& binary_identity(real_git).as_ref() == Some(&identity)
{
store_cached_text(directory, &identity, kind, path.as_os_str().as_encoded_bytes());
}
return Some(path);
}
pub fn with_real_git(variables: &[(OsString, OsString)], real_git: &Path) -> Vec<(OsString, OsString)> {
let mut named: Vec<(OsString, OsString)> = variables.to_vec();
if environment_value(variables, FORWARD_TARGET_VARIABLE).is_none() {
named.push((OsString::from(FORWARD_TARGET_VARIABLE), real_git.as_os_str().to_os_string()));
}
return named;
}
pub fn system_config_for(variables: &[(OsString, OsString)]) -> Option<PathBuf> {
let target: OsString = environment_value(variables, FORWARD_TARGET_VARIABLE)?;
return system_config_of(Path::new(&target), cache_directory(variables).as_deref(), &ask_git);
}
pub fn system_attributes_for(variables: &[(OsString, OsString)]) -> Option<PathBuf> {
let target: OsString = environment_value(variables, FORWARD_TARGET_VARIABLE)?;
return system_attributes_of(Path::new(&target), cache_directory(variables).as_deref(), &ask_git_attributes);
}
#[cfg(test)]
#[path = "git_system_config_tests.rs"]
mod tests;