Initial upload: GitPulse - Developer Productivity Analyzer CLI tool
This commit is contained in:
167
src/git/repository.rs
Normal file
167
src/git/repository.rs
Normal 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()
|
||||
}
|
||||
Reference in New Issue
Block a user