use git2::{Repository, Statuses};
use pathdiff::diff_paths;
use std::path::Path;
use crate::tree::RepoTree;
pub struct RepositoryStatus {
name: String,
long_name: String,
status_flags: StatusFlags,
origin_url: String,
}
impl RepositoryStatus {
pub fn new(repo: &Repository, base_path: &Path) -> RepositoryStatus {
let statuses = repo.statuses(None).unwrap();
let name = repo
.path()
.parent()
.unwrap()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_owned();
let long_name_full = repo.path().parent().unwrap().to_str().unwrap().to_owned();
let long_name = match diff_paths(&long_name_full, base_path) {
Some(path) => path.to_str().unwrap().to_owned(),
None => long_name_full,
};
let origin_url = match repo.find_remote("origin") {
Ok(ref remote) => remote
.url()
.expect("URL for origin could not be found")
.to_owned(),
Err(_) => String::new(),
};
let status_flags = statuses_to_string(statuses);
RepositoryStatus {
name,
long_name,
status_flags,
origin_url,
}
}
}
struct StatusFlags {
new: bool,
modified: bool,
deleted: bool,
renamed: bool,
push: bool,
pull: bool,
}
impl StatusFlags {
fn new() -> StatusFlags {
StatusFlags {
new: false,
modified: false,
deleted: false,
renamed: false,
push: false,
pull: false,
}
}
fn clean(&self) -> bool {
!self.new && !self.modified && !self.deleted && !self.renamed && !self.push && !self.pull
}
fn to_string(&self) -> String {
let mut s = String::new();
let mut add_flag = |add: bool, icon: char| {
if add {
s.push(icon)
} else {
s.push('-')
}
};
add_flag(self.new, 'N');
add_flag(self.modified, 'M');
add_flag(self.deleted, 'D');
add_flag(self.renamed, 'R');
add_flag(self.push, 'P');
add_flag(self.pull, 'U');
s
}
}
fn statuses_to_string(statuses: Statuses<'_>) -> StatusFlags {
let mut flags = StatusFlags::new();
for entry in statuses.iter() {
let status = entry.status();
if status.is_index_new() || status.is_wt_new() {
flags.new = true;
}
if status.is_index_modified() || status.is_wt_modified() {
flags.modified = true;
}
if status.is_index_deleted() || status.is_wt_deleted() {
flags.deleted = true;
}
if status.is_index_renamed() || status.is_wt_renamed() {
flags.renamed = true;
}
}
flags
}
pub fn explore_path(path: &Path, repositories: &mut Vec<Repository>) {
if let Ok(repo) = Repository::open(path) {
repositories.push(repo);
} else {
for child in path.read_dir().expect("read_dir call failed") {
if let Ok(child) = child {
if child.file_type().expect("File type failed").is_dir() {
explore_path(&child.path(), repositories);
}
}
}
}
}
pub fn print_long(statuses: Vec<RepositoryStatus>, url: bool, show_clean: bool) {
let max_repo_name_length = statuses.iter().map(|x| x.name.len()).max().unwrap_or(0);
for status in statuses.iter() {
if status.status_flags.clean() && !show_clean {
continue;
}
print!(
"{:<width$} {}",
status.long_name,
status.status_flags.to_string(),
width = max_repo_name_length
);
if url {
println!(" {}", status.origin_url);
} else {
println!();
}
}
}
pub fn print_long_tree(statuses: Vec<RepositoryStatus>, url: bool, show_clean: bool) {
let statuses = statuses
.iter()
.filter(|x| show_clean || !x.status_flags.clean())
.collect::<Vec<_>>();
let mut tree: RepoTree = RepoTree::new("".to_string());
for status in statuses.iter() {
let mut path = status.long_name.clone() + " (" + &status.status_flags.to_string() + ")";
if url {
path += &status.origin_url;
}
tree.add_path(path)
}
tree.print();
}
struct Summary {
clean: u32,
dirty: u32,
modified: u32,
untracked: u32,
unsynced: u32,
}
impl Summary {
fn new() -> Summary {
Summary {
clean: 0,
dirty: 0,
modified: 0,
untracked: 0,
unsynced: 0,
}
}
fn print(&self) {
println!("{} clean repositories", self.clean);
if self.dirty > 0 {
println!("{} repositories in dirty state, of which:", self.dirty);
}
if self.modified > 0 {
println!("\t{} repositories with modified files", self.modified);
}
if self.untracked > 0 {
println!("\t{} repositories with untracked files", self.untracked);
}
if self.unsynced > 0 {
println!("\t{} repositories with unsynced commits", self.unsynced);
}
}
}
pub fn print_summary(statuses: Vec<RepositoryStatus>) {
let mut summary = Summary::new();
for status in statuses.iter() {
if status.status_flags.clean() {
summary.clean += 1;
} else {
summary.dirty += 1;
}
if status.status_flags.modified {
summary.modified += 1;
}
if status.status_flags.new {
summary.untracked += 1;
}
}
summary.print();
}