use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::process::Command;
pub fn common_dir_for(git_dir: &Path) -> PathBuf {
let Ok(text) = std::fs::read_to_string(git_dir.join("commondir")) else {
return git_dir.to_path_buf();
};
let named: &str = text.trim_end_matches(['\r', '\n']);
if named.is_empty() {
return git_dir.to_path_buf();
}
let joined: PathBuf = git_dir.join(named);
return std::fs::canonicalize(&joined).unwrap_or(joined);
}
pub fn git_directory_of(start: &Path) -> Option<PathBuf> {
for directory in start.ancestors() {
let dot_git: PathBuf = directory.join(".git");
if let Ok(metadata) = std::fs::symlink_metadata(dot_git.as_path()) {
if metadata.is_dir() {
return Some(dot_git);
}
if metadata.is_file() {
let text: String = std::fs::read_to_string(dot_git.as_path()).ok()?;
let named: &str = text.lines().next()?.strip_prefix("gitdir:")?.trim();
return Some(directory.join(named));
}
}
if directory.join("HEAD").is_file() && directory.join("objects").is_dir() {
return Some(directory.to_path_buf());
}
}
return None;
}
pub fn roots_for(working: &Path, named: Option<PathBuf>) -> Vec<PathBuf> {
let git_dir: Option<PathBuf> = named.or_else(|| { return git_directory_of(working); });
let mut roots: Vec<PathBuf> = Vec::new();
if let Some(directory) = git_dir {
let common: PathBuf = common_dir_for(directory.as_path());
roots.push(directory.clone());
if common != directory {
roots.push(common);
}
}
return roots;
}
pub fn roots_of_command(command: &Command) -> Vec<PathBuf> {
let working: PathBuf = command
.get_current_dir()
.map(Path::to_path_buf)
.or_else(|| { return std::env::current_dir().ok(); })
.unwrap_or_default();
let named: Option<PathBuf> = command
.get_envs()
.filter(|(name, _)| { return *name == OsStr::new("GIT_DIR"); })
.last()
.and_then(|(_, value)| { return value.map(|path| { return working.join(path); }); });
return roots_for(working.as_path(), named);
}