gix_discover/upwards/
types.rs1use std::{env, ffi::OsStr, path::PathBuf};
2
3#[derive(Debug, thiserror::Error)]
5#[expect(missing_docs)]
6pub enum Error {
7 #[error("Could not obtain the current working directory")]
8 CurrentDir(#[from] std::io::Error),
9 #[error("Relative path \"{}\"tries to reach beyond root filesystem", directory.display())]
10 InvalidInput { directory: PathBuf },
11 #[error("Failed to access a directory, or path is not a directory: '{}'", .path.display())]
12 InaccessibleDirectory { path: PathBuf },
13 #[error("Could not find a git repository in '{}' or in any of its parents", .path.display())]
14 NoGitRepository { path: PathBuf },
15 #[error("Could not find a git repository in '{}' or in any of its parents within ceiling height of {}", .path.display(), .ceiling_height)]
16 NoGitRepositoryWithinCeiling { path: PathBuf, ceiling_height: usize },
17 #[error("Could not find a git repository in '{}' or in any of its parents within device limits below '{}'", .path.display(), .limit.display())]
18 NoGitRepositoryWithinFs { path: PathBuf, limit: PathBuf },
19 #[error("None of the passed ceiling directories prefixed the git-dir candidate, making them ineffective.")]
20 NoMatchingCeilingDir,
21 #[error("Could not find a trusted git repository in '{}' or in any of its parents, candidate at '{}' discarded", .path.display(), .candidate.display())]
22 NoTrustedGitRepository {
23 path: PathBuf,
24 candidate: PathBuf,
25 required: gix_sec::Trust,
26 },
27 #[error("Could not determine trust level for path '{}'.", .path.display())]
28 CheckTrust {
29 path: PathBuf,
30 #[source]
31 err: std::io::Error,
32 },
33}
34
35#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
37pub enum TrustPolicy {
38 Required(gix_sec::Trust),
40 Assume(gix_sec::Trust),
42}
43
44impl Default for TrustPolicy {
45 fn default() -> Self {
46 TrustPolicy::Required(gix_sec::Trust::Reduced)
47 }
48}
49
50pub struct Options<'a> {
53 pub trust: TrustPolicy,
59 pub ceiling_dirs: Vec<PathBuf>,
67 pub match_ceiling_dir_or_error: bool,
70 pub cross_fs: bool,
75 pub dot_git_only: bool,
81 pub current_dir: Option<&'a std::path::Path>,
90}
91
92impl Default for Options<'_> {
93 fn default() -> Self {
94 Options {
95 trust: TrustPolicy::default(),
96 ceiling_dirs: vec![],
97 match_ceiling_dir_or_error: true,
98 cross_fs: false,
99 dot_git_only: false,
100 current_dir: None,
101 }
102 }
103}
104
105impl Options<'_> {
106 pub fn apply_environment(mut self) -> Self {
115 let name = "GIT_CEILING_DIRECTORIES";
116 if let Some(ceiling_dirs) = env::var_os(name) {
117 self.ceiling_dirs = parse_ceiling_dirs(&ceiling_dirs);
118 }
119 self
120 }
121}
122
123pub(crate) fn parse_ceiling_dirs(ceiling_dirs: &OsStr) -> Vec<PathBuf> {
128 let mut should_normalize = true;
129 let mut out = Vec::new();
130 for ceiling_dir in std::env::split_paths(ceiling_dirs) {
131 if ceiling_dir.as_os_str().is_empty() {
132 should_normalize = false;
133 continue;
134 }
135
136 if ceiling_dir.is_relative() {
138 continue;
139 }
140
141 let mut dir = ceiling_dir;
142 if should_normalize {
143 if let Ok(normalized) = gix_path::realpath(&dir) {
144 dir = normalized;
145 }
146 }
147 out.push(dir);
148 }
149 out
150}
151
152#[cfg(test)]
153mod tests {
154
155 #[test]
156 #[cfg(unix)]
157 fn parse_ceiling_dirs_from_environment_format() -> std::io::Result<()> {
158 use std::{fs, os::unix::fs::symlink};
159
160 use super::*;
161
162 let dir = tempfile::tempdir().expect("success creating temp dir");
164 let direct_path = dir.path().join("direct");
165 let symlink_path = dir.path().join("symlink");
166 fs::create_dir(&direct_path)?;
167 symlink(&direct_path, &symlink_path)?;
168
169 let symlink_str = symlink_path.to_str().expect("symlink path is valid utf8");
171 let ceiling_dir_string = format!("{symlink_str}:relative::{symlink_str}");
172 let ceiling_dirs = parse_ceiling_dirs(OsStr::new(ceiling_dir_string.as_str()));
173
174 assert_eq!(ceiling_dirs.len(), 2, "Relative path is discarded");
175 assert_eq!(
176 ceiling_dirs[0],
177 symlink_path.canonicalize().expect("symlink path exists"),
178 "Symlinks are resolved"
179 );
180 assert_eq!(
181 ceiling_dirs[1], symlink_path,
182 "Symlink are not resolved after empty item"
183 );
184
185 dir.close()
186 }
187
188 #[test]
189 #[cfg(windows)]
190 fn parse_ceiling_dirs_from_environment_format() -> std::io::Result<()> {
191 use std::{fs, os::windows::fs::symlink_dir};
192
193 use super::*;
194
195 let dir = tempfile::tempdir().expect("success creating temp dir");
197 let direct_path = dir.path().join("direct");
198 let symlink_path = dir.path().join("symlink");
199 fs::create_dir(&direct_path)?;
200 symlink_dir(&direct_path, &symlink_path)?;
201
202 let symlink_str = symlink_path.to_str().expect("symlink path is valid utf8");
204 let ceiling_dir_string = format!("{};relative;;{}", symlink_str, symlink_str);
205 let ceiling_dirs = parse_ceiling_dirs(OsStr::new(ceiling_dir_string.as_str()));
206
207 assert_eq!(ceiling_dirs.len(), 2, "Relative path is discarded");
208 assert_eq!(ceiling_dirs[0], direct_path, "Symlinks are resolved");
209 assert_eq!(
210 ceiling_dirs[1], symlink_path,
211 "Symlink are not resolved after empty item"
212 );
213
214 dir.close()
215 }
216}