1use std::{
2 io::Error,
3 path::{Path, PathBuf},
4};
5
6pub fn git_root<P: AsRef<Path>>(path: P) -> Result<Option<PathBuf>, Error> {
13 let path: PathBuf = normalize(path)?;
14 Ok(traverse(&path))
15}
16
17fn normalize<P: AsRef<Path>>(path: P) -> Result<PathBuf, Error> {
18 let path: PathBuf = path.as_ref().canonicalize()?;
19 if path.is_dir() {
20 Ok(path)
21 } else {
22 match path.parent() {
23 Some(parent) => Ok(parent.to_path_buf()),
24 None => Ok(path),
25 }
26 }
27}
28
29fn traverse(path: &Path) -> Option<PathBuf> {
30 let git_config: PathBuf = path.join(".git").join("config");
31 if git_config.exists() {
32 Some(path.to_path_buf())
33 } else {
34 match path.parent() {
35 Some(parent) => traverse(parent),
36 None => None,
37 }
38 }
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44
45 #[test]
46 fn test_directory_is_not_in_git_repo() {
47 let git_root: Option<PathBuf> = git_root("/dev").unwrap();
48 assert_eq!(None, git_root);
49 }
50
51 #[test]
52 fn test_file_is_not_in_git_repo() {
53 let git_root: Option<PathBuf> = git_root("/dev/null").unwrap();
54 assert_eq!(None, git_root);
55 }
56
57 #[test]
58 fn test_project_has_git_root() {
59 let git_root: Option<PathBuf> = git_root(".").unwrap();
60 assert!(git_root.is_some());
61 }
62
63 #[test]
64 fn test_project_git_config_dir_is_inside_git_repo() {
65 let git_root: Option<PathBuf> = git_root(".git/").unwrap();
66 assert!(git_root.is_some());
67 }
68}