use std::path::Path;
use anyhow::Result;
use git2::{DiffDelta, DiffFindOptions, Oid, Repository, Sort, Tree};
pub fn blob_bytes(repo: &Repository, tree: &Tree, path: &Path) -> Result<Vec<u8>> {
let entry = tree.get_path(path)?;
let blob = repo.find_blob(entry.id())?;
Ok(blob.content().to_vec())
}
pub fn text_loc_if_in_range(
repo: &Repository,
oid: Oid,
min_bytes: usize,
max_bytes: usize,
) -> Option<usize> {
let blob = repo.find_blob(oid).ok()?;
let content = blob.content();
if content.len() < min_bytes || content.len() > max_bytes {
return None;
}
let text = std::str::from_utf8(content).ok()?;
Some(text.lines().count())
}
pub fn walk_single_parent_commit_diffs(
repo_path: &Path,
max_commits: usize,
detect_renames: bool,
mut on_delta: impl FnMut(&Repository, Oid, &DiffDelta) -> Result<()>,
) -> Result<()> {
let repo = Repository::open(repo_path)?;
let mut walk = repo.revwalk()?;
walk.set_sorting(Sort::TIME)?;
walk.push_head()?;
for (visited, id) in walk.enumerate() {
if visited >= max_commits {
break;
}
let Ok(id) = id else { continue };
let Ok(commit) = repo.find_commit(id) else {
continue;
};
if commit.parents().len() != 1 {
continue;
}
let parent = commit.parent(0)?;
let before_tree = parent.tree()?;
let after_tree = commit.tree()?;
let mut diff = repo.diff_tree_to_tree(Some(&before_tree), Some(&after_tree), None)?;
if detect_renames {
let mut find_opts = DiffFindOptions::new();
find_opts.renames(true);
diff.find_similar(Some(&mut find_opts))?;
}
for delta in diff.deltas() {
on_delta(&repo, id, &delta)?;
}
}
Ok(())
}