use std::io;
use std::path::{Path, PathBuf};
use crate::paths;
#[derive(Debug)]
pub(crate) enum FindError {
IsHome(PathBuf),
Io(io::Error),
}
impl From<io::Error> for FindError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
pub(crate) fn find(dir: &Path, markers: &[PathBuf]) -> Result<PathBuf, FindError> {
let abs = paths::absolute(dir)?;
let bound = repo_root(&abs).unwrap_or_else(|| abs.clone());
let mut chosen = bound.clone();
let mut at: &Path = &abs;
loop {
if has_marker(at, markers) {
chosen = at.to_path_buf();
break;
}
if at == bound {
break;
}
match at.parent() {
Some(parent) if parent != at => at = parent,
_ => break,
}
}
if let Ok(home) = paths::home()
&& paths::same_dir(&chosen, &home)
{
return Err(FindError::IsHome(chosen));
}
Ok(chosen)
}
fn repo_root(dir: &Path) -> Option<PathBuf> {
let mut at = dir;
loop {
if at.join(".git").symlink_metadata().is_ok() {
return Some(at.to_path_buf());
}
at = at.parent().filter(|p| *p != at)?;
}
}
fn has_marker(dir: &Path, markers: &[PathBuf]) -> bool {
markers.iter().any(|m| dir.join(m).is_dir())
}
#[derive(Debug)]
pub(crate) struct OutsideRoot {
pub(crate) dir: PathBuf,
pub(crate) real: PathBuf,
}
#[derive(Debug)]
pub(crate) enum WithinError {
Outside(OutsideRoot),
Io(io::Error),
}
impl From<io::Error> for WithinError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
pub(crate) fn within(root: &Path, dir: &Path) -> Result<(), WithinError> {
let real_root = paths::real(root)?;
let real_dir = paths::real(dir)?;
if real_dir.starts_with(&real_root) {
return Ok(());
}
Err(WithinError::Outside(OutsideRoot { dir: dir.to_path_buf(), real: real_dir }))
}