git_tracker/
fs.rs

1use std::{
2    collections::HashSet,
3    os::unix::ffi::OsStrExt,
4    path::{Path, PathBuf},
5};
6
7#[tracing::instrument]
8pub fn find_dirs(
9    root: &Path,
10    target_name: &str,
11    follow: bool,
12    ignore: &HashSet<PathBuf>,
13) -> impl Iterator<Item = PathBuf> {
14    let root = root.to_path_buf();
15    Dirs {
16        ignore: ignore.to_owned(),
17        follow,
18        target_name: target_name.to_string(),
19        frontier: vec![root],
20    }
21}
22
23#[derive(Debug)]
24struct Dirs {
25    target_name: String,
26    follow: bool,
27    ignore: HashSet<PathBuf>,
28    frontier: Vec<PathBuf>,
29}
30
31impl Iterator for Dirs {
32    type Item = PathBuf;
33
34    fn next(&mut self) -> Option<PathBuf> {
35        // XXX Walking the fs tree with tokio is about 5x slower!
36        // use tokio::fs;
37        use std::fs;
38
39        while let Some(path) = self.frontier.pop() {
40            if self.ignore.contains(&path) {
41                continue;
42            }
43            if !&path.try_exists().is_ok_and(|exists| exists) {
44                continue;
45            }
46            match fs::symlink_metadata(&path) {
47                Ok(meta) if meta.is_symlink() => {
48                    if !self.follow {
49                        continue;
50                    }
51                    match fs::read_link(&path) {
52                        Ok(path1) => {
53                            self.frontier.push(path1);
54                        }
55                        Err(error) => {
56                            tracing::error!(
57                                ?path,
58                                ?error,
59                                "Failed to read link."
60                            );
61                        }
62                    }
63                }
64                Ok(meta) if meta.is_dir() => {
65                    if path.file_name().is_some_and(|name| {
66                        name.as_bytes() == self.target_name.as_bytes()
67                    }) {
68                        return Some(path);
69                    }
70                    match fs::read_dir(&path) {
71                        Err(error) => {
72                            tracing::error!(
73                                ?path,
74                                ?error,
75                                "Failed to read directory",
76                            );
77                        }
78                        Ok(entries) => {
79                            for entry_result in entries {
80                                match entry_result {
81                                    Ok(entry) => {
82                                        self.frontier.push(entry.path());
83                                    }
84                                    Err(error) => {
85                                        tracing::error!(
86                                            from = ?path, ?error,
87                                            "Failed to read an entry",
88                                        );
89                                    }
90                                }
91                            }
92                        }
93                    }
94                }
95                Ok(_) => {}
96                Err(error) => {
97                    tracing::error!(
98                        from = ?path, ?error,
99                        "Failed to read metadata",
100                    );
101                }
102            }
103        }
104        None
105    }
106}