Skip to content

Commit f6686a3

Browse files
committed
wip: move dual resolver to its own file
1 parent e2281a8 commit f6686a3

4 files changed

Lines changed: 102 additions & 90 deletions

File tree

src/core/cache.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use super::owner_resolver::find_owners_for_file;
99
use super::parse::parse_repo;
1010
use super::tag_resolver::find_tags_for_file;
1111
use super::types::{CacheEncoding, CodeownersCache, CodeownersEntry, FileEntry};
12-
use crate::core::owner_resolver::find_owners_and_tags_for_file;
12+
use crate::core::resolver::find_owners_and_tags_for_file;
1313
use crate::utils::error::{Error, Result};
1414

1515
/// Create a cache from parsed CODEOWNERS entries and files

src/core/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub(crate) mod common;
44
pub mod owner_resolver;
55
pub(crate) mod parse;
66
pub mod parser;
7+
pub mod resolver;
78
pub(crate) mod smart_iter;
89
pub mod tag_resolver;
910
pub mod types;

src/core/owner_resolver.rs

Lines changed: 0 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -402,92 +402,3 @@ mod tests {
402402
assert_eq!(result[0].identifier, "@rust-team");
403403
}
404404
}
405-
406-
/// Find both owners and tags for a specific file based on all parsed CODEOWNERS entries
407-
pub fn find_owners_and_tags_for_file(
408-
file_path: &Path, entries: &[CodeownersEntry],
409-
) -> Result<(Vec<Owner>, Vec<Tag>)> {
410-
let target_dir = file_path.parent().ok_or_else(|| {
411-
std::io::Error::new(
412-
std::io::ErrorKind::InvalidInput,
413-
"file path has no parent directory",
414-
)
415-
})?;
416-
417-
let mut candidates: Vec<_> = entries
418-
.iter()
419-
.filter_map(|entry| {
420-
let codeowners_dir = match entry.source_file.parent() {
421-
Some(dir) => dir,
422-
None => {
423-
eprintln!(
424-
"CODEOWNERS entry has no parent directory: {}",
425-
entry.source_file.display()
426-
);
427-
return None;
428-
}
429-
};
430-
431-
// Check if the CODEOWNERS directory is an ancestor of the target directory
432-
if !target_dir.starts_with(codeowners_dir) {
433-
return None;
434-
}
435-
436-
// Calculate the depth as the number of components in the relative path from codeowners_dir to target_dir
437-
let rel_path = match target_dir.strip_prefix(codeowners_dir) {
438-
Ok(p) => p,
439-
Err(_) => return None, // Should not happen due to starts_with check
440-
};
441-
let depth = rel_path.components().count();
442-
443-
// Check if the pattern matches the target file
444-
let matches = {
445-
let mut builder = OverrideBuilder::new(codeowners_dir);
446-
if let Err(e) = builder.add(&entry.pattern) {
447-
eprintln!(
448-
"Invalid pattern '{}' in {}: {}",
449-
entry.pattern,
450-
entry.source_file.display(),
451-
e
452-
);
453-
return None;
454-
}
455-
let over: Override = match builder.build() {
456-
Ok(o) => o,
457-
Err(e) => {
458-
eprintln!(
459-
"Failed to build override for pattern '{}': {}",
460-
entry.pattern, e
461-
);
462-
return None;
463-
}
464-
};
465-
over.matched(file_path, false).is_whitelist()
466-
};
467-
468-
if matches { Some((entry, depth)) } else { None }
469-
})
470-
.collect();
471-
472-
// Sort the candidates by depth, source file, and line number
473-
candidates.sort_unstable_by(|a, b| {
474-
let a_entry = a.0;
475-
let a_depth = a.1;
476-
let b_entry = b.0;
477-
let b_depth = b.1;
478-
479-
// Primary sort by depth (ascending)
480-
a_depth
481-
.cmp(&b_depth)
482-
// Then by source file (to group entries from the same CODEOWNERS file)
483-
.then_with(|| a_entry.source_file.cmp(&b_entry.source_file))
484-
// Then by line number (descending) to prioritize later entries in the same file
485-
.then_with(|| b_entry.line_number.cmp(&a_entry.line_number))
486-
});
487-
488-
// Extract both owners and tags from the highest priority entry, if any
489-
Ok(candidates
490-
.first()
491-
.map(|(entry, _)| (entry.owners.clone(), entry.tags.clone()))
492-
.unwrap_or_default())
493-
}

