Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "codeowners"
version = "0.2.4"
version = "0.2.5"
edition = "2024"

[profile.release]
Expand All @@ -14,7 +14,7 @@ clap = { version = "4.5.20", features = ["derive"] }
clap_derive = "4.5.18"
error-stack = "0.5.0"
enum_dispatch = "0.3.13"
fast-glob = "0.4.0"
fast-glob = "1.0.0"
glob = "0.3.2"
ignore = "0.4.23"
itertools = "0.14.0"
Expand Down
36 changes: 33 additions & 3 deletions src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,7 @@ impl Project {
}

pub fn relative_path<'a>(&'a self, absolute_path: &'a Path) -> &'a Path {
absolute_path
.strip_prefix(&self.base_path)
.expect("Could not generate relative path")
absolute_path.strip_prefix(&self.base_path).unwrap_or(absolute_path)
}

pub fn get_team(&self, name: &str) -> Option<Team> {
Expand All @@ -197,3 +195,35 @@ impl Project {
result
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_vendored_gem_by_name_maps_all_gems() {
let vg1 = VendoredGem {
path: PathBuf::from("vendored/a"),
name: "a".to_string(),
};
let vg2 = VendoredGem {
path: PathBuf::from("vendored/b"),
name: "b".to_string(),
};
let project = Project {
base_path: PathBuf::from("."),
files: vec![],
packages: vec![],
vendored_gems: vec![vg1.clone(), vg2.clone()],
teams: vec![],
codeowners_file_path: PathBuf::from(".github/CODEOWNERS"),
directory_codeowner_files: vec![],
teams_by_name: HashMap::new(),
};

let map = project.vendored_gem_by_name();
assert_eq!(map.len(), 2);
assert_eq!(map.get("a").unwrap().name, vg1.name);
assert_eq!(map.get("b").unwrap().name, vg2.name);
}
}
34 changes: 26 additions & 8 deletions src/project_builder.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use std::{
fs::File,
path::{Path, PathBuf},
sync::{Arc, Mutex},
};

use error_stack::{Result, ResultExt};
use fast_glob::glob_match;
use ignore::WalkBuilder;
use ignore::{WalkBuilder, WalkParallel, WalkState};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use tracing::{instrument, warn};

Expand Down Expand Up @@ -53,12 +54,29 @@ impl<'a> ProjectBuilder<'a> {
let mut entry_types = Vec::with_capacity(INITIAL_VECTOR_CAPACITY);
let mut builder = WalkBuilder::new(&self.base_path);
builder.hidden(false);
let walkdir = builder.build();
let walk_parallel: WalkParallel = builder.build_parallel();

for entry in walkdir {
let entry = entry.change_context(Error::Io)?;
let collected = Arc::new(Mutex::new(Vec::with_capacity(INITIAL_VECTOR_CAPACITY)));
let collected_for_threads = Arc::clone(&collected);

walk_parallel.run(move || {
let collected = Arc::clone(&collected_for_threads);
Box::new(move |res| {
if let Ok(entry) = res {
if let Ok(mut v) = collected.lock() {
v.push(entry);
}
}
WalkState::Continue
})
});

// Process sequentially with &mut self
let collected_entries = Arc::try_unwrap(collected).unwrap().into_inner().unwrap();
for entry in collected_entries {
entry_types.push(self.build_entry_type(entry)?);
}

self.build_project_from_entry_types(entry_types)
}

Expand Down Expand Up @@ -216,7 +234,10 @@ impl<'a> ProjectBuilder<'a> {
}

fn matches_globs(path: &Path, globs: &[String]) -> bool {
globs.iter().any(|glob| glob_match(glob, path.to_str().unwrap()))
match path.to_str() {
Some(s) => globs.iter().any(|glob| glob_match(glob, s)),
None => false,
}
}

fn ruby_package_owner(path: &Path) -> Result<Option<String>, Error> {
Expand All @@ -241,14 +262,11 @@ mod tests {

#[test]
fn test_matches_globs() {
// should fail because hidden directories are ignored by glob patterns unless explicitly included
assert!(matches_globs(Path::new("script/.eslintrc.js"), &[OWNED_GLOB.to_string()]));
}

#[test]
fn test_glob_match() {
// Exposes bug in glob-match https://github.com/devongovett/glob-match/issues/9
// should fail because hidden directories are ignored by glob patterns unless explicitly included
assert!(glob_match(OWNED_GLOB, "script/.eslintrc.js"));
}
}