use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct RelatedFiles {
pub target_file: String,
pub related: Vec<(String, usize)>,
}
pub fn analyze_related_files<I>(target_file: &str, commit_files: I) -> RelatedFiles
where
I: Iterator<Item = Vec<String>>,
{
let mut co_change_counts: HashMap<String, usize> = HashMap::new();
for files in commit_files {
if files.iter().any(|f| f == target_file) {
for file in files {
if file != target_file {
*co_change_counts.entry(file).or_insert(0) += 1;
}
}
}
}
let mut related: Vec<(String, usize)> = co_change_counts.into_iter().collect();
related.sort_by(|a, b| b.1.cmp(&a.1));
related.truncate(20);
RelatedFiles {
target_file: target_file.to_string(),
related,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analyze_related_files_basic() {
let commits = vec![
vec!["src/a.rs".to_string(), "src/b.rs".to_string()],
vec!["src/a.rs".to_string(), "src/c.rs".to_string()],
vec![
"src/a.rs".to_string(),
"src/b.rs".to_string(),
"src/c.rs".to_string(),
],
vec!["src/d.rs".to_string()], ];
let result = analyze_related_files("src/a.rs", commits.into_iter());
assert_eq!(result.target_file, "src/a.rs");
assert!(result
.related
.iter()
.any(|(f, c)| f == "src/b.rs" && *c == 2));
assert!(result
.related
.iter()
.any(|(f, c)| f == "src/c.rs" && *c == 2));
assert!(!result.related.iter().any(|(f, _)| f == "src/d.rs"));
}
#[test]
fn test_analyze_related_files_no_target() {
let commits = vec![
vec!["src/x.rs".to_string(), "src/y.rs".to_string()],
vec!["src/z.rs".to_string()],
];
let result = analyze_related_files("src/a.rs", commits.into_iter());
assert!(result.related.is_empty());
}
#[test]
fn test_analyze_related_files_empty() {
let commits: Vec<Vec<String>> = vec![];
let result = analyze_related_files("src/a.rs", commits.into_iter());
assert!(result.related.is_empty());
}
}