src/core/resolver.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use super::types::Tag;
2+
use crate::utils::error::{Error, Result};
3+
use ignore::overrides::{Override, OverrideBuilder};
4+
5+
use std::path::Path;
6+
7+
use super::types::{CodeownersEntry, Owner};
8+
9+
/// Find both owners and tags for a specific file based on all parsed CODEOWNERS entries
10+
pub fn find_owners_and_tags_for_file(
11+
file_path: &Path, entries: &[CodeownersEntry],
12+
) -> Result<(Vec<Owner>, Vec<Tag>)> {
13+
// Early return if no entries
14+
if entries.is_empty() {
15+
return Ok((Vec::new(), Vec::new()));
16+
}
17+
let target_dir = file_path.parent().ok_or_else(|| {
18+
std::io::Error::new(
19+
std::io::ErrorKind::InvalidInput,
20+
"file path has no parent directory",
21+
)
22+
})?;
23+
24+
let mut candidates: Vec<_> = entries
25+
.iter()
26+
.filter_map(|entry| {
27+
let codeowners_dir = match entry.source_file.parent() {
28+
Some(dir) => dir,
29+
None => {
30+
eprintln!(
31+
"CODEOWNERS entry has no parent directory: {}",
32+
entry.source_file.display()
33+
);
34+
return None;
35+
}
36+
};
37+
38+
// Check if the CODEOWNERS directory is an ancestor of the target directory
39+
if !target_dir.starts_with(codeowners_dir) {
40+
return None;
41+
}
42+
43+
// Calculate the depth as the number of components in the relative path from codeowners_dir to target_dir
44+
let rel_path = match target_dir.strip_prefix(codeowners_dir) {
45+
Ok(p) => p,
46+
Err(_) => return None, // Should not happen due to starts_with check
47+
};
48+
let depth = rel_path.components().count();
49+
50+
// Check if the pattern matches the target file
51+
let matches = {
52+
let mut builder = OverrideBuilder::new(codeowners_dir);
53+
if let Err(e) = builder.add(&entry.pattern) {
54+
eprintln!(
55+
"Invalid pattern '{}' in {}: {}",
56+
entry.pattern,
57+
entry.source_file.display(),
58+
e
59+
);
60+
return None;
61+
}
62+
let over: Override = match builder.build() {
63+
Ok(o) => o,
64+
Err(e) => {
65+
eprintln!(
66+
"Failed to build override for pattern '{}': {}",
67+
entry.pattern, e
68+
);
69+
return None;
70+
}
71+
};
72+
over.matched(file_path, false).is_whitelist()
73+
};
74+
75+
if matches { Some((entry, depth)) } else { None }
76+
})
77+
.collect();
78+
79+
// Sort the candidates by depth, source file, and line number
80+
candidates.sort_unstable_by(|a, b| {
81+
let a_entry = a.0;
82+
let a_depth = a.1;
83+
let b_entry = b.0;
84+
let b_depth = b.1;
85+
86+
// Primary sort by depth (ascending)
87+
a_depth
88+
.cmp(&b_depth)
89+
// Then by source file (to group entries from the same CODEOWNERS file)
90+
.then_with(|| a_entry.source_file.cmp(&b_entry.source_file))
91+
// Then by line number (descending) to prioritize later entries in the same file
92+
.then_with(|| b_entry.line_number.cmp(&a_entry.line_number))
93+
});
94+
95+
// Extract both owners and tags from the highest priority entry, if any
96+
Ok(candidates
97+
.first()
98+
.map(|(entry, _)| (entry.owners.clone(), entry.tags.clone()))
99+
.unwrap_or_default())
100+
}

0 commit comments

Comments
 (0)