1use std::path::Path;
6use std::process::Command;
7
8#[derive(Clone, Debug)]
10pub struct GitOutput {
11 pub stdout: String,
12 pub stderr: String,
13 pub success: bool,
15}
16
17impl GitOutput {
18 pub fn trimmed(&self) -> &str {
20 self.stdout.trim()
21 }
22}
23
24pub trait Git {
26 fn run(&self, dir: &Path, args: &[&str]) -> GitOutput;
28}
29
30pub struct SystemGit;
32
33impl Git for SystemGit {
34 fn run(&self, dir: &Path, args: &[&str]) -> GitOutput {
35 match Command::new("git").arg("-C").arg(dir).args(args).output() {
36 Ok(o) => GitOutput {
37 stdout: String::from_utf8_lossy(&o.stdout).into_owned(),
38 stderr: String::from_utf8_lossy(&o.stderr).into_owned(),
39 success: o.status.success(),
40 },
41 Err(e) => GitOutput {
42 stdout: String::new(),
43 stderr: format!("failed to run git: {e}"),
44 success: false,
45 },
46 }
47 }
48}
49
50#[cfg(test)]
51pub mod test_support {
52 use super::*;
53 use std::collections::HashMap;
54
55 #[derive(Default)]
57 pub struct FakeGit {
58 responses: HashMap<String, GitOutput>,
59 }
60
61 impl FakeGit {
62 pub fn new() -> Self {
63 Self::default()
64 }
65
66 pub fn ok(mut self, args: &str, stdout: &str) -> Self {
68 self.responses.insert(
69 args.to_string(),
70 GitOutput {
71 stdout: stdout.to_string(),
72 stderr: String::new(),
73 success: true,
74 },
75 );
76 self
77 }
78
79 pub fn fail(mut self, args: &str) -> Self {
81 self.responses.insert(
82 args.to_string(),
83 GitOutput {
84 stdout: String::new(),
85 stderr: String::new(),
86 success: false,
87 },
88 );
89 self
90 }
91
92 pub fn ok_in(mut self, dir: &str, args: &str, stdout: &str) -> Self {
95 self.responses.insert(
96 format!("{dir}\u{0}{args}"),
97 GitOutput {
98 stdout: stdout.to_string(),
99 stderr: String::new(),
100 success: true,
101 },
102 );
103 self
104 }
105 }
106
107 impl Git for FakeGit {
108 fn run(&self, dir: &Path, args: &[&str]) -> GitOutput {
109 let joined = args.join(" ");
110 let dir_disp = dir.display().to_string().replace('\\', "/");
113 let dir_key = format!("{dir_disp}\u{0}{joined}");
114 self.responses
116 .get(&dir_key)
117 .or_else(|| self.responses.get(&joined))
118 .cloned()
119 .unwrap_or(GitOutput {
120 stdout: String::new(),
121 stderr: format!("FakeGit: no response for `git {joined}` in {dir_disp}"),
122 success: false,
123 })
124 }
125 }
126}