use git2::{Commit, Delta, DiffOptions, Repository};
use crate::collect::errors::Result;
use crate::core::models::ChangeType;
#[derive(Debug, Clone, Default)]
pub struct CommitDiff {
pub files_changed: u32,
pub insertions: u32,
pub deletions: u32,
pub files: Vec<FileDiff>,
}
#[derive(Debug, Clone)]
pub struct FileDiff {
pub path: String,
pub change_type: ChangeType,
pub insertions: u32,
pub deletions: u32,
}
pub fn compute_commit_diff(repo: &Repository, commit: &Commit<'_>) -> Result<CommitDiff> {
let tree = commit.tree()?;
let parent_tree = if commit.parent_count() > 0 {
Some(commit.parent(0)?.tree()?)
} else {
None
};
let mut opts = DiffOptions::new();
opts.include_typechange(true);
let mut diff = repo.diff_tree_to_tree(parent_tree.as_ref(), Some(&tree), Some(&mut opts))?;
let mut find_opts = git2::DiffFindOptions::new();
find_opts.renames(true).copies(true);
diff.find_similar(Some(&mut find_opts))?;
let stats = diff.stats()?;
let files_cell: std::cell::RefCell<Vec<FileDiff>> =
std::cell::RefCell::new(Vec::with_capacity(stats.files_changed()));
diff.foreach(
&mut |delta, _progress| {
let path = delta
.new_file()
.path()
.or_else(|| delta.old_file().path())
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default();
let change_type = map_change_type(delta.status());
files_cell.borrow_mut().push(FileDiff {
path,
change_type,
insertions: 0,
deletions: 0,
});
true
},
None,
None,
Some(&mut |delta, _hunk, line| {
let path = delta
.new_file()
.path()
.or_else(|| delta.old_file().path())
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default();
let mut files = files_cell.borrow_mut();
if let Some(file) = files.iter_mut().find(|f| f.path == path) {
match line.origin() {
'+' => file.insertions = file.insertions.saturating_add(1),
'-' => file.deletions = file.deletions.saturating_add(1),
_ => {}
}
}
true
}),
)?;
Ok(CommitDiff {
files_changed: stats.files_changed() as u32,
insertions: stats.insertions() as u32,
deletions: stats.deletions() as u32,
files: files_cell.into_inner(),
})
}
fn map_change_type(delta: Delta) -> ChangeType {
match delta {
Delta::Added | Delta::Copied | Delta::Untracked => ChangeType::Added,
Delta::Deleted => ChangeType::Deleted,
Delta::Renamed => ChangeType::Renamed,
_ => ChangeType::Modified,
}
}