Initial upload: GitPulse - Developer Productivity Analyzer CLI tool
Some checks failed
CI / test (push) Has been cancelled
CI / release (push) Has been cancelled

This commit is contained in:
2026-02-04 15:45:31 +00:00
parent fb2e061b83
commit b54349c031

145
src/git/commit.rs Normal file
View File

@@ -0,0 +1,145 @@
use anyhow::Result;
use chrono::{DateTime, TimeZone, Utc};
use git2;
pub struct Commit<'a> {
commit: git2::Commit<'a>,
}
impl<'a> Commit<'a> {
pub fn new(commit: git2::Commit<'a>) -> Self {
Self { commit }
}
pub fn id(&self) -> git2::Oid {
self.commit.id()
}
pub fn hex(&self) -> String {
self.id().to_string()
}
pub fn short_id(&self, len: usize) -> String {
self.id().to_string()[..len.min(40)].to_string()
}
pub fn message(&self) -> Option<&str> {
self.commit.message()
}
pub fn message_short(&self, max_len: usize) -> String {
let msg = self.commit.message().unwrap_or("");
if msg.len() > max_len {
format!("{}...", &msg[..max_len - 3])
} else {
msg.to_string()
}
}
pub fn summary(&self) -> Option<&str> {
self.commit.summary()
}
pub fn time(&self) -> DateTime<Utc> {
let time = self.commit.time();
let naive = chrono::NaiveDateTime::from_timestamp_opt(time.seconds(), 0).unwrap();
Utc.from_utc_datetime(&naive)
}
pub fn author(&self) -> &git2::Signature {
self.commit.author()
}
pub fn author_name(&self) -> String {
self.commit.author().name().unwrap_or("Unknown").to_string()
}
pub fn author_email(&self) -> String {
self.commit.author().email().unwrap_or("Unknown").to_string()
}
pub fn committer(&self) -> &git2::Signature {
self.commit.committer()
}
pub fn committer_name(&self) -> String {
self.commit.committer().name().unwrap_or("Unknown").to_string()
}
pub fn committer_email(&self) -> String {
self.commit.committer().email().unwrap_or("Unknown").to_string()
}
pub fn parent_count(&self) -> usize {
self.commit.parent_count()
}
pub fn parents(&self) -> Vec<git2::Commit<'a>> {
self.commit.parents().collect()
}
pub fn is_merge(&self) -> bool {
self.commit.parent_count() > 1
}
pub fn is_initial_commit(&self) -> bool {
self.commit.parent_count() == 0
}
pub fn tree(&self) -> Result<git2::Tree> {
self.commit.tree().with_context(|| "Failed to get tree")
}
pub fn diff_to_parent(&self) -> Result<Option<git2::Diff>> {
if self.is_initial_commit() {
return Ok(None);
}
let parent = self.commit.parent(0)?;
let parent_tree = parent.tree()?;
let commit_tree = self.commit.tree()?;
let repo = self.commit.repository();
let diff = repo.diff_tree_to_tree(Some(&parent_tree), Some(&commit_tree), None)?;
Ok(Some(diff))
}
pub fn raw(&self) -> &git2::Commit<'a> {
&self.commit
}
}
pub struct CommitIterator<'a> {
repo: &'a git2::Repository,
revwalk: git2::Revwalk<'a>,
}
impl<'a> CommitIterator<'a> {
pub fn new(repo: &'a git2::Repository) -> Result<Self> {
let mut revwalk = repo.revwalk()?;
revwalk.set_sorting(git2::Sort::TIME)?;
revwalk.push_head()?;
Ok(Self { repo, revwalk })
}
pub fn with_sorting(repo: &'a git2::Repository, sorting: git2::Sort) -> Result<Self> {
let mut revwalk = repo.revwalk()?;
revwalk.set_sorting(sorting)?;
revwalk.push_head()?;
Ok(Self { repo, revwalk })
}
}
impl<'a> Iterator for CommitIterator<'a> {
type Item = Result<Commit<'a>>;
fn next(&mut self) -> Option<Self::Item> {
self.revwalk
.next()
.map(|oid| match oid {
Ok(oid) => match self.repo.find_commit(oid) {
Ok(commit) => Ok(Commit::new(commit)),
Err(e) => Err(anyhow::anyhow!("Failed to find commit {}: {}", oid, e)),
},
Err(e) => Err(anyhow::anyhow!("Revwalk error: {}", e)),
})
}
}