use std::{env, ffi::OsStr, path::PathBuf};
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("Could not obtain the current working directory")]
CurrentDir(#[from] std::io::Error),
#[error("Relative path \"{}\"tries to reach beyond root filesystem", directory.display())]
InvalidInput { directory: PathBuf },
#[error("Failed to access a directory, or path is not a directory: '{}'", .path.display())]
InaccessibleDirectory { path: PathBuf },
#[error("Could find a git repository in '{}' or in any of its parents", .path.display())]
NoGitRepository { path: PathBuf },
#[error("Could find a git repository in '{}' or in any of its parents within ceiling height of {}", .path.display(), .ceiling_height)]
NoGitRepositoryWithinCeiling { path: PathBuf, ceiling_height: usize },
#[error("Could find a git repository in '{}' or in any of its parents within device limits below '{}'", .path.display(), .limit.display())]
NoGitRepositoryWithinFs { path: PathBuf, limit: PathBuf },
#[error("None of the passed ceiling directories prefixed the git-dir candidate, making them ineffective.")]
NoMatchingCeilingDir,
#[error("Could find a trusted git repository in '{}' or in any of its parents, candidate at '{}' discarded", .path.display(), .candidate.display())]
NoTrustedGitRepository {
path: PathBuf,
candidate: PathBuf,
required: git_sec::Trust,
},
#[error("Could not determine trust level for path '{}'.", .path.display())]
CheckTrust {
path: PathBuf,
#[source]
err: std::io::Error,
},
}
pub struct Options<'a> {
pub required_trust: git_sec::Trust,
pub ceiling_dirs: Vec<PathBuf>,
pub match_ceiling_dir_or_error: bool,
pub cross_fs: bool,
pub current_dir: Option<&'a std::path::Path>,
}
impl Default for Options<'_> {
fn default() -> Self {
Options {
required_trust: git_sec::Trust::Reduced,
ceiling_dirs: vec![],
match_ceiling_dir_or_error: true,
cross_fs: false,
current_dir: None,
}
}
}
impl Options<'_> {
pub fn apply_environment(mut self) -> Self {
let name = "GIT_CEILING_DIRECTORIES";
if let Some(ceiling_dirs) = env::var_os(name) {
self.ceiling_dirs = parse_ceiling_dirs(&ceiling_dirs);
}
self.match_ceiling_dir_or_error = false;
self
}
}
pub(crate) fn parse_ceiling_dirs(ceiling_dirs: &OsStr) -> Vec<PathBuf> {
let mut should_normalize = true;
let mut out = Vec::new();
for ceiling_dir in std::env::split_paths(ceiling_dirs) {
if ceiling_dir.as_os_str().is_empty() {
should_normalize = false;
continue;
}
if ceiling_dir.is_relative() {
continue;
}
let mut dir = ceiling_dir;
if should_normalize {
if let Ok(normalized) = git_path::realpath(&dir) {
dir = normalized;
}
}
out.push(dir);
}
out
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(unix)]
fn parse_ceiling_dirs_from_environment_format() -> std::io::Result<()> {
use std::{fs, os::unix::fs::symlink};
use super::*;
let dir = tempfile::tempdir().expect("success creating temp dir");
let direct_path = dir.path().join("direct");
let symlink_path = dir.path().join("symlink");
fs::create_dir(&direct_path)?;
symlink(&direct_path, &symlink_path)?;
let symlink_str = symlink_path.to_str().expect("symlink path is valid utf8");
let ceiling_dir_string = format!("{symlink_str}:relative::{symlink_str}");
let ceiling_dirs = parse_ceiling_dirs(OsStr::new(ceiling_dir_string.as_str()));
assert_eq!(ceiling_dirs.len(), 2, "Relative path is discarded");
assert_eq!(
ceiling_dirs[0],
symlink_path.canonicalize().expect("symlink path exists"),
"Symlinks are resolved"
);
assert_eq!(
ceiling_dirs[1], symlink_path,
"Symlink are not resolved after empty item"
);
dir.close()
}
#[test]
#[cfg(windows)]
fn parse_ceiling_dirs_from_environment_format() -> std::io::Result<()> {
use std::{fs, os::windows::fs::symlink_dir};
use super::*;
let dir = tempfile::tempdir().expect("success creating temp dir");
let direct_path = dir.path().join("direct");
let symlink_path = dir.path().join("symlink");
fs::create_dir(&direct_path)?;
symlink_dir(&direct_path, &symlink_path)?;
let symlink_str = symlink_path.to_str().expect("symlink path is valid utf8");
let ceiling_dir_string = format!("{};relative;;{}", symlink_str, symlink_str);
let ceiling_dirs = parse_ceiling_dirs(OsStr::new(ceiling_dir_string.as_str()));
assert_eq!(ceiling_dirs.len(), 2, "Relative path is discarded");
assert_eq!(ceiling_dirs[0], direct_path, "Symlinks are resolved");
assert_eq!(
ceiling_dirs[1], symlink_path,
"Symlink are not resolved after empty item"
);
dir.close()
}
}