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

This commit is contained in:
2026-02-04 15:45:29 +00:00
parent 91cf5351a2
commit fb2e061b83

167
src/git/repository.rs Normal file
View File

@@ -0,0 +1,167 @@
use anyhow::{anyhow, Context, Result};
use git2::{BranchType, Repository as Git2Repository};
use std::path::PathBuf;
pub struct Repository {
path: PathBuf,
repo: Git2Repository,
}
impl Repository {
pub fn new(path: Option<PathBuf>) -> Result<Self> {
let path = path.unwrap_or_else(|| PathBuf::from("."));
let repo = Git2Repository::open(&path)
.with_context(|| format!("Failed to open git repository at '{}'", path.display()))?;
Ok(Self { path, repo })
}
pub fn path(&self) -> &PathBuf {
&self.path
}
pub fn workdir(&self) -> Option<std::path::PathBuf> {
self.repo.workdir().map(|p| p.to_path_buf())
}
pub fn is_empty(&self) -> bool {
self.repo.is_empty()
}
pub fn head(&self) -> Result<git2::Reference> {
self.repo.head().with_context(|| "Failed to get HEAD reference")
}
pub fn head_name(&self) -> Result<Option<String>> {
let head = self.head()?;
Ok(head.shorthand().map(|s| s.to_string()))
}
pub fn branch_name(&self) -> Result<Option<String>> {
let head = self.head()?;
if head.is_branch() {
return Ok(head.shorthand().map(|s| s.to_string()));
}
Ok(None)
}
pub fn remote_url(&self) -> Result<Option<String>> {
if let Some(remote) = self.repo.find_remote("origin").ok() {
return Ok(remote.url().map(|s| s.to_string()));
}
Ok(None)
}
pub fn is_detached(&self) -> bool {
self.repo.head_detached().unwrap_or(false)
}
pub fn branches(&self) -> Result<Vec<String>> {
let mut branches = Vec::new();
self.repo.branches(None, |_| true)?.iter().for_each(|b| {
if let Ok(Some(branch)) = b {
if let Some(name) = branch.name() {
if let Some(name) = name {
branches.push(name.to_string());
}
}
}
});
Ok(branches)
}
pub fn local_branches(&self) -> Result<Vec<String>> {
let mut branches = Vec::new();
self.repo.branches(Some(BranchType::Local), |_| true)?.iter().for_each(|b| {
if let Ok(Some(branch)) = b {
if let Some(name) = branch.name() {
if let Some(name) = name {
branches.push(name.to_string());
}
}
}
});
Ok(branches)
}
pub fn remote_branches(&self) -> Result<Vec<String>> {
let mut branches = Vec::new();
self.repo.branches(Some(BranchType::Remote), |_| true)?.iter().for_each(|b| {
if let Ok(Some(branch)) = b {
if let Some(name) = branch.name() {
if let Some(name) = name {
branches.push(name.to_string());
}
}
}
});
Ok(branches)
}
pub fn commit_count(&self) -> Result<usize> {
let mut count = 0;
let mut revwalk = self.repo.revwalk()?;
revwalk.set_sorting(git2::Sort::NONE)?;
revwalk.push_head()?;
for _ in revwalk {
count += 1;
}
Ok(count)
}
pub fn first_commit(&self) -> Result<Option<git2::Commit>> {
let mut revwalk = self.repo.revwalk()?;
revwalk.set_sorting(git2::Sort::TIME)?;
revwalk.push_head()?;
if let Ok(Some(oid)) = revwalk.nth(0) {
return Ok(self.repo.find_commit(oid).ok());
}
Ok(None)
}
pub fn get_commit(&self, oid: git2::Oid) -> Result<git2::Commit> {
self.repo.find_commit(oid).with_context(|| "Failed to find commit")
}
pub fn diff_tree_to_tree(
&self,
old_tree: &git2::Tree,
new_tree: &git2::Tree,
) -> Result<git2::Diff> {
self.repo
.diff_tree_to_tree(Some(old_tree), Some(new_tree), None)
.with_context(|| "Failed to create diff between trees")
}
pub fn diff_commit_to_parent(&self, commit: &git2::Commit) -> Result<git2::Diff> {
let parent = if commit.parent_count() > 0 {
Some(commit.parent(0)?)
} else {
None
};
let parent_tree = parent.as_ref().map(|p| p.tree()).transpose()?;
let commit_tree = commit.tree()?;
self.repo
.diff_tree_to_tree(parent_tree.as_ref(), Some(&commit_tree), None)
.with_context(|| "Failed to create diff for commit")
}
pub fn diff_find_similar(&self, diff: &mut git2::Diff) -> Result<()> {
diff.find_similar(None)?;
Ok(())
}
pub fn stats(&self, diff: &git2::Diff) -> Result<git2::DiffStats> {
diff.stats_with_context(git2::DiffStatsFormat::FULL, 0)
.with_context(|| "Failed to get diff stats")
}
pub fn raw(&self) -> &Git2Repository {
&self.repo
}
}
pub fn is_git_repository(path: &PathBuf) -> bool {
Git2Repository::open(path).is_ok()
}