Skip to content

Commit

Permalink
refactor(lib): extract git statuses to a dedicated module
Browse files Browse the repository at this point in the history
  • Loading branch information
oknozor committed Oct 24, 2020
1 parent 6d107cd commit 7191f4e
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 39 deletions.
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::git::status::Statuses;
use thiserror::Error;

#[derive(Error, Debug)]
Expand All @@ -10,6 +11,8 @@ pub(crate) enum ErrorKind {
cause: String,
additional_info: String,
},
#[error("{statuses}")]
NothingToCommit { statuses: Statuses },
#[error("{level}:\n\t{cause}\n")]
Semver { level: String, cause: String },
#[error("{level}:\n\t{cause}\n")]
Expand Down
1 change: 1 addition & 0 deletions src/git/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod hook;
pub mod repository;
pub mod status;
47 changes: 14 additions & 33 deletions src/git/repository.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use super::status::Statuses;
use crate::error::ErrorKind;
use crate::error::ErrorKind::Git;
use crate::OidOf;
use anyhow::Result;
use colored::Colorize;
use git2::{
Commit as Git2Commit, Diff, DiffOptions, IndexAddOption, Object, ObjectType, Oid,
Repository as Git2Repository, StatusOptions, Statuses,
Repository as Git2Repository, StatusOptions,
};
use std::path::Path;

Expand Down Expand Up @@ -74,10 +76,9 @@ impl Repository {
.commit(Some("HEAD"), &sig, &sig, &message, &tree, &[])
.map_err(|err| anyhow!(err))
} else {
Err(anyhow!(
"{}, nothing to commit (use \"git add\" to track)",
self.statuses_display()?
))
Err(anyhow!(ErrorKind::NothingToCommit {
statuses: self.get_statuses()?
}))
}
}

Expand All @@ -87,9 +88,12 @@ impl Repository {
options.exclude_submodules(true);
options.include_unmodified(false);

self.0
let statuses = self
.0
.statuses(Some(&mut options))
.map_err(|err| anyhow!(err))
.map_err(|err| anyhow!(err))?;

Ok(Statuses::from(statuses))
}

