libscilo/version_control/
mod.rs1use std::{
5 env::current_dir,
6 fs, io,
7 ops::Not,
8 path::{Path, PathBuf},
9};
10use strum::VariantArray;
11
12#[derive(VariantArray)]
14pub enum SupportedVCS {
15 Git,
17
18 Jujutsu,
20
21 Mercurial,
23
24 Subversion,
26}
27
28impl SupportedVCS {
29 fn repo_dirname(&self) -> &str {
31 match self {
32 Self::Git => ".git",
33 Self::Jujutsu => ".jj",
34 Self::Mercurial => ".hg",
35 Self::Subversion => ".svn",
36 }
37 }
38}
39
40pub fn project_root() -> PathBuf {
44 match current_dir() {
45 Ok(current_dir) => match determine_vcs_root(¤t_dir) {
46 Ok(dir) => dir,
47 Err(_) => current_dir,
48 },
49 Err(_) => panic!(
50 "Error getting the current working directory. This directory might have been deleted by another process. Please double check this is the correct directory."
51 ),
52 }
53}
54
55fn determine_vcs_root(start_path: &Path) -> io::Result<PathBuf> {
59 let mut path = start_path;
60 let mut errors = Vec::new();
61
62 loop {
63 let mut dir_contents = fs::read_dir(path)?;
64
65 let contains_vcs_info = dir_contents.any(|entry| match entry {
66 Ok(entry) => match entry.file_name().to_str() {
67 Some(name) => SupportedVCS::VARIANTS
69 .iter()
70 .any(|vcs| vcs.repo_dirname() == name),
71 None => {
72 errors.push(format!(
73 "Error when reading entry name {0} inside directory {1}.",
74 entry.file_name().display(),
75 path.display()
76 ));
77 false
78 }
79 },
80 Err(cause) => {
81 errors.push(format!(
82 "Error when reading entry inside directory {0}: {1}",
83 path.display(),
84 cause,
85 ));
86 false
87 }
88 });
89
90 if contains_vcs_info {
91 return Ok(path.to_path_buf());
92 }
93
94 if let Some(parent) = path.parent() {
95 path = parent;
96 } else {
97 let mut message = format!(
98 "No parent of {0} contains version control information. Are you sure you're in a repository?",
99 start_path.display(),
100 );
101
102 if errors.is_empty().not() {
104 message.push_str("\n\nThe following errors have occurred while looking for version control information:\n");
105 for error in errors {
106 message.push_str(&format!(" - {error}\n"));
107 }
108 }
109
110 return Err(io::Error::other(message));
111 }
112 }
113}