tuxmux/
walker.rs

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                    // Only return paths which are repositories are further participating in the traversal
60                    // Don't let bare repositories cause siblings to be pruned.
61                    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    // Can be git dir or worktree checkout (file)
86    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        // git files are always worktrees
98        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}