Skip to content

Commit 9b39cbc

Browse files
committed
imp(core): initial matching logic
1 parent e5d31ae commit 9b39cbc

File tree

3 files changed

+195
-8
lines changed

3 files changed

+195
-8
lines changed

core/src/commands.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::common::find_files;
2-
use crate::types::OutputFormat;
2+
use crate::types::{CodeownersEntry, OutputFormat};
33

44
use utils::app_config::AppConfig;
55
use utils::error::Result;
@@ -22,20 +22,28 @@ pub fn codeowners_parse(
2222

2323
dbg!(&codeowners_files);
2424

25-
let parsed_codeowners = codeowners_files
25+
let parsed_codeowners: Vec<CodeownersEntry> = codeowners_files
2626
.iter()
2727
.filter_map(|file| {
2828
let parsed = crate::common::parse_codeowners(file).ok()?;
29-
Some((file, parsed))
29+
Some(parsed)
3030
})
31-
.collect::<Vec<_>>();
31+
.flatten()
32+
.collect();
3233

3334
dbg!(&parsed_codeowners);
3435

3536
let files = find_files(path)?;
3637

3738
dbg!(&files);
3839

40+
for file in files {
41+
let owners = crate::common::find_owners_for_file(&file, parsed_codeowners.as_slice());
42+
let tags = crate::common::find_tags_for_file(&file, parsed_codeowners.as_slice());
43+
println!("File: {} - Owners: {:?}", file.display(), owners);
44+
println!("File: {} - Tags: {:?}", file.display(), tags);
45+
}
46+
3947
println!("CODEOWNERS parsing completed successfully");
4048
Ok(())
4149
}

core/src/common.rs

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use ignore::Walk;
1+
use ignore::{
2+
Walk,
3+
overrides::{Override, OverrideBuilder},
4+
};
25
use std::path::{Path, PathBuf};
36
use utils::error::{Error, Result};
47

@@ -134,6 +137,182 @@ fn parse_owner(owner_str: &str) -> Result<Owner> {
134137
})
135138
}
136139

