use std::{process::Command, rc::Rc, str::FromStr};
use crate::{
application::{
manager::{
bounded_commit_summary_ingress_manager::BoundedCommitSummaryIngressManager,
commit_metadata_ingress_manager::CommitMetadataIngressManager,
full_commit_summary_history_ingress_manager::FullCommitSummaryHistoryIngressManager,
gitinfo_ingress_manager::GitInfoIngressManager,
version_ingress_manager::VersionIngressManager,
},
manager::{
conventional_commit_egress_manager::ConventionalCommitEgressManager,
git_tree_ingress_manager::GitTreeIngressManager,
init_egress_manager::InitEgressManager, tag_egress_manager::TagEgressManager,
},
},
domain::semantic_version::SemanticVersion,
infrastructure::error::{
command_execution_error::CommandExecutionError, generic_cli_error::CliError,
},
usecase::{metadata_spec::MetadataSpec, type_aliases::AnyError},
};
pub struct GitCli {}
impl GitCli {
pub fn new() -> GitCli {
GitCli {}
}
fn run_git_command<'a>(
&self,
args: impl Iterator<Item = &'a str> + Clone,
) -> Result<String, AnyError> {
let execution_output = Command::new("git")
.args(args.clone())
.output()
.map_err(|e| {
CommandExecutionError::new(
&format!(
"{} {}",
"git",
args.fold(String::new(), |acc, x| acc.to_owned() + " " + x)
),
Box::new(e),
)
})?;
if execution_output.status.success() {
Ok(std::str::from_utf8(&execution_output.stdout)?
.trim()
.to_string())
} else {
Err(Box::new(CliError::new(std::str::from_utf8(
&execution_output.stderr,
)?)))
}
}
fn split_and_clean_commits(&self, list: String) -> Vec<String> {
list.split('\n')
.filter(|it| !it.is_empty())
.map(|it| it.to_string())
.collect()
}
}
impl FullCommitSummaryHistoryIngressManager for GitCli {
fn get_all_commits(&self) -> Result<Box<dyn DoubleEndedIterator<Item = String>>, AnyError> {
let log_list =
self.run_git_command(vec!["log", "--pretty=format:%s", "--all"].into_iter())?;
Ok(Box::new(self.split_and_clean_commits(log_list).into_iter()))
}
}
impl BoundedCommitSummaryIngressManager for GitCli {
fn get_commits_from(
&self,
version: Rc<Option<SemanticVersion>>,
) -> Result<Box<dyn DoubleEndedIterator<Item = String>>, AnyError> {
let mut args = vec!["log", "--pretty=format:%s"];
let mut _s = String::new();
if let Some(value) = version.as_ref() {
_s = format!("^{}", value);
args.push(&_s);
args.push("HEAD");
}
let log_list = self.run_git_command(args.into_iter())?;
Ok(Box::new(self.split_and_clean_commits(log_list).into_iter()))
}
}
impl VersionIngressManager for GitCli {
fn last_version(&self) -> Result<Option<String>, AnyError> {
let output = self.run_git_command(vec!["describe", "--tags", "--abbrev=0"].into_iter());
match output {
Ok(v) => Ok(Some(SemanticVersion::from_str(v.trim())?.to_string())),
Err(e) if e.to_string().contains("No names found") => Ok(None),
Err(e) => Err(e),
}
}
fn last_stable_version(&self) -> Result<Option<String>, AnyError> {
let output =
self.run_git_command(vec!["--no-pager", "tag", "--list", "--merged"].into_iter());
match output {
Ok(v) => {
if v.trim().is_empty() {
Ok(None)
} else {
Ok(v.trim()
.split('\n')
.filter_map(|it| SemanticVersion::from_str(it).ok())
.filter(|it| it.prerelease().is_none())
.max()
.map(|it| it.to_string()))
}
}
Err(e) => Err(e),
}
}
}
impl GitInfoIngressManager for GitCli {
fn git_dir(&self) -> Result<String, AnyError> {
self.run_git_command(vec!["rev-parse", "--absolute-git-dir"].into_iter())
}
}
impl ConventionalCommitEgressManager for GitCli {
fn create_commit(&self, commit: &str) -> Result<(), AnyError> {
self.run_git_command(vec!["commit", "-m", commit].into_iter())
.map(|_| ())
}
fn create_empty_commit(&self, commit: &str) -> Result<(), AnyError> {
self.run_git_command(vec!["commit", "--allow-empty", "-m", commit].into_iter())
.map(|_| ())
}
}
impl InitEgressManager for GitCli {
fn init_repository(&self) -> Result<(), AnyError> {
self.run_git_command(vec!["init"].into_iter()).map(|_| ())
}
}
impl TagEgressManager for GitCli {
fn create_tag(&self, label: &str, message: Option<&str>, sign: bool) -> Result<(), AnyError> {
let mut args = vec!["tag", label];
args.push("--cleanup=whitespace");
args.push("-m");
match message {
Some(s) => {
args.push(s);
}
None => {
args.push("");
}
}
if sign {
args.push("-s");
}
self.run_git_command(args.into_iter()).map(|_| ())
}
}
impl CommitMetadataIngressManager for GitCli {
fn get_metadata(&self, metadata_spec: &MetadataSpec) -> Result<String, AnyError> {
match metadata_spec {
MetadataSpec::Sha => {
self.run_git_command(vec!["log", "-n", "1", "--pretty=format:%h"].into_iter())
}
MetadataSpec::Date => {
self.run_git_command(vec!["log", "-n", "1", "--pretty=format:%as"].into_iter())
}
}
}
}
impl GitTreeIngressManager for GitCli {
fn commit_tree(&self, format: &str) -> Result<Box<[String]>, AnyError> {
Ok(self
.run_git_command(
vec![
"log",
"--all",
"--graph",
"--decorate=short",
"--date-order",
"--color",
&format!("--pretty=format:{}", format),
]
.into_iter(),
)?
.split('\n')
.map(|it| it.to_string())
.collect())
}
}