use std::fs;
use git2::{Error, ErrorCode, Oid, Repository, RepositoryState, StatusOptions};
use crate::branch_status::BranchStatus;
use crate::status_entry_ext::StatusEntryExt;
pub trait RepositoryExt {
fn action(&self) -> Option<&str>;
fn branch_name(&self) -> Result<String, Error>;
fn branch_status(&self) -> Result<BranchStatus, Error>;
fn rebase_i_head_name(&self) -> Result<String, Error>;
fn to_short_oid(&self, oid: Oid) -> Result<Option<String>, Error>;
}
impl RepositoryExt for Repository {
fn action(&self) -> Option<&str> {
match self.state() {
RepositoryState::ApplyMailbox => Some("am"),
RepositoryState::ApplyMailboxOrRebase => Some("am/rebase"),
RepositoryState::Bisect => Some("bisect"),
RepositoryState::CherryPick => Some("cherry"),
RepositoryState::CherryPickSequence => Some("cherry-seq"),
RepositoryState::Merge => Some("merge"),
RepositoryState::Rebase => Some("rebase"),
RepositoryState::RebaseInteractive => Some("rebase-i"),
RepositoryState::RebaseMerge => Some("rebase-m"),
RepositoryState::Revert => Some("revert"),
RepositoryState::RevertSequence => Some("revert-seq"),
_ => None,
}
}
fn branch_name(&self) -> Result<String, Error> {
let head = match self.head() {
Ok(head) => Some(head),
Err(ref e)
if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound =>
{
None
}
Err(e) => return Err(e),
};
let detached = self.head_detached()?;
let branch = if self.state() == RepositoryState::RebaseInteractive {
self.rebase_i_head_name()?
} else if detached {
let oid = head.as_ref().and_then(|h| h.target());
let short = match oid.map(|oid| self.to_short_oid(oid)) {
Some(Ok(id)) => id,
Some(Err(e)) => return Err(e),
None => None,
};
short.unwrap_or_else(|| "HEAD (detached)".to_string())
} else {
head.as_ref()
.and_then(|h| h.shorthand().ok())
.unwrap_or("HEAD (no branch)")
.to_string()
};
match self.action() {
Some(action) => Ok(branch + ":" + action),
None => Ok(branch),
}
}
fn branch_status(&self) -> Result<BranchStatus, Error> {
let mut opts = StatusOptions::new();
opts.include_untracked(false)
.include_ignored(false)
.include_unmodified(false)
.exclude_submodules(true);
let stats = self.statuses(Some(&mut opts))?;
let status = stats.iter().fold(BranchStatus::NotChanged, |acc, s| {
if acc < BranchStatus::Conflicted && s.is_conflicted() {
BranchStatus::Conflicted
} else if acc < BranchStatus::Unstaged && s.is_unstaged() {
BranchStatus::Unstaged
} else if acc < BranchStatus::Staged && s.is_staged() {
BranchStatus::Staged
} else {
acc
}
});
Ok(status)
}
fn rebase_i_head_name(&self) -> Result<String, Error> {
let path = self.path().join("rebase-merge").join("head-name");
let refname = match fs::read_to_string(path) {
Ok(content) => content,
Err(e) => return Err(Error::from_str(&e.to_string())),
};
match self.find_reference(refname.trim()) {
Ok(ref reference) => Ok(reference.shorthand().unwrap_or(&refname).to_string()),
Err(e) => Err(e),
}
}
fn to_short_oid(&self, oid: Oid) -> Result<Option<String>, Error> {
let object = self.find_object(oid, None)?;
match object.short_id() {
Ok(id) => Ok(id.as_str().map(|i| i.to_string()).ok()),
Err(e) => Err(e),
}
}
}