use git2;
use regex;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::Path};
use tracing::debug;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PriorityRule {
pub pattern: String,
pub score: i32,
}
pub fn get_file_priority(path: &str, rules: &[PriorityRule]) -> i32 {
let mut priority = 0;
for rule in rules {
if let Ok(re) = regex::Regex::new(&rule.pattern) {
if re.is_match(path) {
priority += rule.score;
}
}
}
priority
}
pub fn get_file_priority_with_category(
path: &str,
rules: &[PriorityRule],
category_weights: &crate::category::CategoryWeights,
) -> (i32, crate::category::FileCategory) {
let category = crate::category::categorize_file(path);
let rule_priority = get_file_priority(path, rules);
let category_offset = category_weights.get_offset(category);
let total_priority = rule_priority + category_offset;
debug!(
"File: {} | Category: {} | Rule priority: {} | Category offset: {} | Total: {}",
path,
category.name(),
rule_priority,
category_offset,
total_priority
);
(total_priority, category)
}
pub fn compute_recentness_boost(
commit_times: &HashMap<String, u64>,
max_boost: i32,
) -> HashMap<String, i32> {
if commit_times.is_empty() {
return HashMap::new();
}
let mut sorted: Vec<(&String, &u64)> = commit_times.iter().collect();
sorted.sort_by_key(|(_, t)| **t);
if sorted.len() <= 1 {
let mut single = HashMap::new();
for file in commit_times.keys() {
single.insert(file.clone(), 0);
}
return single;
}
let mut result = HashMap::new();
let oldest_time = *sorted.first().unwrap().1;
let newest_time = *sorted.last().unwrap().1;
let time_range = newest_time.saturating_sub(oldest_time) as f64;
if time_range == 0.0 {
for (path, _) in sorted {
result.insert(path.clone(), 0);
}
return result;
}
for (path, time) in sorted {
let time_diff = (*time - oldest_time) as f64;
let rank = time_diff / time_range; let boost = (rank * max_boost as f64).round() as i32; result.insert(path.clone(), boost);
}
result
}
pub fn get_recent_commit_times_git2(
repo_path: &Path,
max_commits: usize,
) -> Option<HashMap<String, u64>> {
let mut current_path = repo_path.to_path_buf();
while current_path.components().count() > 1 {
if current_path.join(".git").exists() {
break;
}
current_path = current_path.parent()?.to_path_buf();
}
let repo = match git2::Repository::open(¤t_path) {
Ok(repo) => repo,
Err(_) => {
debug!("Not a Git repository or unable to open: {:?}", current_path);
return None;
}
};
let mut revwalk = match repo.revwalk() {
Ok(revwalk) => revwalk,
Err(_) => {
debug!("Unable to get revwalk for: {:?}", current_path);
return None;
}
};
if let Err(e) = revwalk.push_head() {
debug!(
"Unable to push HEAD to revwalk: {:?} in {:?}",
e, current_path
);
return None;
}
revwalk.set_sorting(git2::Sort::TIME).ok()?;
let mut commit_times = HashMap::new();
for oid_result in revwalk.take(max_commits) {
let oid = match oid_result {
Ok(oid) => oid,
Err(e) => {
debug!("Error during revwalk iteration: {:?}", e);
continue;
}
};
let commit = match repo.find_commit(oid) {
Ok(commit) => commit,
Err(e) => {
debug!("Failed to find commit for OID {:?}: {:?}", oid, e);
continue;
}
};
let tree = match commit.tree() {
Ok(tree) => tree,
Err(e) => {
debug!("Failed to get tree for commit {:?}: {:?}", oid, e);
continue;
}
};
let time = commit.time().seconds() as u64;
tree.walk(git2::TreeWalkMode::PreOrder, |root, entry| {
if let Some(name) = entry.name() {
if entry.kind() == Some(git2::ObjectType::Blob) {
let full_path = format!("{}{}", root, name);
commit_times.entry(full_path).or_insert(time);
}
}
git2::TreeWalkResult::Ok
})
.ok()?;
}
Some(commit_times)
}