use std::path::Path;
use std::process::Command;
#[derive(Clone, Debug)]
pub struct GitOutput {
pub stdout: String,
pub stderr: String,
pub success: bool,
}
impl GitOutput {
pub fn trimmed(&self) -> &str {
self.stdout.trim()
}
}
pub trait Git {
fn run(&self, dir: &Path, args: &[&str]) -> GitOutput;
}
pub struct SystemGit;
impl Git for SystemGit {
fn run(&self, dir: &Path, args: &[&str]) -> GitOutput {
match Command::new("git").arg("-C").arg(dir).args(args).output() {
Ok(o) => GitOutput {
stdout: String::from_utf8_lossy(&o.stdout).into_owned(),
stderr: String::from_utf8_lossy(&o.stderr).into_owned(),
success: o.status.success(),
},
Err(e) => GitOutput {
stdout: String::new(),
stderr: format!("failed to run git: {e}"),
success: false,
},
}
}
}
#[cfg(test)]
pub mod test_support {
use super::*;
use std::collections::HashMap;
#[derive(Default)]
pub struct FakeGit {
responses: HashMap<String, GitOutput>,
}
impl FakeGit {
pub fn new() -> Self {
Self::default()
}
pub fn ok(mut self, args: &str, stdout: &str) -> Self {
self.responses.insert(
args.to_string(),
GitOutput {
stdout: stdout.to_string(),
stderr: String::new(),
success: true,
},
);
self
}
pub fn fail(mut self, args: &str) -> Self {
self.responses.insert(
args.to_string(),
GitOutput {
stdout: String::new(),
stderr: String::new(),
success: false,
},
);
self
}
pub fn ok_in(mut self, dir: &str, args: &str, stdout: &str) -> Self {
self.responses.insert(
format!("{dir}\u{0}{args}"),
GitOutput {
stdout: stdout.to_string(),
stderr: String::new(),
success: true,
},
);
self
}
}
impl Git for FakeGit {
fn run(&self, dir: &Path, args: &[&str]) -> GitOutput {
let joined = args.join(" ");
let dir_disp = dir.display().to_string().replace('\\', "/");
let dir_key = format!("{dir_disp}\u{0}{joined}");
self.responses
.get(&dir_key)
.or_else(|| self.responses.get(&joined))
.cloned()
.unwrap_or(GitOutput {
stdout: String::new(),
stderr: format!("FakeGit: no response for `git {joined}` in {dir_disp}"),
success: false,
})
}
}
}