use std::path::Path;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use git2::Repository;
use crate::commit::CommitInfo;
fn find_default_branch(repo: &Repository) -> Result<git2::Reference<'_>> {
for name in &["refs/heads/main", "refs/heads/master"] {
if let Ok(r) = repo.find_reference(name) {
return Ok(r);
}
}
Err(anyhow!("Could not find 'main' or 'master' branch"))
}
fn resolve_start_commit(repo: &Repository) -> Result<git2::Commit<'_>> {
let head = repo.head().context("Failed to read HEAD")?;
if head.is_branch() {
head.peel_to_commit()
.context("HEAD does not point to a commit")
} else {
let branch = find_default_branch(repo)?;
branch
.peel_to_commit()
.context("Default branch does not point to a commit")
}
}
pub fn collect_linear_history(repo_path: &Path) -> Result<Vec<CommitInfo>> {
collect_linear_history_since(repo_path, None)
}
pub fn collect_linear_history_since(
repo_path: &Path,
since_commit: Option<&str>,
) -> Result<Vec<CommitInfo>> {
let repo = Repository::open(repo_path).context("Failed to open git repository")?;
let start_commit = resolve_start_commit(&repo)?;
let mut revwalk = repo.revwalk().context("Failed to create revwalk")?;
revwalk.push(start_commit.id())?;
revwalk.simplify_first_parent()?;
if let Some(hash) = since_commit {
let oid =
git2::Oid::from_str(hash).with_context(|| format!("Invalid commit hash: {hash}"))?;
revwalk.hide(oid)?;
}
let mut commits = Vec::new();
for oid_result in revwalk {
let oid = oid_result.context("Failed to iterate revwalk")?;
let commit = repo
.find_commit(oid)
.with_context(|| format!("Failed to find commit {oid}"))?;
let time = commit.time();
let timestamp = unigraph_timestamp::Timestamp::from_unix_timestamp(time.seconds());
commits.push(CommitInfo {
hash: oid.to_string(),
timestamp,
summary: commit.summary().unwrap_or("").to_string(),
});
}
commits.reverse();
Ok(commits)
}