summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Gerace <nickagerace@gmail.com>2022-04-23 13:13:38 -0400
committerNick Gerace <nickagerace@gmail.com>2022-04-23 13:47:02 -0400
commit6d4aa0bf924a030b10a6034658407b493d9cc762 (patch)
treede506df576ac225adfbc216cb1e0ce0491074b32
parent4516907acec09ace341927c89d93d1f23c662e92 (diff)
downloadgfold-6d4aa0bf924a030b10a6034658407b493d9cc762.zip
Bolster loose bench to provide better results
Loose bench: - Bolster loose bench to calculate more runs with averages - Alternate order at the halfway mark of total runs for loose bench - Display run averages and global duration for loose bench - Add TODO for loose bench to track average deviation (i.e. how consistently does gfold perform across runs?) - Add module comment for loose bench explaining its purpose Misc: - Add TargetOption enum for processing target entries - Name reports and target varaibles with more specificity - Add fix commands to DEVELOPING - Misc updates to Cargo.lock - Update to 4.0.0-rc.3 Result: - Only use "anyhow::Result" when there is more than one error type Bug fix: - Add current dir to scripts to ensure gfold is being built Signed-off-by: Nick Gerace <nickagerace@gmail.com>
-rw-r--r--CHANGELOG.md5
-rw-r--r--Cargo.lock23
-rw-r--r--Cargo.toml2
-rw-r--r--docs/DEVELOPING.md8
-rw-r--r--scripts/bench-loosely/src/main.rs148
-rw-r--r--scripts/size/src/main.rs1
-rw-r--r--src/cli.rs8
-rw-r--r--src/config.rs11
-rw-r--r--src/display.rs17
-rw-r--r--src/main.rs3
-rw-r--r--src/report.rs39
-rw-r--r--src/report/target.rs44
-rw-r--r--src/run.rs3
13 files changed, 208 insertions, 104 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b279a2..2aada48 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -42,6 +42,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Git path option for CLI and config file
- `git` subcommand usage
+### Notes
+
+- Substantial performance gains should be noticeable in certain scenarios
+- Using `RUST_LOG` and `RUST_BACKTRACE` should be more helpful when debugging unexpected output, performance or suspected bugs
+
### [3.0.0] - 2022-01-06
### Added
diff --git a/Cargo.lock b/Cargo.lock
index baa57f5..0ea7340 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "anyhow"
-version = "1.0.56"
+version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
+checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
dependencies = [
"backtrace",
]
@@ -74,9 +74,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
-version = "0.3.64"
+version = "0.3.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f"
+checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61"
dependencies = [
"addr2line",
"cc",
@@ -213,7 +213,7 @@ dependencies = [
[[package]]
name = "gfold"
-version = "4.0.0-rc.2"
+version = "4.0.0-rc.3"
dependencies = [
"anyhow",
"argh",
@@ -306,9 +306,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.123"
+version = "0.2.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd"
+checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
[[package]]
name = "libgit2-sys"
@@ -366,12 +366,11 @@ dependencies = [
[[package]]
name = "miniz_oxide"
-version = "0.4.4"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
+checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
dependencies = [
"adler",
- "autocfg",
]
[[package]]
@@ -386,9 +385,9 @@ dependencies = [
[[package]]
name = "object"
-version = "0.27.1"
+version = "0.28.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9"
+checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456"
dependencies = [
"memchr",
]
diff --git a/Cargo.toml b/Cargo.toml
index 4db83ac..c3f03fd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,7 +10,7 @@ readme = "README.md"
repository = "https://github.com/nickgerace/gfold/"
edition = "2021"
-version = "4.0.0-rc.2"
+version = "4.0.0-rc.3"
[dependencies]
anyhow = { version = "1", features = ["backtrace"] }
diff --git a/docs/DEVELOPING.md b/docs/DEVELOPING.md
index 23b6607..51113ac 100644
--- a/docs/DEVELOPING.md
+++ b/docs/DEVELOPING.md
@@ -29,6 +29,13 @@ cargo build --all-targets
> cargo nextest run
> ```
+If you'd like to mass "fix" everything, you should commit/save existing work and execute the following:
+
+```shell
+cargo fix --all-targets --all-features --allow-dirty --allow-staged
+cargo clippy --fix --all-features --all-targets --allow-dirty --allow-staged
+```
+
## Performance Checks
Navigate to the [README in the `scripts` directory](../scripts/README.md) for more information on
@@ -38,7 +45,6 @@ how to run performance checks.
The following checks are optional and should be run occasionally.
-
```shell
# This command requires a nightly toolchain to be installed.
cargo +nightly udeps
diff --git a/scripts/bench-loosely/src/main.rs b/scripts/bench-loosely/src/main.rs
index 37da22d..6613fb5 100644
--- a/scripts/bench-loosely/src/main.rs
+++ b/scripts/bench-loosely/src/main.rs
@@ -1,8 +1,19 @@
+//! This is the loose bench for gfold. Why use a loose bench over a precise one? Most of gfold
+//! development happens with a mobile processor, which is prone to performance fluctuations over
+//! background I/O operations, battery life and temperature. Moreover, the "real world" use case of
+//! gfold is via CLI and not a library. Thus, this loose bench executes gfold as a CLI similarly
+//! to real world use. This benchmark is not precise and is designed to give a high level overview.
+
+// TODO: add consistency deviation. We should see how far each result strays from the average. We
+// want gfold to perform consistently as well as quickly.
+
use std::path::Path;
use std::process::Command;
use std::time::{Duration, Instant};
fn main() {
+ let global_instant = Instant::now();
+
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let repo = manifest_dir
.parent()
@@ -14,6 +25,7 @@ fn main() {
let output = Command::new("cargo")
.arg("build")
.arg("--release")
+ .current_dir(repo)
.output()
.expect("could not execute command");
if !output.status.success() {
@@ -21,55 +33,127 @@ fn main() {
}
let binary = repo.join("target").join("release").join("gfold");
- let home = dirs::home_dir().expect("could not find home directory");
- let installed = home.join(".cargo").join("bin").join("gfold");
- let runs = 4;
+ let home_path = dirs::home_dir().expect("could not find home directory");
+ let installed = home_path.join(".cargo").join("bin").join("gfold");
+ let home = home_path.to_str().expect("could not convert to str");
+
+ // Add "1" to total runs to ensure caching does not skew results. We need one
+ // "warm up" run.
+ let runs = 41;
// Loosely bench using the home directory as the target.
+ let mut home_new_durations = Vec::new();
+ let mut home_old_durations = Vec::new();
+ let mut first = true;
for run in 0..runs {
- println!(
- "group {} of {} in {}",
- run + 1,
- runs,
- home.to_str().expect("could not convert to str")
- );
- loose_bench(&binary, &installed, &home);
+ println!("group {} of {} in {}", run + 1, runs, home);
+
+ // Alternate at the halfway point. Though, it is doubtful that this would actually change anything.
+ // Only calculate the average after the first run to avoid caching skewing results.
+ if run > runs / 2 {
+ let (old_duration, new_duration) = loose_bench(&installed, &binary, &home_path);
+ if first {
+ first = false;
+ } else {
+ home_new_durations.push(new_duration);
+ home_old_durations.push(old_duration);
+ }
+ } else {
+ let (new_duration, old_duration) = loose_bench(&binary, &installed, &home_path);
+ if first {
+ first = false;
+ } else {
+ home_new_durations.push(new_duration);
+ home_old_durations.push(old_duration);
+ }
+ }
}
// Loosely bench with the parent directory of the repository as the target.
let parent_of_repo = repo.parent().expect("could not get parent");
+ let parent = parent_of_repo.to_str().expect("could not convert to str");
+ let mut parent_new_durations = Vec::new();
+ let mut parent_old_durations = Vec::new();
+ let mut first = true;
for run in 0..runs {
- println!(
- "group {} of {} in {}",
- run + 1,
- runs,
- parent_of_repo.to_str().expect("could not convert to str")
- );
- loose_bench(&binary, &installed, parent_of_repo);
+ println!("group {} of {} in {}", run + 1, runs, parent);
+
+ // Alternate at the halfway point. Though, it is doubtful that this would actually change anything.
+ // Only calculate the average after the first run to avoid caching skewing results.
+ if run > runs / 2 {
+ let (old_duration, new_duration) = loose_bench(&installed, &binary, parent_of_repo);
+ if first {
+ first = false;
+ } else {
+ parent_new_durations.push(new_duration);
+ parent_old_durations.push(old_duration);
+ }
+ } else {
+ let (new_duration, old_duration) = loose_bench(&binary, &installed, parent_of_repo);
+ if first {
+ first = false;
+ } else {
+ parent_new_durations.push(new_duration);
+ parent_old_durations.push(old_duration);
+ }
+ }
}
+
+ // Print the averages. Start with a multi-platform empty newline print.
+ println!();
+ println!(
+ "Average: {:?} - {} - {}",
+ average_duration(&home_new_durations),
+ home,
+ &binary.display(),
+ );
+ println!(
+ "Average: {:?} - {} - {}",
+ average_duration(&home_old_durations),
+ home,
+ &installed.display(),
+ );
+ println!(
+ "Average: {:?} - {} - {}",
+ average_duration(&parent_new_durations),
+ parent,
+ &binary.display(),
+ );
+ println!(
+ "Average: {:?} - {} - {}",
+ average_duration(&parent_old_durations),
+ parent,
+ &installed.display(),
+ );
+
+ // Display the global duration for the entire script. Start with a multi-platform empty newline
+ // print.
+ println!();
+ println!("Total duration: {:?}", global_instant.elapsed());
}
-fn loose_bench(new: &Path, old: &Path, target: &Path) {
- let new_duration = execute(new, target);
- let old_duration = execute(old, target);
- let (new_text, old_text) = match new_duration {
- new_duration if new_duration > old_duration => ("LOST", "WON "),
- new_duration if new_duration < old_duration => ("WON ", "LOST"),
+fn loose_bench(first: &Path, second: &Path, target: &Path) -> (Duration, Duration) {
+ let first_duration = execute(first, target);
+ let second_duration = execute(second, target);
+ let (first_text, second_text) = match first_duration {
+ first_duration if first_duration > second_duration => ("LOST", "WON "),
+ first_duration if first_duration < second_duration => ("WON ", "LOST"),
_ => ("TIE ", "TIE "),
};
println!(
" {} @ {:?} - {}",
- new_text,
- new_duration,
- new.to_str().expect("could not convert to str"),
+ first_text,
+ first_duration,
+ first.to_str().expect("could not convert to str"),
);
println!(
" {} @ {:?} - {}",
- old_text,
- old_duration,
- old.to_str().expect("could not convert to str"),
+ second_text,
+ second_duration,
+ second.to_str().expect("could not convert to str"),
);
+ (first_duration, second_duration)
}
fn execute(binary: &Path, target: &Path) -> Duration {
@@ -88,3 +172,9 @@ fn execute(binary: &Path, target: &Path) -> Duration {
duration
}
+
+fn average_duration(durations: &[Duration]) -> Duration {
+ let sum = durations.iter().sum::<Duration>();
+ let count = durations.len() as f32;
+ sum.div_f32(count)
+}
diff --git a/scripts/size/src/main.rs b/scripts/size/src/main.rs
index 2d5f5e9..a92309a 100644
--- a/scripts/size/src/main.rs
+++ b/scripts/size/src/main.rs
@@ -16,6 +16,7 @@ fn main() {
let output = Command::new("cargo")
.arg("build")
.arg("--release")
+ .current_dir(repo)
.output()
.expect("could not execute command");
if !output.status.success() {
diff --git a/src/cli.rs b/src/cli.rs
index ed6c8fc..89518c2 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -4,7 +4,6 @@
use crate::config::{ColorMode, Config, DisplayMode};
use crate::error::Error;
use crate::run;
-use anyhow::Result;
use argh::FromArgs;
use log::debug;
use std::env;
@@ -65,7 +64,7 @@ struct Args {
/// Parse CLI arguments, initialize the logger, merge configurations as needed, and call
/// [`run::run()`] with the resulting [`Config`].
-pub fn parse_and_run() -> Result<()> {
+pub fn parse_and_run() -> anyhow::Result<()> {
// First and foremost, get logging up and running. We want logs as quickly as possible for
// debugging by setting "RUST_LOG".
let args: Args = argh::from_env();
@@ -111,7 +110,8 @@ pub fn parse_and_run() -> Result<()> {
debug!("finalized config options");
match &args.dry_run {
- true => config.print(),
- false => run::run(&config),
+ true => config.print()?,
+ false => run::run(&config)?,
}
+ Ok(())
}
diff --git a/src/config.rs b/src/config.rs
index db3537a..bb4ac57 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,7 +1,6 @@
//! This module contains the config specification and functionality for creating a config.
use crate::error::Error;
-use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::{env, fs, io};
@@ -67,7 +66,7 @@ impl Config {
/// This method tries to deserialize the config file (empty, non-existent, partial or complete)
/// and uses [`EntryConfig`] as an intermediary struct. This is the primary method used when
/// creating a config.
- pub fn try_config() -> Result<Self> {
+ pub fn try_config() -> anyhow::Result<Self> {
// Within this method, we check if the config file is empty before deserializing it. Users
// should be able to proceed with empty config files. If empty or not found, then we fall
// back to the "EntryConfig" default before conversion.
@@ -83,22 +82,22 @@ impl Config {
_ => return Err(e.into()),
},
};
- Self::from_entry_config(&entry_config)
+ Ok(Self::from_entry_config(&entry_config)?)
}
/// This method does not look for the config file and uses [`EntryConfig`]'s defaults instead.
/// It is best for testing use and when the user wishes to skip config file lookup.
- pub fn new() -> Result<Self> {
+ pub fn new() -> io::Result<Self> {
Self::from_entry_config(&EntryConfig::default())
}
/// This method prints the full config (merged with config file, as needed) as valid, pretty TOML.
- pub fn print(self) -> Result<()> {
+ pub fn print(self) -> Result<(), toml::ser::Error> {
print!("{}", toml::to_string_pretty(&self)?);
Ok(())
}
- fn from_entry_config(entry_config: &EntryConfig) -> Result<Self> {
+ fn from_entry_config(entry_config: &EntryConfig) -> io::Result<Self> {
Ok(Config {
path: match &entry_config.path {
Some(s) => s.clone(),
diff --git a/src/display.rs b/src/display.rs
index 6440021..98f320b 100644
--- a/src/display.rs
+++ b/src/display.rs
@@ -4,9 +4,9 @@ use crate::config::{ColorMode, DisplayMode};
use crate::display::color::ColorHarness;
use crate::error::Error;
use crate::report::LabeledReports;
-use anyhow::Result;
use log::debug;
use log::warn;
+use std::io;
use std::path::Path;
mod color;
@@ -19,16 +19,17 @@ pub fn display(
display_mode: &DisplayMode,
reports: &LabeledReports,
color_mode: &ColorMode,
-) -> Result<()> {
+) -> anyhow::Result<()> {
match display_mode {
- DisplayMode::Standard => standard(reports, color_mode),
- DisplayMode::Json => json(reports),
- DisplayMode::Classic => classic(reports, color_mode),
+ DisplayMode::Standard => standard(reports, color_mode)?,
+ DisplayMode::Json => json(reports)?,
+ DisplayMode::Classic => classic(reports, color_mode)?,
}
+ Ok(())
}
/// Display [`LabeledReports`] to `stdout` in the standard (default) format.
-fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> Result<()> {
+fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> anyhow::Result<()> {
debug!("detected standard display mode");
let mut all_reports = Vec::new();
for grouped_report in reports {
@@ -72,7 +73,7 @@ fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> Result<()> {
}
/// Display [`LabeledReports`] to `stdout` in JSON format.
-fn json(reports: &LabeledReports) -> Result<()> {
+fn json(reports: &LabeledReports) -> serde_json::error::Result<()> {
debug!("detected json display mode");
let mut all_reports = Vec::new();
for grouped_report in reports {
@@ -85,7 +86,7 @@ fn json(reports: &LabeledReports) -> Result<()> {
}
/// Display [`LabeledReports`] to `stdout` in the classic format.
-fn classic(reports: &LabeledReports, color_mode: &ColorMode) -> Result<()> {
+fn classic(reports: &LabeledReports, color_mode: &ColorMode) -> io::Result<()> {
debug!("detected classic display mode");
let color_harness = ColorHarness::new(color_mode);
diff --git a/src/main.rs b/src/main.rs
index f1c7c72..dd48e98 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,7 +2,6 @@
//! track of multiple Git repositories. The source code uses private modules rather than leveraging
//! a library via `lib.rs`.
-use anyhow::Result;
use env_logger::Builder;
use log::debug;
use log::LevelFilter;
@@ -18,7 +17,7 @@ mod status;
/// Initializes the logger based on the debug flag and `RUST_LOG` environment variable and calls
/// [`cli::parse_and_run()`] to generate a [`config::Config`] and eventually call [`run::run()`].
-fn main() -> Result<()> {
+fn main() -> anyhow::Result<()> {
match env::var("RUST_LOG").is_err() {
true => Builder::new().filter_level(LevelFilter::Off).init(),
false => env_logger::init(),
diff --git a/src/report.rs b/src/report.rs
index 70a2799..469ff62 100644
--- a/src/report.rs
+++ b/src/report.rs
@@ -3,7 +3,6 @@
use crate::config::DisplayMode;
use crate::error::Error;
use crate::status::Status;
-use anyhow::Result;
use git2::{ErrorCode, Reference, Remote, Repository, StatusOptions};
use log::{debug, trace};
use rayon::prelude::*;
@@ -47,23 +46,21 @@ impl Report {
status: &Status,
url: Option<String>,
email: Option<String>,
- ) -> Result<Self> {
+ ) -> Result<Self, crate::error::Error> {
Ok(Self {
name: match path.file_name() {
Some(s) => match s.to_str() {
Some(s) => s.to_string(),
- None => {
- return Err(Error::FileNameToStrConversionFailure(path.to_path_buf()).into())
- }
+ None => return Err(Error::FileNameToStrConversionFailure(path.to_path_buf())),
},
- None => return Err(Error::FileNameNotFound(path.to_path_buf()).into()),
+ None => return Err(Error::FileNameNotFound(path.to_path_buf())),
},
branch: (*branch).into(),
status: *status,
parent: match path.parent() {
Some(s) => match s.to_str() {
Some(s) => Some(s.to_string()),
- None => return Err(Error::PathToStrConversionFailure(s.to_path_buf()).into()),
+ None => return Err(Error::PathToStrConversionFailure(s.to_path_buf())),
},
None => None,
},
@@ -75,24 +72,26 @@ impl Report {
/// Generate [`LabeledReports`] for a given path and its children. The [`DisplayMode`] is required
/// because any two display modes can require differing amounts of data to be collected.
-pub fn generate_reports(path: &Path, display_mode: &DisplayMode) -> Result<LabeledReports> {
+pub fn generate_reports(path: &Path, display_mode: &DisplayMode) -> anyhow::Result<LabeledReports> {
let include_email = match display_mode {
DisplayMode::Standard | DisplayMode::Json => true,
DisplayMode::Classic => false,
};
- let unprocessed = target::recursive_target_gen(path)?
+ let unprocessed = target::generate_targets(path.to_path_buf())?
.par_iter()
.map(|path| generate_report(path, include_email))
- .collect::<Vec<Result<Report>>>();
+ .collect::<Vec<anyhow::Result<Report>>>();
let mut processed = LabeledReports::new();
for wrapped_report in unprocessed {
match wrapped_report {
Ok(report) => {
- if let Some(mut v) = processed.insert(report.parent.clone(), vec![report.clone()]) {
- v.push(report.clone());
- processed.insert(report.parent, v);
+ if let Some(mut reports) =
+ processed.insert(report.parent.clone(), vec![report.clone()])
+ {
+ reports.push(report.clone());
+ processed.insert(report.parent, reports);
}
}
Err(e) => return Err(e),
@@ -102,7 +101,7 @@ pub fn generate_reports(path: &Path, display_mode: &DisplayMode) -> Result<Label
}
/// Generates a report with a given path.
-fn generate_report(repo_path: &Path, include_email: bool) -> Result<Report> {
+fn generate_report(repo_path: &Path, include_email: bool) -> anyhow::Result<Report> {
debug!(
"attemping to generate report for repository at path: {:?}",
repo_path
@@ -164,11 +163,15 @@ fn generate_report(repo_path: &Path, include_email: bool) -> Result<Report> {
"finalized report collection for repository at path: {:?}",
repo_path
);
- Report::new(repo_path, branch, &status, url, email)
+ Ok(Report::new(repo_path, branch, &status, url, email)?)
}
/// Checks if local commit(s) on the current branch have not yet been pushed to the remote.
-fn is_unpushed(repo: &Repository, head: &Reference, remote_name: &str) -> Result<bool> {
+fn is_unpushed(
+ repo: &Repository,
+ head: &Reference,
+ remote_name: &str,
+) -> Result<bool, git2::Error> {
let local_head = head.peel_to_commit()?;
let remote = format!(
"{}/{}",
@@ -227,7 +230,9 @@ fn get_email(repository: &Repository) -> Option<String> {
None
}
-fn choose_remote_greedily(repository: &Repository) -> Result<(Option<Remote>, Option<String>)> {
+fn choose_remote_greedily(
+ repository: &Repository,
+) -> Result<(Option<Remote>, Option<String>), git2::Error> {
let remotes = repository.remotes()?;
Ok(match remotes.get(0) {
Some(remote_name) => (
diff --git a/src/report/target.rs b/src/report/target.rs
index 3401101..abc836a 100644
--- a/src/report/target.rs
+++ b/src/report/target.rs
@@ -3,16 +3,19 @@
use log::{debug, error, warn};
use rayon::prelude::*;
use std::fs::DirEntry;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
use std::{fs, io};
-/// This type represents bundled target Git directories that were generated from a given [`DirEntry`].
-type Targets = io::Result<Option<Vec<PathBuf>>>;
+enum TargetOption {
+ Multiple(Vec<PathBuf>),
+ Single(PathBuf),
+ None,
+}
/// 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. Finally, generate targets based on that
/// repository.
-fn process_entry(entry: &DirEntry) -> Targets {
+fn process_entry(entry: &DirEntry) -> io::Result<TargetOption> {
match entry.file_type()?.is_dir()
&& !entry
.file_name()
@@ -26,17 +29,18 @@ fn process_entry(entry: &DirEntry) -> Targets {
match git_sub_directory.exists() && git_sub_directory.is_dir() {
true => {
debug!("found target: {:?}", &path);
- Ok(Some(vec![path]))
+ Ok(TargetOption::Single(path))
}
- false => Ok(Some(recursive_target_gen(&path)?)),
+ false => Ok(TargetOption::Multiple(generate_targets(path)?)),
}
}
- false => Ok(None),
+ false => Ok(TargetOption::None),
}
}
-/// Recursive function for generating targets in a child directory.
-pub fn recursive_target_gen(path: &Path) -> io::Result<Vec<PathBuf>> {
+/// Generate targets from a given [`PathBuf`] based on its children (recursively).
+/// We use recursion paired with [`rayon`] since we prioritize speed over memory use.
+pub fn generate_targets(path: PathBuf) -> io::Result<Vec<PathBuf>> {
let entries: Vec<DirEntry> = match fs::read_dir(&path) {
Ok(o) => o.filter_map(|r| r.ok()).collect(),
Err(e) if e.kind() == io::ErrorKind::PermissionDenied => {
@@ -49,22 +53,18 @@ pub fn recursive_target_gen(path: &Path) -> io::Result<Vec<PathBuf>> {
}
};
- let targets = entries
+ let processed = entries
.par_iter()
.map(process_entry)
- .collect::<Vec<Targets>>();
+ .collect::<Vec<io::Result<TargetOption>>>();
- let mut results = vec![];
- for target in targets {
- match target {
- Ok(v) => {
- if let Some(mut v) = v {
- if !v.is_empty() {
- results.append(&mut v);
- }
- }
- }
- Err(e) => return Err(e),
+ let mut results = Vec::new();
+ for entry in processed {
+ let entry = entry?;
+ if let TargetOption::Multiple(targets) = entry {
+ results.extend(targets);
+ } else if let TargetOption::Single(target) = entry {
+ results.push(target);
}
}
Ok(results)
diff --git a/src/run.rs b/src/run.rs
index 48f0a4d..a67b9cf 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -2,12 +2,11 @@
use crate::config::Config;
use crate::{display, report};
-use anyhow::Result;
/// This function is the primary entrypoint for the crate. It takes a given config and performs
/// the end-to-end workflow using it. At this point, all CLI and config file options should be
/// set, merged, ignored, etc.
-pub fn run(config: &Config) -> Result<()> {
+pub fn run(config: &Config) -> anyhow::Result<()> {
let reports = report::generate_reports(&config.path, &config.display_mode)?;
display::display(&config.display_mode, &reports, &config.color_mode)
}