git_repos/
lib.rs

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