grm/
lib.rs

1#![forbid(unsafe_code)]
2
3use std::path::Path;
4
5pub mod auth;
6pub mod config;
7pub mod output;
8pub mod path;
9pub mod provider;
10pub mod repo;
11pub mod table;
12pub mod tree;
13pub mod worktree;
14
15/// Find all git repositories under root, recursively
16///
17/// The bool in the return value specifies whether there is a repository
18/// in root itself.
19#[allow(clippy::type_complexity)]
20fn find_repos(
21    root: &Path,
22    exclusion_pattern: Option<&str>,
23) -> Result<Option<(Vec<repo::Repo>, Vec<String>, bool)>, String> {
24    let mut repos: Vec<repo::Repo> = Vec::new();
25    let mut repo_in_root = false;
26    let mut warnings = Vec::new();
27
28    let exlusion_regex: regex::Regex = regex::Regex::new(exclusion_pattern.unwrap_or(r"^$"))
29        .map_err(|e| format!("invalid regex: {e}"))?;
30    for path in tree::find_repo_paths(root)? {
31        if exclusion_pattern.is_some() && exlusion_regex.is_match(&path::path_as_string(&path)) {
32            warnings.push(format!("[skipped] {}", &path::path_as_string(&path)));
33            continue;
34        }
35
36        let is_worktree = repo::RepoHandle::detect_worktree(&path);
37        if path == root {
38            repo_in_root = true;
39        }
40
41        match repo::RepoHandle::open(&path, is_worktree) {
42            Err(error) => {
43                warnings.push(format!(
44                    "Error opening repo {}{}: {}",
45                    path.display(),
46                    if is_worktree { " as worktree" } else { "" },
47                    error
48                ));
49                continue;
50            }
51            Ok(repo) => {
52                let remotes = match repo.remotes() {
53                    Ok(remote) => remote,
54                    Err(error) => {
55                        warnings.push(format!(
56                            "{}: Error getting remotes: {}",
57                            &path::path_as_string(&path),
58                            error
59                        ));
60                        continue;
61                    }
62                };
63
64                let mut results: Vec<repo::Remote> = Vec::new();
65                for remote_name in remotes {
66                    match repo.find_remote(&remote_name)? {
67                        Some(remote) => {
68                            let name = remote.name();
69                            let url = remote.url();
70                            let remote_type = match repo::detect_remote_type(&url) {
71                                Ok(t) => t,
72                                Err(e) => {
73                                    warnings.push(format!(
74                                        "{}: Could not handle URL {}. Reason: {}",
75                                        &path::path_as_string(&path),
76                                        &url,
77                                        e
78                                    ));
79                                    continue;
80                                }
81                            };
82
83                            results.push(repo::Remote {
84                                name,
85                                url,
86                                remote_type,
87                            });
88                        }
89                        None => {
90                            warnings.push(format!(
91                                "{}: Remote {} not found",
92                                &path::path_as_string(&path),
93                                remote_name
94                            ));
95                            continue;
96                        }
97                    };
98                }
99                let remotes = results;
100
101                let (namespace, name) = if path == root {
102                    (
103                        None,
104                        match &root.parent() {
105                            Some(parent) => {
106                                path::path_as_string(path.strip_prefix(parent).unwrap())
107                            }
108                            None => {
109                                warnings.push(String::from("Getting name of the search root failed. Do you have a git repository in \"/\"?"));
110                                continue;
111                            }
112                        },
113                    )
114                } else {
115                    let name = path.strip_prefix(root).unwrap();
116                    let namespace = name.parent().unwrap();
117                    (
118                        if namespace == Path::new("") {
119                            None
120                        } else {
121                            Some(path::path_as_string(namespace).to_string())
122                        },
123                        path::path_as_string(name),
124                    )
125                };
126
127                repos.push(repo::Repo {
128                    name,
129                    namespace,
130                    remotes: Some(remotes),
131                    worktree_setup: is_worktree,
132                });
133            }
134        }
135    }
136    Ok(Some((repos, warnings, repo_in_root)))
137}
138
139pub fn find_in_tree(
140    path: &Path,
141    exclusion_pattern: Option<&str>,
142) -> Result<(tree::Tree, Vec<String>), String> {
143    let mut warnings = Vec::new();
144
145    let (repos, repo_in_root): (Vec<repo::Repo>, bool) = match find_repos(path, exclusion_pattern)?
146    {
147        Some((vec, mut repo_warnings, repo_in_root)) => {
148            warnings.append(&mut repo_warnings);
149            (vec, repo_in_root)
150        }
151        None => (Vec::new(), false),
152    };
153
154    let mut root = path.to_path_buf();
155    if repo_in_root {
156        root = match root.parent() {
157            Some(root) => root.to_path_buf(),
158            None => {
159                return Err(String::from(
160                    "Cannot detect root directory. Are you working in /?",
161                ));
162            }
163        }
164    }
165
166    Ok((
167        tree::Tree {
168            root: root.into_os_string().into_string().unwrap(),
169            repos,
170        },
171        warnings,
172    ))
173}