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