1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
//! This module contains target generation logic required for generating
//! [`RepositoryViews`](crate::repository_view::RepositoryView).
use log::{debug, error, warn};
use rayon::prelude::*;
use std::fs::DirEntry;
use std::path::PathBuf;
use std::{fs, io};
/// An unprocessed target that needs to be disassembled before consumption.
type UnprocessedTarget = io::Result<MaybeTarget>;
/// A unit struct used to centralizing target collection method(s).
pub(crate) struct TargetCollector;
impl TargetCollector {
/// Generate targets for a given [`PathBuf`] based on its children (recursively). We use
/// recursion paired with [`rayon`] since we prioritize speed over memory use.
pub(crate) fn run(path: PathBuf) -> io::Result<Vec<PathBuf>> {
let entries: Vec<DirEntry> = match fs::read_dir(&path) {
Ok(read_dir) => read_dir.filter_map(|r| r.ok()).collect(),
Err(e) => {
match e.kind() {
io::ErrorKind::PermissionDenied => warn!("{}: {}", e, &path.display()),
_ => error!("{}: {}", e, &path.display()),
}
return Ok(Vec::with_capacity(0));
}
};
let unprocessed = entries
.par_iter()
.map(Self::determine_target)
.collect::<Vec<UnprocessedTarget>>();
let mut results = Vec::new();
for entry in unprocessed {
let entry = entry?;
if let MaybeTarget::Multiple(targets) = entry {
results.extend(targets);
} else if let MaybeTarget::Single(target) = entry {
results.push(target);
}
}
Ok(results)
}
/// Ensure the entry is a directory and is not hidden. Then, check if a ".git" sub directory
/// exists, which will indicate if the entry is a repository. If the directory is not a Git
/// repository, then we will recursively call [`Self::run()`].
fn determine_target(entry: &DirEntry) -> io::Result<MaybeTarget> {
match entry.file_type()?.is_dir()
&& !entry
.file_name()
.to_str()
.map(|file_name| file_name.starts_with('.'))
.unwrap_or(false)
{
true => {
let path = entry.path();
let git_sub_directory = path.join(".git");
match git_sub_directory.exists() && git_sub_directory.is_dir() {
true => {
debug!("found target: {:?}", &path);
Ok(MaybeTarget::Single(path))
}
false => Ok(MaybeTarget::Multiple(Self::run(path)?)),
}
}
false => Ok(MaybeTarget::None),
}
}
}
/// An enum that contains 0 to N targets based on the variant.
enum MaybeTarget {
/// Contains multiple targets from recursive call(s) of [`TargetCollector::run()`].
Multiple(Vec<PathBuf>),
/// Contains a single target.
Single(PathBuf),
/// Does not contain a target.
None,
}
|