use crate::prelude::*;
use crate::fleet::error::{Result, SuperGitError};
use crate::git_backend;
pub struct GitOps;
impl GitOps {
pub fn discover_repo_root<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
git_backend::repo_root(path.as_ref()).map_err(|_| SuperGitError::NotARepository)
}
pub fn open_repo<P: AsRef<Path>>(path: P) -> Result<gix::Repository> {
git_backend::open_repo(path.as_ref()).map_err(|_| SuperGitError::NotARepository)
}
pub fn remote_url(repo: &gix::Repository, remote_name: &str) -> Result<String> {
git_backend::remote_url(repo, remote_name).map_err(Into::into)
}
pub fn current_branch(repo: &gix::Repository) -> Result<String> {
git_backend::current_branch(repo).map_err(Into::into)
}
pub fn is_clean(repo: &gix::Repository) -> Result<bool> {
git_backend::is_clean(repo).map_err(Into::into)
}
pub fn get_uncommitted_files(repo: &gix::Repository) -> Result<Vec<String>> {
Ok(git_backend::status_entries(repo)?
.into_iter()
.filter(|entry| entry.status != git_backend::FileStatusKind::Ignored)
.map(|entry| entry.path)
.collect())
}
pub fn branch_exists(repo: &gix::Repository, branch_name: &str) -> Result<bool> {
git_backend::branch_exists(repo, branch_name, git_backend::BranchKind::Local).map_err(Into::into)
}
pub fn remote_branch_exists(repo: &gix::Repository, branch_name: &str) -> Result<bool> {
let remote_branch = format!("origin/{branch_name}");
git_backend::branch_exists(repo, &remote_branch, git_backend::BranchKind::Remote)
.map_err(Into::into)
}
pub fn get_ahead_behind(repo: &gix::Repository) -> Result<(usize, usize)> {
let head = repo.head().map_err(anyhow::Error::from)?;
let branch = match head.referent_name() {
Some(name) => name,
None => return Ok((0, 0)),
};
let tracking = match repo.branch_remote_tracking_ref_name(branch, gix::remote::Direction::Fetch) {
Some(Ok(name)) => name,
Some(Err(err)) => return Err(anyhow::Error::from(err).into()),
None => return Ok((0, 0)),
};
let head_id = repo.head_id().map_err(anyhow::Error::from)?.detach();
let upstream_id = match repo.find_reference(tracking.as_ref()) {
Ok(mut reference) => reference.peel_to_id().map_err(anyhow::Error::from)?.detach(),
Err(_) => return Ok((0, 0)),
};
let merge_base = repo
.merge_base(head_id, upstream_id)
.map_err(anyhow::Error::from)?;
let ahead = count_commits_between(repo, head_id, merge_base.into())?;
let behind = count_commits_between(repo, upstream_id, merge_base.into())?;
Ok((ahead, behind))
}
pub fn fetch(repo: &gix::Repository, remote_name: &str) -> Result<()> {
git_backend::fetch(repo, Some(remote_name)).map_err(Into::into)
}
pub fn is_detached_head(repo: &gix::Repository) -> Result<bool> {
git_backend::is_detached_head(repo).map_err(Into::into)
}
pub fn get_local_branches(repo: &gix::Repository) -> Result<Vec<String>> {
git_backend::local_branches(repo).map_err(Into::into)
}
pub fn get_remote_branches(repo: &gix::Repository) -> Result<Vec<String>> {
git_backend::remote_branches(repo).map_err(Into::into)
}
pub fn get_default_branch(repo: &gix::Repository) -> Result<String> {
git_backend::get_default_branch(repo).map_err(Into::into)
}
}
fn count_commits_between(
repo: &gix::Repository,
from: gix::ObjectId,
stop_at: gix::ObjectId,
) -> Result<usize> {
let commit = repo.find_commit(from).map_err(anyhow::Error::from)?;
let mut count = 0usize;
for ancestor in commit.ancestors().all().map_err(anyhow::Error::from)? {
let ancestor = ancestor.map_err(anyhow::Error::from)?;
if ancestor.id == stop_at {
break;
}
count += 1;
}
Ok(count)
}