1use std::ffi::{OsStr, OsString};
5use std::io;
6use std::io::Write;
7use std::path::Path;
8use std::process::{Command, ExitStatus, Stdio};
9
10#[derive(Debug, Clone)]
12pub struct Git {
13 globals: Vec<OsString>,
14}
15
16impl Git {
17 pub fn new(globals: Vec<OsString>) -> Self {
18 Git { globals }
19 }
20
21 fn command(&self, directory: Option<&Path>) -> Command {
24 let mut command = Command::new("git");
25 command.args(&self.globals);
26 if let Some(directory) = directory {
27 command.arg("-C").arg(directory);
28 }
29 command
30 }
31
32 pub fn capture<I, S>(&self, directory: Option<&Path>, args: I) -> io::Result<Vec<u8>>
34 where
35 I: IntoIterator<Item = S>,
36 S: AsRef<OsStr>,
37 {
38 let output = self
39 .command(directory)
40 .args(args)
41 .stdin(Stdio::null())
42 .stderr(Stdio::null())
43 .output()?;
44 if !output.status.success() {
45 return Err(io::Error::other("git exited with a failure status"));
46 }
47 Ok(output.stdout)
48 }
49
50 pub fn passthrough<I, S>(&self, directory: Option<&Path>, args: I) -> io::Result<ExitStatus>
52 where
53 I: IntoIterator<Item = S>,
54 S: AsRef<OsStr>,
55 {
56 self.command(directory).args(args).status()
57 }
58
59 pub fn capture_with_input<I, S>(
61 &self,
62 directory: Option<&Path>,
63 args: I,
64 input: &[u8],
65 ) -> io::Result<Vec<u8>>
66 where
67 I: IntoIterator<Item = S>,
68 S: AsRef<OsStr>,
69 {
70 let mut child = self
71 .command(directory)
72 .args(args)
73 .stdin(Stdio::piped())
74 .stdout(Stdio::piped())
75 .stderr(Stdio::null())
76 .spawn()?;
77 let stdin = child.stdin.take();
81 let input = input.to_vec();
82 let writer = std::thread::spawn(move || {
83 if let Some(mut stdin) = stdin {
84 let _ = stdin.write_all(&input);
85 }
86 });
87 let output = child.wait_with_output()?;
88 let _ = writer.join();
89 if !output.status.success() {
90 return Err(io::Error::other("git exited with a failure status"));
91 }
92 Ok(output.stdout)
93 }
94
95 pub fn capture_line<I, S>(&self, directory: Option<&Path>, args: I) -> io::Result<String>
97 where
98 I: IntoIterator<Item = S>,
99 S: AsRef<OsStr>,
100 {
101 let bytes = self.capture(directory, args)?;
102 let text =
103 String::from_utf8(bytes).map_err(|_| io::Error::other("git printed non-UTF-8"))?;
104 Ok(text.trim_end_matches(['\n', '\r']).to_string())
105 }
106
107 pub fn config(&self, directory: &Path, key: &str) -> Option<String> {
109 self.capture_line(Some(directory), ["config", "--get", key])
110 .ok()
111 }
112}