use std::borrow::Cow;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use crate::message::Message;
use crate::path::is_executable_path::IsExecutablePath;
use super::gamble_result::GambleError;
use super::gamble_result::GambleResult;
pub(crate) struct Repository {
path: PathBuf,
dry_run: bool,
no_verify: bool,
}
impl Repository {
pub(crate) fn new(path: &Path, dry_run: bool, no_verify: bool) -> Repository {
Repository {
path: path.to_path_buf(),
dry_run,
no_verify,
}
}
fn path(&self) -> &Path {
Path::new(&self.path)
}
}
impl Repository {
fn shell_command(&self, program: &str, args: &[&str]) -> GambleResult {
let executed_command =
&format!("`{program} {}` in {}", args.join(" "), self.path.display());
if self.dry_run {
log::info!("Would execute {executed_command}");
return Ok(());
}
let help_message = &format!("Failed to execute {executed_command}");
let status = Command::new(program)
.current_dir(self.path())
.args(args)
.status()
.expect(help_message);
match status.code() {
Some(code) => {
log::info!("{executed_command} exited with {code}");
if status.success() {
Ok(())
} else {
Err(GambleError {
messages: vec![],
code,
})
}
}
None => Err(GambleError {
messages: vec![Message::Error(format!(
"{executed_command} has exited without a normal code"
))],
code: 1,
}),
}
}
pub(crate) fn command(&self, args: &[&str]) -> GambleResult {
self.shell_command("git", args).map_err(|error| {
let executed_command =
&format!("`git {}` in {}", args.join(" "), self.path().display());
let help_message = format!(
"Failed to execute {executed_command} returns code {}",
error.code
);
error.add_message(Message::Fatal(help_message))
})
}
fn get_hooks_path(&self) -> Cow<'_, Path> {
self.query(&["config", "core.hooksPath"]).map_or_else(
|_| Cow::from(Path::new(".git").join("hooks")),
|hooks_path_output| Cow::from(PathBuf::default().join(hooks_path_output.trim())),
)
}
pub(crate) fn run_hook(&self, hook_name: &str, args: &[&str]) -> GambleResult {
let hooks_path = self.get_hooks_path();
let hook_path = hooks_path.join(hook_name);
if !hook_path.exists() {
return Ok(());
}
if !hook_path.is_executable() {
return Ok(());
}
if self.no_verify {
log::info!("Would execute {hook_name} hook");
return Ok(());
}
let hook_path_as_string = hook_path.to_str().unwrap(); let hook_with_arguments = [vec![hook_path_as_string], args.to_vec()].concat();
self.shell_command("sh", &hook_with_arguments)
.map_err(|error| {
error
.add_message(Message::Error(format!(
"The {hook_name} hook returns code {}, but it should be 0",
error.code
)))
.add_message(Message::Info(format!(
"You can execute : git hook run {hook_name} -- {}",
args.join(" "),
)))
})
}
}
impl Repository {
fn query(&self, args: &[&str]) -> io::Result<String> {
let query = &format!("`git {}` in {}", args.join(" "), self.path.display());
let help_message = &format!("Failed to execute {query}");
let cmd = Command::new("git")
.current_dir(self.path())
.args(args)
.output()
.expect(help_message);
match cmd.status.code() {
Some(code) => {
log::info!("query {query} exited with {code}");
if cmd.status.success() {
Ok(String::from_utf8(cmd.stdout).unwrap()) } else {
Err(io::Error::other(help_message.to_string())) }
}
None => todo!(), }
}
fn get_commit_hash_for(&self, revision: &str) -> io::Result<String> {
self.query(&["rev-parse", revision])
}
pub(crate) fn head_is_failing_ref(&self) -> bool {
let head_commit_hash = self.get_commit_hash_for("HEAD");
let ref_is_failing_commit_hash = self.get_commit_hash_for("gamble-is-failing");
match (head_commit_hash, ref_is_failing_commit_hash) {
(Ok(head_commit_hash), Ok(ref_is_failing_commit_hash)) => {
head_commit_hash == ref_is_failing_commit_hash
}
_ => false,
}
}
pub(crate) fn is_clean(&self) -> bool {
let status = self.query(&["status", "-z"]).expect("Can't get status");
status.is_empty()
}
}