140+
/// Find owners for a specific file based on all parsed CODEOWNERS entries
141+
pub fn find_owners_for_file(file_path: &Path, entries: &[CodeownersEntry]) -> Result<Vec<Owner>> {
142+
// file directory
143+
let target_dir = file_path
144+
.parent()
145+
.ok_or_else(|| Error::new("file path has no parent directory"))?;
146+
147+
// CodeownersEntry candidates
148+
let mut candidates = Vec::new();
149+
150+
for entry in entries {
151+
let codeowners_dir = match entry.source_file.parent() {
152+
Some(dir) => dir,
153+
None => {
154+
eprintln!(
155+
"CODEOWNERS entry has no parent directory: {}",
156+
entry.source_file.display()
157+
);
158+
continue;
159+
}
160+
};
161+
162+
// Check if the CODEOWNERS directory is an ancestor of the target directory
163+
if !target_dir.starts_with(codeowners_dir) {
164+
continue;
165+
}
166+
167+
// Calculate the depth as the number of components in the relative path from codeowners_dir to target_dir
168+
let rel_path = match target_dir.strip_prefix(codeowners_dir) {
169+
Ok(p) => p,
170+
Err(_) => continue, // Should not happen due to starts_with check
171+
};
172+
let depth = rel_path.components().count();
173+
174+
// Check if the pattern matches the target file
175+
let matches = {
176+
let mut builder = OverrideBuilder::new(codeowners_dir);
177+
if let Err(e) = builder.add(&entry.pattern) {
178+
eprintln!(
179+
"Invalid pattern '{}' in {}: {}",
180+
entry.pattern,
181+
entry.source_file.display(),
182+
e
183+
);
184+
continue;
185+
}
186+
187+
let over: Override = match builder.build() {
188+
Ok(o) => o,
189+
Err(e) => {
190+
eprintln!(
191+
"Failed to build override for pattern '{}': {}",
192+
entry.pattern, e
193+
);
194+
continue;
195+
}
196+
};
197+
over.matched(file_path, false).is_whitelist()
198+
};
199+
200+
if matches {
201+
candidates.push((entry, depth));
202+
}
203+
}
204+
205+
// Sort the candidates by depth, source file, and line number
206+
candidates.sort_by(|a, b| {
207+
let a_entry = a.0;
208+
let a_depth = a.1;
209+
let b_entry = b.0;
210+
let b_depth = b.1;
211+
212+
// Primary sort by depth (ascending)
213+
a_depth
214+
.cmp(&b_depth)
215+
// Then by source file (to group entries from the same CODEOWNERS file)
216+
.then_with(|| a_entry.source_file.cmp(&b_entry.source_file))
217+
// Then by line number (descending) to prioritize later entries in the same file
218+
.then_with(|| b_entry.line_number.cmp(&a_entry.line_number))
219+
});
220+
221+
// Extract the owners from the highest priority entry, if any
222+
Ok(candidates
223+
.first()
224+
.map(|(entry, _)| entry.owners.clone())
225+
.unwrap_or_default())
226+
}
227+
228+
/// Find tags for a specific file based on all parsed CODEOWNERS entries
229+
pub fn find_tags_for_file(file_path: &Path, entries: &[CodeownersEntry]) -> Result<Vec<Tag>> {
230+
let target_dir = file_path.parent().ok_or_else(|| {
231+
std::io::Error::new(
232+
std::io::ErrorKind::InvalidInput,
233+
"file path has no parent directory",
234+
)
235+
})?;
236+
237+
let mut candidates = Vec::new();
238+
239+
for entry in entries {
240+
let codeowners_dir = match entry.source_file.parent() {
241+
Some(dir) => dir,
242+
None => {
243+
eprintln!(
244+
"CODEOWNERS entry has no parent directory: {}",
245+
entry.source_file.display()
246+
);
247+
continue;
248+
}
249+
};
250+
251+
// Check if the CODEOWNERS directory is an ancestor of the target directory
252+
if !target_dir.starts_with(codeowners_dir) {
253+
continue;
254+
}
255+
256+
// Calculate the depth as the number of components in the relative path from codeowners_dir to target_dir
257+
let rel_path = match target_dir.strip_prefix(codeowners_dir) {
258+
Ok(p) => p,
259+
Err(_) => continue, // Should not happen due to starts_with check
260+
};
261+
let depth = rel_path.components().count();
262+
263+
// Check if the pattern matches the target file
264+
let matches = {
265+
let mut builder = OverrideBuilder::new(codeowners_dir);
266+
if let Err(e) = builder.add(&entry.pattern) {
267+
eprintln!(
268+
"Invalid pattern '{}' in {}: {}",
269+
entry.pattern,
270+
entry.source_file.display(),
271+
e
272+
);
273+
continue;
274+
}
275+
let over: Override = match builder.build() {
276+
Ok(o) => o,
277+
Err(e) => {
278+
eprintln!(
279+
"Failed to build override for pattern '{}': {}",
280+
entry.pattern, e
281+
);
282+
continue;
283+
}
284+
};
285+
over.matched(file_path, false).is_whitelist()
286+
};
287+
288+
if matches {
289+
candidates.push((entry, depth));
290+
}
291+
}
292+
293+
// Sort the candidates by depth, source file, and line number
294+
candidates.sort_by(|a, b| {
295+
let a_entry = a.0;
296+
let a_depth = a.1;
297+
let b_entry = b.0;
298+
let b_depth = b.1;
299+
300+
// Primary sort by depth (ascending)
301+
a_depth
302+
.cmp(&b_depth)
303+
// Then by source file (to group entries from the same CODEOWNERS file)
304+
.then_with(|| a_entry.source_file.cmp(&b_entry.source_file))
305+
// Then by line number (descending) to prioritize later entries in the same file
306+
.then_with(|| b_entry.line_number.cmp(&a_entry.line_number))
307+
});
308+
309+
// Extract the tags from the highest priority entry, if any
310+
Ok(candidates
311+
.first()
312+
.map(|(entry, _)| entry.tags.clone())
313+
.unwrap_or_default())
314+
}
315+
137316
#[cfg(test)]
138317
mod tests {
139318
use super::*;

core/src/types.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ pub struct CodeownersEntry {
1111
}
1212

1313
/// Detailed owner representation
14-
#[derive(Debug)]
14+
#[derive(Debug, Clone)]
1515
pub struct Owner {
1616
pub identifier: String,
1717
pub owner_type: OwnerType,
1818
}
1919

2020
/// Owner type classification
21-
#[derive(Debug)]
21+
#[derive(Debug, Clone)]
2222
pub enum OwnerType {
2323
User,
2424
Team,
@@ -28,7 +28,7 @@ pub enum OwnerType {
2828
}
2929

3030
/// Tag representation
31-
#[derive(Debug)]
31+
#[derive(Debug, Clone)]
3232
pub struct Tag(pub String);
3333

3434
#[derive(Clone, Debug, PartialEq)]

0 commit comments

Comments
 (0)