use anyhow::{Context, Result};
use git2::Repository;
use crate::features::commits::CommitInfo;
pub fn get_log(
repo: &Repository,
max_count: usize,
filter_author: Option<&str>,
filter_message: Option<&str>,
) -> Result<Vec<CommitInfo>> {
let mut revwalk = repo.revwalk().context("failed to create revwalk")?;
revwalk.push_head().context("failed to push HEAD")?;
revwalk
.set_sorting(git2::Sort::TIME | git2::Sort::TOPOLOGICAL)
.context("failed to set sorting")?;
let author_lower = filter_author.map(|s| s.to_lowercase());
let message_lower = filter_message.map(|s| s.to_lowercase());
let mut results = Vec::with_capacity(max_count.min(256));
for oid_result in revwalk {
if results.len() >= max_count {
break;
}
let oid = oid_result.context("revwalk iteration error")?;
let commit = repo
.find_commit(oid)
.context("failed to find commit during log walk")?;
if let Some(ref needle) = author_lower {
let author = commit.author();
let name = author.name().unwrap_or("").to_lowercase();
let email = author.email().unwrap_or("").to_lowercase();
if !name.contains(needle.as_str()) && !email.contains(needle.as_str()) {
continue;
}
}
if let Some(ref needle) = message_lower {
let msg = commit.message().unwrap_or("").to_lowercase();
if !msg.contains(needle.as_str()) {
continue;
}
}
results.push(CommitInfo::from_git2_commit(&commit));
}
Ok(results)
}
pub fn search_commits(repo: &Repository, query: &str, max_count: usize) -> Result<Vec<CommitInfo>> {
let needle = query.to_lowercase();
let mut revwalk = repo.revwalk().context("failed to create revwalk")?;
revwalk.push_head().context("failed to push HEAD")?;
revwalk
.set_sorting(git2::Sort::TIME | git2::Sort::TOPOLOGICAL)
.context("failed to set sorting")?;
let mut results = Vec::with_capacity(max_count.min(256));
for oid_result in revwalk {
if results.len() >= max_count {
break;
}
let oid = oid_result.context("revwalk iteration error")?;
let commit = repo
.find_commit(oid)
.context("failed to find commit during search")?;
let summary = commit.summary().unwrap_or("").to_lowercase();
let message = commit.message().unwrap_or("").to_lowercase();
let author = commit.author();
let author_name = author.name().unwrap_or("").to_lowercase();
let author_email = author.email().unwrap_or("").to_lowercase();
let oid_str = oid.to_string();
if summary.contains(&needle)
|| message.contains(&needle)
|| author_name.contains(&needle)
|| author_email.contains(&needle)
|| oid_str.contains(&needle)
{
results.push(CommitInfo::from_git2_commit(&commit));
}
}
Ok(results)
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
fn setup_repo_with_commits() -> (tempfile::TempDir, Repository) {
let dir = tempfile::tempdir().unwrap();
let repo = Repository::init(dir.path()).unwrap();
{
let mut config = repo.config().unwrap();
config.set_str("user.name", "Alice Test").unwrap();
config.set_str("user.email", "alice@example.com").unwrap();
}
let c1 = {
std::fs::write(dir.path().join("a.txt"), "aaa\n").unwrap();
let mut index = repo.index().unwrap();
index.add_path(Path::new("a.txt")).unwrap();
index.write().unwrap();
let tree_oid = index.write_tree().unwrap();
let tree = repo.find_tree(tree_oid).unwrap();
let sig = repo.signature().unwrap();
repo.commit(Some("HEAD"), &sig, &sig, "first commit", &tree, &[])
.unwrap()
};
{
let commit1 = repo.find_commit(c1).unwrap();
std::fs::write(dir.path().join("b.txt"), "bbb\n").unwrap();
let mut index2 = repo.index().unwrap();
index2.add_path(Path::new("b.txt")).unwrap();
index2.write().unwrap();
let tree_oid2 = index2.write_tree().unwrap();
let tree2 = repo.find_tree(tree_oid2).unwrap();
let sig2 = git2::Signature::now("Bob Builder", "bob@example.com").unwrap();
repo.commit(
Some("HEAD"),
&sig2,
&sig2,
"second commit by Bob",
&tree2,
&[&commit1],
)
.unwrap();
}
(dir, repo)
}
#[test]
fn get_log_no_filters() {
let (_dir, repo) = setup_repo_with_commits();
let log = get_log(&repo, 100, None, None).unwrap();
assert_eq!(log.len(), 2);
assert_eq!(log[0].summary, "second commit by Bob");
assert_eq!(log[1].summary, "first commit");
}
#[test]
fn get_log_filter_author() {
let (_dir, repo) = setup_repo_with_commits();
let log = get_log(&repo, 100, Some("bob"), None).unwrap();
assert_eq!(log.len(), 1);
assert_eq!(log[0].summary, "second commit by Bob");
}
#[test]
fn get_log_filter_message() {
let (_dir, repo) = setup_repo_with_commits();
let log = get_log(&repo, 100, None, Some("first")).unwrap();
assert_eq!(log.len(), 1);
assert_eq!(log[0].summary, "first commit");
}
#[test]
fn get_log_respects_max_count() {
let (_dir, repo) = setup_repo_with_commits();
let log = get_log(&repo, 1, None, None).unwrap();
assert_eq!(log.len(), 1);
}
#[test]
fn search_commits_by_author() {
let (_dir, repo) = setup_repo_with_commits();
let results = search_commits(&repo, "alice", 100).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].summary, "first commit");
}
#[test]
fn search_commits_by_message() {
let (_dir, repo) = setup_repo_with_commits();
let results = search_commits(&repo, "second", 100).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].author_name, "Bob Builder");
}
#[test]
fn search_commits_case_insensitive() {
let (_dir, repo) = setup_repo_with_commits();
let results = search_commits(&repo, "BOB", 100).unwrap();
assert_eq!(results.len(), 1);
}
}