1use std::{
2 ffi::OsStr,
3 path::{Path, PathBuf},
4 sync::Arc,
5};
6
7use gix::repository::Kind;
8
9use crate::config::Config;
10
11pub trait Walker {
12 fn paths_from_walk(&self) -> Vec<String>;
13}
14
15impl Walker for Config {
16 fn paths_from_walk(&self) -> Vec<String> {
17 let mut result = self.search.single.clone();
18 let exclude_paths = Arc::new(self.exclude_path.clone());
19
20 for workspace in &self.search.workspace {
21 let exclude = exclude_paths.clone();
22
23 let walk = jwalk::WalkDirGeneric::<((), Option<Kind>)>::new(Path::new(workspace))
24 .follow_links(false)
25 .skip_hidden(false);
26
27 let additions = walk
28 .process_read_dir(move |_depth, _path, _read_dir_state, siblings| {
29 siblings.retain(|entry_result| {
30 entry_result
31 .as_ref()
32 .map(|entry| {
33 entry
34 .path()
35 .components()
36 .last()
37 .expect("always has last component")
38 .as_os_str()
39 .to_str()
40 .map(|name| !exclude.iter().any(|e| *e == name))
41 .unwrap_or(false)
42 })
43 .unwrap_or(false)
44 });
45
46 let mut found_any_repo = false;
47 let mut found_bare_repo = false;
48 for entry in siblings.iter_mut().flatten() {
49 let path = entry.path();
50 if let Some(kind) = is_repository(&path) {
51 let is_bare = kind.is_bare();
52 entry.client_state = kind.into();
53 entry.read_children_path = None;
54
55 found_any_repo = true;
56 found_bare_repo = is_bare;
57 }
58 }
59 if found_any_repo && !found_bare_repo {
62 siblings.retain(|e| {
63 e.as_ref()
64 .map(|e| e.client_state.is_some())
65 .unwrap_or(false)
66 });
67 }
68 })
69 .into_iter()
70 .filter_map(Result::ok)
71 .filter_map(|mut e| {
72 e.client_state
73 .take()
74 .map(|state| into_workdir(e.path(), &state).display().to_string())
75 });
76
77 result.extend(additions);
78 }
79
80 result
81 }
82}
83
84fn is_repository(path: &Path) -> Option<Kind> {
85 if path.file_name() != Some(OsStr::new(".git")) && path.extension() != Some(OsStr::new("git")) {
87 return None;
88 }
89
90 if path.is_dir() {
91 if path.join("HEAD").is_file() && path.join("config").is_file() {
92 gix::discover::is_git(path).ok().map(Into::into)
93 } else {
94 None
95 }
96 } else {
97 Some(Kind::WorkTree { is_linked: true })
99 }
100}
101
102fn into_workdir(git_dir: PathBuf, kind: &Kind) -> PathBuf {
103 if matches!(kind, Kind::Bare) || gix::discover::is_bare(&git_dir) {
104 git_dir
105 } else {
106 git_dir
107 .parent()
108 .expect("git is never in the root")
109 .to_owned()
110 }
111}