use std::fmt;
use std::path::PathBuf;
use serde::Serialize;
#[derive(Clone, Eq, Hash, PartialEq, Serialize)]
pub struct Repo {
path: PathBuf,
}
impl Repo {
pub fn new(path: String) -> Repo {
Repo {
path: PathBuf::from(path),
}
}
pub fn as_git2_repo(&self) -> ::git2::Repository {
::git2::Repository::open(&self.path).unwrap_or_else(|_| {
panic!(
"Could not open {} as a git repo. Perhaps you should run \
`git global scan` again.",
&self.path.as_path().to_str().unwrap()
)
})
}
pub fn path(&self) -> String {
self.path.to_str().unwrap().to_string()
}
pub fn get_status_lines(
&self,
mut status_opts: ::git2::StatusOptions,
) -> Vec<String> {
let git2_repo = self.as_git2_repo();
let statuses = git2_repo
.statuses(Some(&mut status_opts))
.unwrap_or_else(|_| panic!("Could not get statuses for {}.", self));
statuses
.iter()
.map(|entry| {
let path = entry.path().unwrap();
let status = entry.status();
let status_for_path = get_short_format_status(status);
format!("{} {}", status_for_path, path)
})
.collect()
}
fn branch_to_commit(branch: git2::Branch) -> Option<git2::Commit> {
branch.into_reference().peel_to_commit().ok()
}
fn get_log(
repo: &git2::Repository,
commit: git2::Commit,
) -> Vec<git2::Oid> {
let mut revwalk = repo.revwalk().unwrap();
revwalk.push(commit.id()).unwrap();
revwalk.filter_map(|id| id.ok()).collect::<Vec<git2::Oid>>()
}
pub fn is_ahead(&self) -> bool {
let repo = self.as_git2_repo();
let local_branches = match repo.branches(Some(git2::BranchType::Local))
{
Ok(branches) => branches,
Err(_) => return false,
};
let remote_branches =
match repo.branches(Some(git2::BranchType::Remote)) {
Ok(branches) => branches,
Err(_) => return false,
};
let remote_commit_ids = remote_branches
.filter_map(|branch| branch.ok().map(|b| b.0))
.filter_map(Self::branch_to_commit)
.flat_map(|commit| Self::get_log(&repo, commit))
.collect::<Vec<_>>();
#[allow(clippy::let_and_return)]
let is_ahead = local_branches
.filter_map(|branch| branch.ok().map(|b| b.0))
.any(|branch| match Self::branch_to_commit(branch) {
Some(commit) => !remote_commit_ids.contains(&commit.id()),
None => false,
});
is_ahead
}
pub fn get_stash_list(&self) -> Vec<String> {
let mut stash = vec![];
self.as_git2_repo()
.stash_foreach(|index, name, _oid| {
stash.push(format!("stash@{{{}}}: {}", index, name));
true
})
.unwrap();
stash
}
}
impl fmt::Display for Repo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.path())
}
}
fn get_short_format_status(status: ::git2::Status) -> String {
let mut istatus = match status {
s if s.is_index_new() => 'A',
s if s.is_index_modified() => 'M',
s if s.is_index_deleted() => 'D',
s if s.is_index_renamed() => 'R',
s if s.is_index_typechange() => 'T',
_ => ' ',
};
let mut wstatus = match status {
s if s.is_wt_new() => {
if istatus == ' ' {
istatus = '?';
}
'?'
}
s if s.is_wt_modified() => 'M',
s if s.is_wt_deleted() => 'D',
s if s.is_wt_renamed() => 'R',
s if s.is_wt_typechange() => 'T',
_ => ' ',
};
if status.is_ignored() {
istatus = '!';
wstatus = '!';
}
if status.is_conflicted() {
istatus = 'C';
wstatus = 'C';
}
format!("{}{}", istatus, wstatus)
}