pub(crate) fn get_head_commit_oid(&self) -> Result<Oid> {
Expand Down Expand Up @@ -169,7 +173,7 @@ impl Repository {
if self.get_diff(true).is_some() {
return Err(anyhow!(
"{}{}",
self.statuses_display()?,
self.get_statuses()?,
"Cannot create tag : changes needs to be commited".red()
));
}
Expand Down Expand Up @@ -227,29 +231,6 @@ impl Repository {
let tree = obj.peel(ObjectType::Tree)?;
Ok(Some(tree))
}

pub fn statuses_display(&self) -> Result<String> {
let statuses = self.get_statuses()?;
//TODO : implement fmt display and use a proper statuses wrapper struct
let mut out = String::new();
statuses.iter().for_each(|entry| {
let status = match entry.status() {
s if s.contains(git2::Status::WT_NEW) => "Untracked: ",
s if s.contains(git2::Status::WT_RENAMED) => "Renamed: ",
s if s.contains(git2::Status::WT_DELETED) => "Deleted: ",
s if s.contains(git2::Status::WT_TYPECHANGE) => "Typechange: ",
s if s.contains(git2::Status::WT_MODIFIED) => "Modified: ",
s if s.contains(git2::Status::INDEX_NEW) => "New file: ",
s if s.contains(git2::Status::INDEX_MODIFIED) => "Modified: ",
s if s.contains(git2::Status::INDEX_DELETED) => "Deleted: ",
s if s.contains(git2::Status::INDEX_RENAMED) => "Renamed: ",
s if s.contains(git2::Status::INDEX_TYPECHANGE) => "Typechange:",
_ => "unknown git status",
};
out.push_str(&format!("{} {}\n", status.red(), entry.path().unwrap()));
});
Ok(out)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -398,7 +379,7 @@ mod test {

let statuses = repo.get_statuses()?;

assert!(statuses.is_empty());
assert!(statuses.0.is_empty());
Ok(())
}

Expand All @@ -412,7 +393,7 @@ mod test {

let statuses = repo.get_statuses()?;

assert!(statuses.is_empty().not());
assert!(statuses.0.is_empty().not());
Ok(())
}

Expand Down
103 changes: 103 additions & 0 deletions src/git/status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use crate::git::status::Changes::{Deleted, Modified, New, Renamed, TypeChange};
use colored::*;
use git2::StatusEntry as Git2StatusEntry;
use git2::Statuses as Git2Statuses;
use serde::export::Formatter;
use std::fmt;

pub(crate) struct Statuses(pub Vec<Status>);

pub(crate) enum Status {
Untracked(Changes),
UnCommitted(Changes),
}

pub(crate) enum Changes {
New(String),
Renamed(String),
Deleted(String),
TypeChange(String),
Modified(String),
}

impl Changes {
fn to_string(&self, color: &str) -> String {
match &self {
New(p) => format!("{}: {}", "new: ".color(color), p),
Renamed(p) => format!("{}: {}", "renamed: ".color(color), p),
Deleted(p) => format!("{}: {}", "deleted: ".color(color), p),
TypeChange(p) => format!("{} {}", "type changed: ".color(color), p),
Modified(p) => format!("{}: {}", "modified: ".color(color), p),
}
}
}

impl From<Git2Statuses<'_>> for Statuses {
fn from(statuses: Git2Statuses) -> Self {
Self(statuses.iter().map(Status::from).collect())
}
}

impl<'a, 'b: 'a> From<Git2StatusEntry<'b>> for Status {
fn from(status: Git2StatusEntry<'b>) -> Self {
let path = status
.path()
.unwrap_or_else(|| "invalid utf8 path")
.to_string();
match status.status() {
s if s.is_wt_new() => Status::Untracked(New(path)),
s if s.is_wt_renamed() => Status::Untracked(Renamed(path)),
s if s.is_wt_deleted() => Status::Untracked(Deleted(path)),
s if s.is_wt_typechange() => Status::Untracked(TypeChange(path)),
s if s.is_wt_modified() => Status::Untracked(Modified(path)),
s if s.is_index_new() => Status::UnCommitted(New(path)),
s if s.is_index_modified() => Status::UnCommitted(Modified(path)),
s if s.is_index_deleted() => Status::UnCommitted(Deleted(path)),
s if s.is_index_renamed() => Status::UnCommitted(Renamed(path)),
s if s.is_index_typechange() => Status::UnCommitted(TypeChange(path)),
_ => unreachable!(),
}
}
}

impl fmt::Display for Statuses {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut untracked = vec![];
let mut uncommitted = vec![];

self.0.iter().for_each(|status| {
match status {
Status::Untracked(changes) => untracked.push(changes),
Status::UnCommitted(changes) => uncommitted.push(changes),
};
});

if !untracked.is_empty() {
writeln!(f, "Untracked files :").unwrap();
untracked.iter().for_each(|change| {
writeln!(f, "\t{}", change.to_string("red")).unwrap();
});
writeln!(f, "Use `git add` to track").unwrap();
}

if !untracked.is_empty() && !uncommitted.is_empty() {
write!(f, "\n\n")?;
}

if !uncommitted.is_empty() {
writeln!(f, "Changes to be committed :").unwrap();
uncommitted.iter().for_each(|change| {
writeln!(f, "\t{}", change.to_string("green")).unwrap();
});
writeln!(f, "Use `coco <type>` to commit changes").unwrap();
}

write!(f, "\n\n")
}
}

impl fmt::Debug for Statuses {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self)
}
}
9 changes: 3 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ use std::{collections::HashMap, str::FromStr};
use tempdir::TempDir;

pub type CommitsMetadata = HashMap<CommitType, CommitConfig>;

pub const CONFIG_PATH: &str = "cog.toml";

lazy_static! {
Expand Down Expand Up @@ -335,12 +336,8 @@ impl CocoGitto {
let statuses = self.repository.get_statuses()?;

// Fail if repo contains un-staged or un-committed changes
if !statuses.is_empty() {
return Err(anyhow!(
"{}\n{}",
"repository contains unstaged change (use `git add` to track)",
self.repository.statuses_display()?,
));
if !statuses.0.is_empty() {
return Err(anyhow!("{}", self.repository.get_statuses()?));
}

let current_tag = self
Expand Down

0 comments on commit 7191f4e

Please sign in to comment.