use std::{
fs,
path::{Path, PathBuf},
};
use crate::{GitError, HashKind, Result, error::invalid, refs};
pub(crate) fn discover(start: &Path) -> Result<(Option<PathBuf>, PathBuf)> {
let canonical = start.canonicalize()?;
let mut current = if canonical.is_file() {
canonical
.parent()
.ok_or_else(|| GitError::NotRepository(start.display().to_string()))?
.to_owned()
} else {
canonical
};
loop {
let dot_git = current.join(".git");
if dot_git.is_dir() {
return Ok((Some(current), dot_git));
}
if dot_git.is_file() {
return Ok((Some(current.clone()), parse_git_file(&dot_git, ¤t)?));
}
if current.join("HEAD").is_file() && current.join("objects").is_dir() {
return Ok((None, current));
}
if !current.pop() {
return Err(GitError::NotRepository(start.display().to_string()));
}
}
}
pub(crate) fn common_dir(git_dir: &Path) -> Result<PathBuf> {
let Some(value) = refs::read_text(&git_dir.join("commondir"))? else {
return Ok(git_dir.to_owned());
};
let path = Path::new(&value);
Ok(if path.is_absolute() {
path.to_owned()
} else {
git_dir.join(path)
})
}
pub(crate) fn hash_kind(common_dir: &Path) -> Result<HashKind> {
let config = fs::read_to_string(common_dir.join("config"))?;
let mut extensions = false;
for line in config.lines().map(str::trim) {
if line.starts_with('[') {
extensions = line.eq_ignore_ascii_case("[extensions]");
} else if extensions
&& let Some((key, value)) = line.split_once('=')
&& key.trim().eq_ignore_ascii_case("objectformat")
{
return match value.trim().to_ascii_lowercase().as_str() {
"sha1" => Ok(HashKind::Sha1),
"sha256" => Ok(HashKind::Sha256),
other => Err(GitError::Unsupported(format!("object format {other}"))),
};
}
}
Ok(HashKind::Sha1)
}
fn parse_git_file(path: &Path, work_dir: &Path) -> Result<PathBuf> {
let text = fs::read_to_string(path)?;
let value = text
.trim()
.strip_prefix("gitdir: ")
.ok_or_else(|| invalid("invalid .git file"))?;
let path = Path::new(value);
Ok(if path.is_absolute() {
path.to_owned()
} else {
work_dir.join(path)
})
}